heapinfo 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ba7cc3de8a173a616d298c30836fa0bb5a2800a
4
+ data.tar.gz: 792feedd7e5a6deea7b956082150cb7b4609d887
5
+ SHA512:
6
+ metadata.gz: d97610801e28487dd52e3e100558e746e8d23784a7925be2f99e4e35dd101c9976cbb526e1f18c18105b4a6bef2b00a305139f5406bd02021cedb75df6394749
7
+ data.tar.gz: 434774059d8ff5f5fcebc1fb81788a8fd9e78856f295c5bbfd3a045ae1b8b28cfa5237d4bd5cd330f71b319e1333efb65cfaffc557263721836a90f48687480f
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ [![Build Status](https://travis-ci.org/david942j/heapinfo.svg?branch=master)](https://travis-ci.org/david942j/heapinfo)
2
+ [![Code Climate](https://codeclimate.com/github/david942j/heapinfo/badges/gpa.svg)](https://codeclimate.com/github/david942j/heapinfo)
3
+ [![Issue Count](https://codeclimate.com/github/david942j/heapinfo/badges/issue_count.svg)](https://codeclimate.com/github/david942j/heapinfo)
4
+ [![Test Coverage](https://codeclimate.com/github/david942j/heapinfo/badges/coverage.svg)](https://codeclimate.com/github/david942j/heapinfo/coverage)
5
+ [![Inline docs](https://inch-ci.org/github/david942j/heapinfo.svg?branch=master)](https://inch-ci.org/github/david942j/heapinfo)
6
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
7
+
8
+ ## HeapInfo
9
+ As pwn lovers, while playing CTF with heap exploitation, we always need a debugger (e.g. gdb) for tracking memory layout. But we don't really need a debugger if we just want to see whether the heap layout same as our imagine or not. Hope this small tool helps us exploit easier ;).
10
+
11
+ Implement with ruby because I love ruby :P. But might also implement with Python (if no others did) in the future.
12
+
13
+ If you prefer [pwntools](https://github.com/Gallopsled/pwntools) for exploiting, you can still use **HeapInfo** in irb/pry as a small debugger.
14
+
15
+ Any suggestion of features or bug issues is welcome.
16
+
17
+ Relation works are [pwntools-ruby](https://github.com/peter50216/pwntools-ruby) and [gdbpwn](https://github.com/scwuaptx/Pwngdb).
18
+
19
+ ## Install
20
+ **HeapInfo** is still under developing for more features, so the version might change frequently :p
21
+
22
+ ```
23
+ $ gem install heapinfo
24
+ ```
25
+
26
+ ## Features
27
+ * Can use in your ruby exploit script or in irb/pry
28
+ * **HeapInfo** works when the `victim` is being traced! i.e. you can use ltrace/strace/gdb and **HeapInfo** simultaneously!
29
+ * `dump` - dump arbitrarily address memory.
30
+ * `layouts` - show the current bin layouts, very useful for heap exploitation.
31
+ * `x` - Provide gdb-like commands.
32
+ * `find` - Provide gdb-like commands.
33
+ * More features and details can be found in [RDoc](http://www.rubydoc.info/github/david942j/heapinfo/master/)
34
+
35
+ ## Usage
36
+
37
+ #### Load
38
+
39
+ ```ruby
40
+ require 'heapinfo'
41
+ # ./victim is running
42
+ h = heapinfo('victim')
43
+ # or use h = heapinfo(20568) to prevent multi processes exist
44
+
45
+ # will present simple info when loading:
46
+ # Program: /home/heapinfo/victim PID: 20568
47
+ # victim base @ 0x400000
48
+ # [heap] base @ 0x11cc000
49
+ # [stack] base @ 0x7fff2b244000
50
+ # libc-2.19.so base @ 0x7f892a63a000
51
+ # ld-2.19.so base @ 0x7f892bee6000
52
+
53
+ # query segments' info
54
+ "%#x" % h.libc.base
55
+ # => "0x7f892a63a000"
56
+ h.libc.name
57
+ # => "/lib/x86_64-linux-gnu/libc-2.19.so"
58
+ "%#x" % h.elf.base
59
+ # => "0x400000"
60
+ "%#x" % h.heap.base
61
+ # => "0x11cc000"
62
+ ```
63
+
64
+ NOTICE: While the process is not found, most methods will return `nil`. One way to prevent some error happend is to wrapper methods within `debug`, the block will be ignored while doing remote exploitation.
65
+
66
+ ```ruby
67
+ h = heapinfo('remote')
68
+ # Process not found
69
+ h.pid # nil
70
+ h.debug {
71
+ fail unless leak_libc_base == h.libc.base
72
+ # wrapper with `debug` so that no error will be raised when pwning remote service
73
+ }
74
+ ```
75
+
76
+ #### Dump
77
+ Query content of specific address.
78
+
79
+ NOTICE: you MUST have permission of attaching a program, otherwise dump will fail.
80
+
81
+ i.e. `/proc/sys/kernel/yama/ptrace_scope` set to 0 or run as root.
82
+
83
+ ```ruby
84
+ h.debug {
85
+ p h.dump(:libc, 8)
86
+ # => "\x7FELF\x02\x01\x01\x00"
87
+ p h.dump(:heap, 16)
88
+ # => "\x00\x00\x00\x00\x00\x00\x00\x00\x31\x00\x00\x00\x00\x00\x00\x00"
89
+ p h.dump('heap+0x30, 16') # support offset!
90
+ # => "\x00\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00"
91
+ p h.dump(:elf, 8)
92
+ # => "\x7FELF\x02\x01\x01\x00"
93
+ p h.dump(0x400000, 8) # or simply give addr
94
+ # => "\x7FELF\x02\x01\x01\x00"
95
+ }
96
+ # invalid examples:
97
+ # h.dump('meow') # no such segment
98
+ # h.dump('heap-1, 64') # not support `-`
99
+ ```
100
+
101
+ #### layouts
102
+ ```ruby
103
+ h.layouts :fastbin
104
+ ```
105
+ ![fastbin layouts](https://github.com/david942j/heapinfo/blob/master/examples/fastbin_layouts.png?raw=true)
106
+
107
+ ```ruby
108
+ h.layouts :unsorted_bin, :smallbin
109
+ ```
110
+ ![smallbin layouts](https://github.com/david942j/heapinfo/blob/master/examples/unsorted_smallbin_layouts.png?raw=true)
111
+
112
+ #### x - gdb-like command
113
+ ```ruby
114
+ h.x 8, :heap
115
+ ```
116
+ ![x/8gx](https://github.com/david942j/heapinfo/blob/master/examples/x8_heap.png?raw=true)
117
+
118
+ #### find - gdb-like command
119
+ Provide a searcher of memory, easier to use than in (naive) gdb.
120
+
121
+ Support search integer, string, and even regular expression.
122
+
123
+ ```ruby
124
+ h.find(/E.F/, 0x400000, 4)
125
+ # => 4194305 # 0x400001
126
+ h.find(/E.F/, 0x400000, 3)
127
+ # => nil
128
+ sh_offset = h.find('/bin/sh', :libc) - h.libc.base
129
+ # => 1559771 # 0x17ccdb
130
+ ```
data/lib/heapinfo.rb ADDED
@@ -0,0 +1,75 @@
1
+ # Basic requirements from standard library
2
+ require 'fileutils'
3
+
4
+ # HeapInfo - an interactive debugger for heap exploitation
5
+ #
6
+ # HeapInfo makes pwning life easier with ruby style memory dumper.
7
+ # Easy to show bin(s) layouts, or dump memory for checking whether exploit (will) works.
8
+ # HeapInfo can be used with ltrace/strace/gdb simultaneously since it not use any ptrace.
9
+ #
10
+ # @author david942j
11
+ module HeapInfo
12
+ # Directory for writing some tmp files when working,
13
+ # make sure /tmp is writable
14
+ TMP_DIR = '/tmp/.heapinfo'
15
+
16
+ # Directory for caching files.
17
+ # e.g. HeapInfo will record main_arena_offset for glibc(s)
18
+ CACHE_DIR = '~/.cache/heapinfo'
19
+
20
+ FileUtils.mkdir_p(TMP_DIR)
21
+ FileUtils.mkdir_p(CACHE_DIR)
22
+
23
+ # Entry point for using HeapInfo.
24
+ # Show segments info of the process after loaded
25
+ # @param [String, Fixnum] prog The program name of victim. If a number is given, seem as pid (useful when multi-processes exist)
26
+ # @param [Hash] options Give library's file name.
27
+ # @option options [String, Regexp] :libc file name of glibc, default is <tt>/libc[^\w]/</tt>
28
+ # @option options [String, Regexp] :ld file name of dynamic linker/loader, default is <tt>/\/ld-.+\.so/</tt>
29
+ # @return [HeapInfo::Process] The object for further usage
30
+ # @example
31
+ # h = heapinfo './victim'
32
+ # # outputs:
33
+ # # Program: /home/heapinfo/victim PID: 20568
34
+ # # victim base @ 0x400000
35
+ # # [heap] base @ 0x11cc000
36
+ # # [stack] base @ 0x7fff2b244000
37
+ # # libc-2.19.so base @ 0x7f892a63a000
38
+ # # ld-2.19.so base @ 0x7f892bee6000
39
+ # p h.libc.name
40
+ # # => "/lib/x86_64-linux-gnu/libc-2.19.so"
41
+ # p h.ld.name
42
+ # # => "/lib/x86_64-linux-gnu/ld-2.19.so"
43
+ #
44
+ # @example
45
+ # h = heapinfo(27605, libc: 'libc.so.6', ld: 'ld-linux-x86-64.so.2')
46
+ # # pid 27605 is run by custom loader
47
+ # p h.libc.name
48
+ # # => "/home/heapinfo/libc.so.6"
49
+ # p h.ld.name
50
+ # # => "/home/heapinfo/ld-linux-x86-64.so.2"
51
+ def self.heapinfo(prog, options = {})
52
+ h = HeapInfo::Process.new(prog, options)
53
+ puts h
54
+ h
55
+ end
56
+ end
57
+
58
+ # Alias method of #HeapInfo::heapinfo for global usage
59
+ # @return [HeapInfo::Process]
60
+ # @param [Mixed] args see #HeapInfo::heapinfo for more information
61
+ def heapinfo(*args)
62
+ ::HeapInfo::heapinfo(*args)
63
+ end
64
+
65
+
66
+ require 'heapinfo/helper'
67
+ require 'heapinfo/nil'
68
+ require 'heapinfo/process'
69
+ require 'heapinfo/segment'
70
+ require 'heapinfo/libc'
71
+ require 'heapinfo/chunk'
72
+ require 'heapinfo/chunks'
73
+ require 'heapinfo/arena'
74
+ require 'heapinfo/dumper'
75
+ require 'heapinfo/ext/string.rb'
@@ -0,0 +1,193 @@
1
+ module HeapInfo
2
+ class Arena
3
+ attr_reader :fastbin, :unsorted_bin, :smallbin, :top_chunk
4
+ # attr_reader :largebin, :last_remainder
5
+
6
+ # Instantiate a <tt>HeapInfo::Arena</tt> object
7
+ #
8
+ # @param [Integer] base Base address of arena.
9
+ # @param [Integer] bits Either 64 or 32
10
+ # @param [Proc] dumper For dump more data
11
+ def initialize(base, bits, dumper)
12
+ @base, @dumper = base, dumper
13
+ @size_t = bits / 8
14
+ reload
15
+ end
16
+
17
+ # Refresh all attributes
18
+ # Retrive data using <tt>@dumper</tt>, load bins, top chunk etc.
19
+ # @return [HeapInfo::Arena] self
20
+ def reload
21
+ top_ptr = Helper.unpack(size_t, @dumper.call(@base + 8 + size_t * 10, size_t))
22
+ @fastbin = []
23
+ return self if top_ptr == 0 # arena not init yet
24
+ @top_chunk = Chunk.new size_t, top_ptr, @dumper
25
+ @fastbin = Array.new(7) do |idx|
26
+ f = Fastbin.new(size_t, @base + 8 - size_t * 2 + size_t * idx, @dumper, head: true)
27
+ f.index = idx
28
+ f
29
+ end
30
+ @unsorted_bin = UnsortedBin.new(size_t, @base + 8 + size_t * 10, @dumper, head: true)
31
+ @smallbin = Array.new(55) do |idx|
32
+ s = Smallbin.new(size_t, @base + 8 + size_t * (26 + 2 * idx), @dumper, head: true)
33
+ s.index = idx
34
+ s
35
+ end
36
+ self
37
+ end
38
+
39
+ # Pretty dump of bins layouts.
40
+ #
41
+ # @param [Symbol] args Bin type(s) you want to see.
42
+ # @return [String] Bin layouts that wrapper with color codes.
43
+ # @example
44
+ # puts h.libc.main_arena.layouts :fastbin, :unsorted_bin, :smallbin
45
+ def layouts(*args)
46
+ res = ''
47
+ res += fastbin.map(&:inspect).join if args.include? :fastbin
48
+ res += unsorted_bin.inspect if args.include? :unsorted_bin
49
+ res += smallbin.map(&:inspect).join if args.include? :smallbin
50
+ res
51
+ end
52
+
53
+ private
54
+ attr_reader :size_t
55
+ end
56
+
57
+ class Fastbin < Chunk
58
+ attr_reader :fd
59
+ attr_accessor :index
60
+
61
+ # Instantiate a <tt>HeapInfo::Fastbin</tt> object
62
+ #
63
+ # @param [Mixed] args See <tt>HeapInfo::Chunk</tt> for more information.
64
+ def initialize(*args)
65
+ super
66
+ @fd = Helper.unpack(size_t, @data[0, @size_t])
67
+ end
68
+
69
+ # Mapping index of fastbin to chunk size
70
+ # @return [Integer] size
71
+ def idx_to_size
72
+ index * size_t * 2 + size_t * 4
73
+ end
74
+
75
+ # For pretty inspect
76
+ # @return [String] Title with color codes
77
+ def title
78
+ "%s%s: " % [Helper.color(Helper.class_name(self), sev: :bin), index.nil? ? nil : "[#{Helper.color("%#x" % idx_to_size)}]"]
79
+ end
80
+
81
+ def inspect
82
+ title + list.map do |ptr|
83
+ next "(#{ptr})\n" if ptr.is_a? Symbol
84
+ next " => (nil)\n" if ptr.nil?
85
+ " => %s" % Helper.color("%#x" % ptr)
86
+ end.join
87
+ end
88
+
89
+ # @return [Array<Integer, Symbol, NilClass>] single link list of <tt>fd</tt> chain.
90
+ # Last element will be:
91
+ # - <tt>:loop</tt> if loop detectded
92
+ # - <tt>:invalid</tt> invalid address detected
93
+ # - <tt>nil</tt> end with zero address (normal case)
94
+ def list
95
+ dup = {}
96
+ ptr = @fd
97
+ ret = []
98
+ while ptr != 0
99
+ ret << ptr
100
+ return ret << :loop if dup[ptr]
101
+ dup[ptr] = true
102
+ ptr = fd_of(ptr)
103
+ return ret << :invalid if ptr.nil?
104
+ end
105
+ ret << nil
106
+ end
107
+
108
+ def fd_of(ptr)
109
+ addr_of(ptr, 2)
110
+ end
111
+
112
+ def bk_of(ptr)
113
+ addr_of(ptr, 3)
114
+ end
115
+
116
+ private
117
+ def addr_of(ptr, offset)
118
+ t = dump(ptr + size_t * offset, size_t)
119
+ return nil if t.nil?
120
+ Helper.unpack(size_t, t)
121
+ end
122
+ end
123
+
124
+ class UnsortedBin < Fastbin
125
+ attr_reader :bk
126
+ def initialize(*args)
127
+ super
128
+ @bk = Helper.unpack(size_t, @data[@size_t, @size_t])
129
+ end
130
+
131
+ # size: at most extend size
132
+ # size=2: bk, bk, bin, fd, fd
133
+ def inspect(size: 2)
134
+ list = link_list(size)
135
+ return '' if list.size <= 1 and Helper.class_name(self) != 'UnsortedBin' # bad..
136
+ title + pretty_list(list) + "\n"
137
+ end
138
+
139
+ def pretty_list(list)
140
+ center = nil
141
+ list.map.with_index do |c, idx|
142
+ next center = Helper.color("[self]", sev: :bin) if c == @base
143
+ fwd = fd_of(c)
144
+ next "%s(invalid)" % Helper.color("%#x" % c) if fwd.nil? # invalid c
145
+ bck = bk_of(c)
146
+ if center.nil? # bk side
147
+ next Helper.color("%s%s" % [
148
+ Helper.color("%#x" % c),
149
+ fwd == list[idx+1] ? nil : "(%#x)" % fwd,
150
+ ])
151
+ else #fd side
152
+ next Helper.color("%s%s" % [
153
+ bck == list[idx-1] ? nil : "(%#x)" % bck,
154
+ Helper.color("%#x" % c),
155
+ ])
156
+ end
157
+ end.join(" === ")
158
+ end
159
+
160
+ def link_list(expand_size)
161
+ list = [@base]
162
+ # fd
163
+ work = Proc.new do |ptr, nxt, append|
164
+ sz = 0
165
+ dup = {}
166
+ while ptr != @base and sz < expand_size
167
+ append.call ptr
168
+ break if ptr.nil? # invalid pointer
169
+ break if dup[ptr] # looped
170
+ dup[ptr] = true
171
+ ptr = self.send(nxt, ptr)
172
+ sz += 1
173
+ end
174
+ end
175
+ work.call(@fd, :fd_of, lambda{|ptr| list << ptr})
176
+ work.call(@bk, :bk_of, lambda{|ptr| list.unshift ptr})
177
+ list
178
+ end
179
+ end
180
+
181
+ class Smallbin < UnsortedBin
182
+
183
+ # Mapping index of smallbin to chunk size
184
+ # @return [Integer] size
185
+ def idx_to_size
186
+ index * size_t * 2 + size_t * 18
187
+ end
188
+ end
189
+
190
+ # class Largebin < Smallbin
191
+ # attr_accessor :fd_nextsize, :bk_nextsize
192
+ # end
193
+ end
@@ -0,0 +1,96 @@
1
+ module HeapInfo
2
+ # The object of a heap chunk
3
+ class Chunk
4
+ # @return [Integer] 4 or 8 according to 32bit or 64bit, respectively.
5
+ attr_reader :size_t
6
+ # @return [Integer] previous chunk size
7
+ attr_reader :prev_size
8
+ # @return [String] chunk data
9
+ attr_reader :data
10
+ # @return [Integer] Base address of this chunk
11
+ attr_reader :base
12
+
13
+ # Instantiate a <tt>HeapInfo::Chunk</tt> object
14
+ #
15
+ # @param [Integer] size_t 4 or 8
16
+ # @param [Integer] base Start address of this chunk
17
+ # @param [Proc] dumper For dump more information of this chunk
18
+ # @param [Boolean] head For specific if is fake chunk in <tt>arena</tt>. If <tt>head</tt> is <tt>true</tt>, will not load <tt>size</tt> and <tt>prev_size</tt> (since it's meaningless)
19
+ # @example
20
+ # HeapInfo::Chunk.new 8, 0x602000, lambda{|addr, len| [0,0x21, 0xda4a].pack("Q*")[addr-0x602000, len]}
21
+ # # create a chunk with chunk size 0x21
22
+ def initialize(size_t, base, dumper, head: false)
23
+ fail unless [4, 8].include? size_t
24
+ self.class.send(:define_method, :dump){|*args| dumper.call(*args)}
25
+ @size_t = size_t
26
+ @base = base
27
+ sz = dump(@base, size_t * 2)
28
+ if head # no need to read size if is bin
29
+ return @data = dump(@base + size_t * 2, size_t * 4)
30
+ end
31
+ @prev_size = Helper.unpack(size_t, sz[0, size_t])
32
+ @size = Helper.unpack(size_t, sz[size_t..-1])
33
+ r_size = [size - size_t * 2, size_t * 4].min # don't read too much data
34
+ r_size = [r_size, 0].max # prevent negative size
35
+ @data = dump(@base + size_t * 2, r_size)
36
+ end
37
+
38
+ # Hook <tt>#to_s</tt> for pretty printing
39
+ # @return [String]
40
+ def to_s
41
+ ret = Helper.color("#<%s:%#x>\n" % [self.class.to_s, @base], sev: :klass) +
42
+ "flags = [#{flags.map{|f|Helper.color(":#{f}", sev: :sym)}.join(',')}]\n" +
43
+ "size = #{Helper.color "%#x" % size} (#{bintype})\n"
44
+ ret += "prev_size = #{Helper.color "%#x" % @prev_size}\n" unless flags.include? :prev_inuse
45
+ ret += "data = #{Helper.color @data.inspect}#{'...' if @data.length < size - size_t * 2}\n"
46
+ ret
47
+ end
48
+
49
+ # The chunk flags record in low three bits of size
50
+ # @return [Array<Symbol>] flags of chunk
51
+ # @example
52
+ # c = [0, 0x25].pack("Q*").to_chunk
53
+ # c.flags
54
+ # # [:non_main_arena, :prev_inuse]
55
+ def flags
56
+ mask = @size - size
57
+ flag = []
58
+ flag << :non_main_arena unless mask & 4 == 0
59
+ flag << :mmapped unless mask & 2 == 0
60
+ flag << :prev_inuse unless mask & 1 == 0
61
+ flag
62
+ end
63
+
64
+ # Size of chunk
65
+ # @return [Integer] The chunk size without flag masks
66
+ def size
67
+ @size & -8
68
+ end
69
+
70
+ # Bin type of this chunk
71
+ # @return [Symbol] Bin type is simply determined according to <tt>#size</tt>
72
+ # @example
73
+ # [c.size, c.size_t]
74
+ # # => [80, 8]
75
+ # c.bintype
76
+ # # => :fast
77
+ # @example
78
+ # [c.size, c.size_t]
79
+ # # => [80, 4]
80
+ # c.bintype
81
+ # # => :small
82
+ # @example
83
+ # c.size
84
+ # # => 135168
85
+ # c.bintype
86
+ # # => :mmap
87
+ def bintype
88
+ sz = size
89
+ return :unknown if sz < @size_t * 4
90
+ return :fast if sz <= @size_t * 16
91
+ return :small if sz <= @size_t * 0x7e
92
+ return :large if sz <= @size_t * 0x3ffe # is this correct?
93
+ return :mmap
94
+ end
95
+ end
96
+ end