heapinfo 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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