heapinfo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b734871451b91231d5c1c159633f1de9ad3255bb
4
- data.tar.gz: 2742c1043b6466cad49415191c9753819e499e2d
3
+ metadata.gz: 0fcd356865d018666cc16677f3c7ab5052f8a43d
4
+ data.tar.gz: f5125e3505ff162b159edacec8d7b00c81e50bb2
5
5
  SHA512:
6
- metadata.gz: 93861f6cd8f05a68666e2e67caff87d810f784f42aac009a4db4ca7b90031dc792a6643f780f316e7e5b43211e34c18eb5bd4a9dab3634dfad44a17d43c9de24
7
- data.tar.gz: e6349ece2cba17b12af3daafb2a62dad995e4a03b0ca83e33a35661ed6985db16cc26e4d50810dedc411ceef497537c964a2804c84a081887cddd3cb1159f4f1
6
+ metadata.gz: f0386440f6b738e43b882473eb913420b2fa92b3b9e5b186d96c680cb25d65a8e2608c2dd25866cd1eb0805f9f0b0ea2a068c533296cf027ae6b4d69bd42ffa7
7
+ data.tar.gz: e31e4a02718653ed6909ca9f644d7101ba58b5eabcb508485bb390a66ac076cb856d5bd8ab380a297e4d0e0b256a465f988e2cce518f379389a73e7700b61d47
data/README.md CHANGED
@@ -10,10 +10,10 @@
10
10
  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 gdb if we want to see whether the heap layout same as our imagine or not. Hope this small tool helps us exploit easier ;).
11
11
 
12
12
  ### Why
13
- **HeapInfo** is very helpful when binary has somehow anti-debugger limitations, e.g being ptraced.
13
+ **HeapInfo** is very helpful when binary has somehow anti-debugger limitations, e.g. being ptraced.
14
14
  **HeapInfo** still works because it doesn't use ptrace.
15
15
 
16
- Implement with ruby because I love ruby :P. But might also implement with Python (if no others did) in the future.
16
+ Implement with Ruby because I love Ruby :P. But might implement with Python (if no others did) in the future.
17
17
 
18
18
  If you prefer [pwntools](https://github.com/Gallopsled/pwntools) for exploiting, you can still use **HeapInfo** in irb/pry as a small debugger.
19
19
 
@@ -33,13 +33,11 @@ $ gem install heapinfo
33
33
  * `layouts` - Show the current bin layouts, very useful for heap exploitation.
34
34
  * `offset` - Show the offset between given address and segment. Very useful for calculating relative offset.
35
35
  * `canary` - Fetch the value of stack guard!
36
- * `x` - Provide gdb-like commands.
37
- * `find` - Provide gdb-like commands.
36
+ * `x` - Provide gdb-like command.
37
+ * `s` - Provide gdb-like command.
38
+ * `find` - Provide gdb-like command.
38
39
  * More features and details can be found in [RDoc](http://www.rubydoc.info/github/david942j/heapinfo/master/)
39
40
 
40
- ### Under developing
41
- * `free` - Show what will happend when calling `glibc#free` an address.
42
-
43
41
  ## Usage
44
42
 
45
43
  #### Load
@@ -48,7 +46,7 @@ $ gem install heapinfo
48
46
  require 'heapinfo'
49
47
  # ./victim is running
50
48
  h = heapinfo('victim')
51
- # or use h = heapinfo(20568) to specific pid
49
+ # or use h = heapinfo(20568) to specify a pid
52
50
 
53
51
  # will present simple info when loading:
54
52
  # Program: /home/heapinfo/victim PID: 20568
@@ -119,6 +117,11 @@ h.layouts :unsorted, :small
119
117
  ```
120
118
  ![smallbin layouts](https://github.com/david942j/heapinfo/blob/master/examples/unsorted_smallbin_layouts.png?raw=true)
121
119
 
120
+ ```ruby
121
+ h.layouts :tcache
122
+ ```
123
+ ![tcache layouts](https://github.com/david942j/heapinfo/blob/master/examples/tcache_layouts.png?raw=true)
124
+
122
125
  #### offset
123
126
  ```ruby
124
127
  h.offset(0x7fda86fe8670)
@@ -169,3 +172,5 @@ h.offset(h.find('/bin/sh', :libc))
169
172
  * libc-2.23
170
173
  * libc-2.24
171
174
  * libc-2.25
175
+ * libc-2.26
176
+ * libc-2.27
@@ -1,6 +1,9 @@
1
1
  # Basic requirements from standard library
2
2
  require 'fileutils'
3
3
 
4
+ require 'heapinfo/ext/string.rb'
5
+ require 'heapinfo/process'
6
+
4
7
  # HeapInfo - an interactive debugger for heap exploitation
5
8
  #
6
9
  # HeapInfo makes pwning life easier with ruby style memory dumper.
@@ -46,9 +49,7 @@ module HeapInfo
46
49
  # p h.ld.name
47
50
  # #=> "/home/heapinfo/ld-linux-x86-64.so.2"
48
51
  def self.heapinfo(prog, options = {})
49
- h = HeapInfo::Process.new(prog, options)
50
- puts h
51
- h
52
+ HeapInfo::Process.new(prog, options).tap { |h| $stdout.puts h }
52
53
  end
53
54
  end
54
55
 
@@ -58,17 +59,3 @@ end
58
59
  def heapinfo(*args)
59
60
  ::HeapInfo.heapinfo(*args)
60
61
  end
61
-
62
- require 'heapinfo/helper'
63
- require 'heapinfo/nil'
64
- require 'heapinfo/cache'
65
- require 'heapinfo/process_info'
66
- require 'heapinfo/process'
67
- require 'heapinfo/segment'
68
- require 'heapinfo/glibc/glibc'
69
- require 'heapinfo/libc'
70
- require 'heapinfo/chunk'
71
- require 'heapinfo/chunks'
72
- require 'heapinfo/arena'
73
- require 'heapinfo/dumper'
74
- require 'heapinfo/ext/string.rb'
@@ -1,3 +1,6 @@
1
+ require 'heapinfo/chunk'
2
+ require 'heapinfo/helper'
3
+
1
4
  module HeapInfo
2
5
  # Records status of an arena, including bin(s) and top chunk.
3
6
  class Arena
@@ -18,8 +21,8 @@ module HeapInfo
18
21
  # Instantiate a {HeapInfo::Arena} object.
19
22
  #
20
23
  # @param [Integer] base Base address of arena.
21
- # @param [Integer] size_t Either 8 or 4
22
- # @param [Proc] dumper For dump more data
24
+ # @param [Integer] size_t Either 8 or 4.
25
+ # @param [Proc] dumper For dumping more data.
23
26
  def initialize(base, size_t, dumper)
24
27
  @base = base
25
28
  @size_t = size_t
@@ -35,22 +38,18 @@ module HeapInfo
35
38
  top_ptr = Helper.unpack(size_t, @dumper.call(top_ptr_offset, size_t))
36
39
  @fastbin = []
37
40
  return self if top_ptr.zero? # arena not init yet
38
- @top_chunk = Chunk.new size_t, top_ptr, @dumper
39
- @last_remainder = Chunk.new size_t, top_ptr_offset + 8, @dumper
41
+ @top_chunk = Chunk.new(size_t, top_ptr, @dumper)
42
+ @last_remainder = Chunk.new(size_t, top_ptr_offset + 8, @dumper)
40
43
  # this offset diff after 2.23
41
44
  @system_mem = Array.new(2) do |off|
42
45
  Helper.unpack(size_t, @dumper.call(top_ptr_offset + 258 * size_t + 16 + off * size_t, size_t))
43
46
  end.find { |val| val >= 0x21000 && (val & 0xfff).zero? }
44
47
  @fastbin = Array.new(7) do |idx|
45
- f = Fastbin.new(size_t, @base + 8 - size_t * 2 + size_t * idx, @dumper, head: true)
46
- f.index = idx
47
- f
48
+ Fastbin.new(size_t, @base + 8 - size_t * 2 + size_t * idx, @dumper, head: true).tap { |f| f.index = idx }
48
49
  end
49
50
  @unsorted_bin = UnsortedBin.new(size_t, top_ptr_offset, @dumper, head: true)
50
51
  @smallbin = Array.new(62) do |idx|
51
- s = Smallbin.new(size_t, @base + 8 + size_t * (12 + 2 * idx), @dumper, head: true)
52
- s.index = idx
53
- s
52
+ Smallbin.new(size_t, @base + 8 + size_t * (12 + 2 * idx), @dumper, head: true).tap { |s| s.index = idx }
54
53
  end
55
54
  self
56
55
  end
@@ -86,10 +85,10 @@ module HeapInfo
86
85
 
87
86
  # Instantiate a {HeapInfo::Fastbin} object.
88
87
  #
89
- # @param [Mixed] args See {HeapInfo::Chunk} for more information.
90
- def initialize(*args)
88
+ # @see HeapInfo::Chunk
89
+ def initialize(_size_t, base, *)
91
90
  super
92
- @fd = Helper.unpack(size_t, @data[0, @size_t])
91
+ @fd = fd_of(base)
93
92
  end
94
93
 
95
94
  # Mapping index of fastbin to chunk size.
@@ -110,7 +109,7 @@ module HeapInfo
110
109
  # @return [String] fastbin layouts wrapper with color codes.
111
110
  def inspect
112
111
  title + list.map do |ptr|
113
- next "(#{ptr})\n" if ptr.is_a? Symbol
112
+ next "(#{ptr})\n" if ptr.is_a?(Symbol)
114
113
  next " => (nil)\n" if ptr.nil?
115
114
  format(' => %s', Helper.color(format('%#x', ptr)))
116
115
  end.join
@@ -135,6 +134,14 @@ module HeapInfo
135
134
  ret << nil
136
135
  end
137
136
 
137
+ private
138
+
139
+ def addr_of(ptr, offset)
140
+ t = dump(ptr + size_t * offset, size_t)
141
+ return nil if t.nil?
142
+ Helper.unpack(size_t, t)
143
+ end
144
+
138
145
  # @param [Integer] ptr Get the +fd+ value of chunk at +ptr+.
139
146
  # @return [Integer] The +fd+.
140
147
  def fd_of(ptr)
@@ -146,14 +153,6 @@ module HeapInfo
146
153
  def bk_of(ptr)
147
154
  addr_of(ptr, 3)
148
155
  end
149
-
150
- private
151
-
152
- def addr_of(ptr, offset)
153
- t = dump(ptr + size_t * offset, size_t)
154
- return nil if t.nil?
155
- Helper.unpack(size_t, t)
156
- end
157
156
  end
158
157
 
159
158
  # Class for record unsorted bin type chunk.
@@ -178,7 +177,7 @@ module HeapInfo
178
177
  title + pretty_list(list) + "\n"
179
178
  end
180
179
 
181
- # Wrapper the double-linked list with color codes.
180
+ # Wrapper the doubly linked list with color codes.
182
181
  # @param [Array<Integer>] list The list from {#link_list}.
183
182
  # @return [String] Wrapper with color codes.
184
183
  def pretty_list(list)
@@ -190,9 +189,9 @@ module HeapInfo
190
189
  next "#{color_c}(invalid)" if fwd.nil? # invalid c
191
190
  bck = bk_of(c)
192
191
  if center.nil? # bk side
193
- Helper.color(format('%s%s', color_c, fwd == list[idx + 1] ? nil : format('(%#x)', fwd)))
192
+ format('%s%s', color_c, fwd == list[idx + 1] ? nil : Helper.color(format('(%#x)', fwd)))
194
193
  else # fd side
195
- Helper.color(format('%s%s', bck == list[idx - 1] ? nil : format('(%#x)', bck), color_c))
194
+ format('%s%s', bck == list[idx - 1] ? nil : Helper.color(format('(%#x)', bck)), color_c)
196
195
  end
197
196
  end.join(' === ')
198
197
  end
@@ -210,16 +209,15 @@ module HeapInfo
210
209
  sz = 0
211
210
  dup = {}
212
211
  while ptr != @base && sz < expand_size
213
- append.call ptr
214
- break if ptr.nil? # invalid pointer
215
- break if dup[ptr] # looped
212
+ append.call(ptr)
213
+ break if ptr.nil? || dup[ptr] # invalid or duplicated pointer
216
214
  dup[ptr] = true
217
- ptr = send(nxt, ptr)
215
+ ptr = __send__(nxt, ptr)
218
216
  sz += 1
219
217
  end
220
218
  end
221
219
  work.call(@fd, :fd_of, ->(ptr) { list << ptr })
222
- work.call(@bk, :bk_of, ->(ptr) { list.unshift ptr })
220
+ work.call(@bk, :bk_of, ->(ptr) { list.unshift(ptr) })
223
221
  list
224
222
  end
225
223
  end
@@ -1,4 +1,6 @@
1
1
  require 'digest'
2
+ require 'fileutils'
3
+
2
4
  module HeapInfo
3
5
  # Self implment file-base cache manager.
4
6
  #
@@ -10,12 +12,12 @@ module HeapInfo
10
12
 
11
13
  # Define class methods.
12
14
  module ClassMethods
13
- # Get the key for store libc offsets.
15
+ # Get the key for storing libc info.
14
16
  #
15
17
  # @param [String] libc_path The realpath to libc file.
16
- # @return [String] The key for cache read/write.
17
- def key_libc_offset(libc_path)
18
- File.join('libc', Digest::MD5.hexdigest(IO.binread(libc_path)), 'offset')
18
+ # @return [String] The key for cache to read/write.
19
+ def key_libc_info(libc_path)
20
+ File.join('libc', Digest::MD5.hexdigest(IO.binread(libc_path)), 'info')
19
21
  end
20
22
 
21
23
  # Write cache to file.
@@ -55,7 +57,7 @@ module HeapInfo
55
57
  FileUtils.mkdir_p(CACHE_DIR)
56
58
  rescue Errno::EACCES
57
59
  # To prevent ~/ is not writable.
58
- send(:remove_const, :CACHE_DIR)
60
+ __send__(:remove_const, :CACHE_DIR)
59
61
  const_set(:CACHE_DIR, File.join(HeapInfo::TMP_DIR, '.cache/heapinfo'))
60
62
  FileUtils.mkdir_p(CACHE_DIR)
61
63
  end
@@ -1,3 +1,5 @@
1
+ require 'heapinfo/helper'
2
+
1
3
  module HeapInfo
2
4
  # The object of a heap chunk
3
5
  class Chunk
@@ -23,7 +25,7 @@ module HeapInfo
23
25
  # # create a chunk with chunk size 0x21
24
26
  def initialize(size_t, base, dumper, head: false)
25
27
  raise ArgumentError, 'size_t can be either 4 or 8' unless [4, 8].include?(size_t)
26
- self.class.send(:define_method, :dump) { |*args| dumper.call(*args) }
28
+ self.class.__send__(:define_method, :dump) { |*args| dumper.call(*args) }
27
29
  @size_t = size_t
28
30
  @base = base
29
31
  sz = dump(@base, size_t * 2)
@@ -1,4 +1,11 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'heapinfo/helper'
4
+ require 'heapinfo/nil'
5
+
1
6
  module HeapInfo
7
+ # @api private
8
+ #
2
9
  # Class for memory dump relation works
3
10
  class Dumper
4
11
  # Default dump length
@@ -67,8 +74,22 @@ module HeapInfo
67
74
  puts str
68
75
  end
69
76
 
70
- # @api private
77
+ # Dump data from +address+ until reach null-byte.
71
78
  #
79
+ # @return [String]
80
+ def cstring(address)
81
+ base = base_of(address)
82
+ len = 1
83
+ cur = ''
84
+ loop do
85
+ cur << (dump(base + len - 1, len) || '')
86
+ break if cur.index("\x00")
87
+ len <<= 1
88
+ return cur if cur.size != len - 1 # reached undumpable memory
89
+ end
90
+ cur[0, cur.index("\x00")]
91
+ end
92
+
72
93
  # Search a specific value/string/regexp in memory.
73
94
  # +#find+ only return the first matched address.
74
95
  # @param [Integer, String, Regexp] pattern
@@ -98,6 +119,20 @@ module HeapInfo
98
119
  ret
99
120
  end
100
121
 
122
+ # @return [Array<Integer>]
123
+ def scan(pattern, from, length)
124
+ from = base_of(from)
125
+ cur = from
126
+ result = []
127
+ loop do
128
+ addr = find(pattern, cur, length, false)
129
+ break if addr.nil?
130
+ result << addr - from
131
+ cur = addr + @match_length
132
+ end
133
+ result
134
+ end
135
+
101
136
  private
102
137
 
103
138
  def need_permission
@@ -151,15 +186,20 @@ module HeapInfo
151
186
  end
152
187
 
153
188
  def find_integer(value, from, length)
189
+ @match_length = size_t
154
190
  find_string([value].pack(size_t == 4 ? 'L*' : 'Q*'), from, length)
155
191
  end
156
192
 
157
193
  def find_string(string, from, length)
194
+ @match_length = string.size
158
195
  batch_dumper(from, length) { |str| str.index(string) }
159
196
  end
160
197
 
161
198
  def find_regexp(pattern, from, length)
162
- batch_dumper(from, length) { |str| str =~ pattern }
199
+ batch_dumper(from, length) do |str|
200
+ str.match(pattern).tap { |m| @match_length = m[0].size if m }
201
+ str =~ pattern
202
+ end
163
203
  end
164
204
 
165
205
  def batch_dumper(from, remain_size)
@@ -1,3 +1,6 @@
1
+ require 'heapinfo/chunk'
2
+ require 'heapinfo/chunks'
3
+
1
4
  module HeapInfo
2
5
  # Define extensions of naive objects.
3
6
  module Ext
@@ -36,4 +39,4 @@ module HeapInfo
36
39
  end
37
40
  end
38
41
 
39
- ::String.send(:include, HeapInfo::Ext::String::InstanceMethods)
42
+ ::String.__send__(:include, HeapInfo::Ext::String::InstanceMethods)
@@ -1,6 +1,7 @@
1
1
  require 'dentaku'
2
2
  require 'shellwords'
3
3
  require 'time'
4
+ require 'tmpdir'
4
5
 
5
6
  module HeapInfo
6
7
  # Some helper functions.
@@ -151,9 +152,6 @@ module HeapInfo
151
152
  def evaluate(formula, store: {})
152
153
  calc = Dentaku::Calculator.new
153
154
  formula = formula.delete(':')
154
- formula.gsub!(/0x[\da-z]+/) do |s|
155
- s.to_i(16).to_s
156
- end
157
155
  calc.store(store).evaluate(formula)
158
156
  end
159
157
 
@@ -1,3 +1,13 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+
4
+ require 'heapinfo/arena'
5
+ require 'heapinfo/cache'
6
+ require 'heapinfo/glibc/glibc'
7
+ require 'heapinfo/helper'
8
+ require 'heapinfo/segment'
9
+ require 'heapinfo/tcache'
10
+
1
11
  module HeapInfo
2
12
  # Record libc's base, name, and offsets.
3
13
  class Libc < Segment
@@ -7,15 +17,12 @@ module HeapInfo
7
17
  # @param [Mixed] args See {HeapInfo::Segment#initialize} for more information.
8
18
  def initialize(*args)
9
19
  super
10
- @offset = {}
11
20
  end
12
21
 
13
22
  # Get the offset of +main_arena+ in libc.
14
23
  # @return [Integer]
15
24
  def main_arena_offset
16
- return @offset[:main_arena] if @offset[:main_arena]
17
- return nil unless exhaust_search :main_arena
18
- @offset[:main_arena]
25
+ info['main_arena_offset']
19
26
  end
20
27
 
21
28
  # Get the +main_arena+ of libc.
@@ -27,50 +34,69 @@ module HeapInfo
27
34
  @main_arena = Arena.new(off + base, size_t, dumper)
28
35
  end
29
36
 
37
+ # Does this glibc support tcache?
38
+ #
39
+ # @return [Boolean]
40
+ # +true+ or +false+.
41
+ def tcache?
42
+ info['tcache_enable']
43
+ end
44
+
45
+ # The tcache object.
46
+ #
47
+ # @return [HeapInfo::Tcache?]
48
+ # Returns +nil+ if this libc doesn't support tcache.
49
+ def tcache
50
+ return unless tcache?
51
+ @tcache ||= Tcache.new(tcache_base, size_t, dumper)
52
+ end
53
+
30
54
  # @param [Array] maps See {HeapInfo::Segment#find} for more information.
31
55
  # @param [String] name See {HeapInfo::Segment#find} for more information.
32
- # @param [Integer] bits Either 64 or 32.
33
- # @param [String] ld_name The loader's realpath, will be used for running subprocesses.
34
- # @param [Proc] dumper The memory dumper for fetch more information.
56
+ #
57
+ # @option options [Integer] bits Either 64 or 32.
58
+ # @option options [String] ld_name The loader's realpath, will be used for running subprocesses.
59
+ # @option options [Proc] dumper The memory dumper for fetch more information.
60
+ # @option options [Proc] method_heap Method for getting heap segment.
61
+ #
35
62
  # @return [HeapInfo::Libc] libc segment found in maps.
36
- def self.find(maps, name, bits, ld_name, dumper)
37
- obj = super(maps, name)
38
- obj.size_t = bits / 8
39
- obj.send(:ld_name=, ld_name)
40
- obj.send(:dumper=, dumper)
41
- obj
63
+ def self.find(maps, name, **options)
64
+ super(maps, name).tap do |obj|
65
+ obj.size_t = options[:bits] / 8
66
+ %i[ld_name dumper method_heap].each do |sym|
67
+ obj.__send__("#{sym}=", options[sym])
68
+ end
69
+ end
42
70
  end
43
71
 
44
72
  private
45
73
 
46
- attr_accessor :ld_name
47
- # only for searching offset of main_arena now
48
- def exhaust_search(symbol)
49
- return false if symbol != :main_arena
50
- read_main_arena_offset
51
- true
74
+ attr_accessor :ld_name, :method_heap
75
+ # Get libc's info.
76
+ def info
77
+ return @info if @info
78
+ # Try to fetch from cache first.
79
+ key = HeapInfo::Cache.key_libc_info(name)
80
+ @info = HeapInfo::Cache.read(key)
81
+ @info ||= execute_libc_info.tap { |i| HeapInfo::Cache.write(key, i) }
52
82
  end
53
83
 
54
- def read_main_arena_offset
55
- key = HeapInfo::Cache.key_libc_offset(name)
56
- @offset = HeapInfo::Cache.read(key) || {}
57
- return @offset[:main_arena] if @offset.key?(:main_arena)
58
- @offset[:main_arena] = resolve_main_arena_offset
59
- HeapInfo::Cache.write(key, @offset)
60
- end
61
-
62
- def resolve_main_arena_offset
84
+ def execute_libc_info
63
85
  dir = HeapInfo::Helper.tempfile('')
64
86
  FileUtils.mkdir(dir)
65
- tmp_elf = File.join(dir, 'get_arena')
87
+ tmp_elf = File.join(dir, 'libc_info')
66
88
  libc_file = File.join(dir, 'libc.so.6')
67
89
  ld_file = File.join(dir, 'ld.so')
68
90
  flags = "-w #{size_t == 4 ? '-m32' : ''}"
69
- `cp #{name} #{libc_file} && \
91
+ JSON.parse(`cp #{name} #{libc_file} && \
70
92
  cp #{ld_name} #{ld_file} && \
71
- gcc #{flags} #{File.expand_path('../tools/get_arena.c', __FILE__)} -o #{tmp_elf} 2>&1 > /dev/null && \
93
+ gcc #{flags} #{File.expand_path('tools/libc_info.c', __dir__)} -o #{tmp_elf} 2>&1 > /dev/null && \
72
94
  #{ld_file} --library-path #{dir} #{tmp_elf} && \
73
- rm -fr #{dir}`.to_i(16)
95
+ rm -fr #{dir}`)
96
+ end
97
+
98
+ def tcache_base
99
+ method_heap.call.base + 2 * size_t
74
100
  end
75
101
  end
76
102
  end
@@ -5,7 +5,7 @@ module HeapInfo
5
5
  # to prevent use the return value for calculating accidentally while exploiting remote.
6
6
  class Nil
7
7
  %i[nil? inspect to_s].each do |method_sym|
8
- define_method(method_sym) { |*args, &block| nil.send(method_sym, *args, &block) }
8
+ define_method(method_sym) { |*args, &block| nil.__send__(method_sym, *args, &block) }
9
9
  end
10
10
 
11
11
  # Hook all missing methods
@@ -15,7 +15,7 @@ module HeapInfo
15
15
  # p h.dump(:heap)[8, 8].unpack('Q*')
16
16
  # #=> nil
17
17
  def method_missing(method_sym, *args, &block)
18
- return nil.send(method_sym, *args, &block) if nil.respond_to?(method_sym)
18
+ return nil.__send__(method_sym, *args, &block) if nil.respond_to?(method_sym)
19
19
  self || super
20
20
  end
21
21
 
@@ -1,5 +1,10 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
+ require 'heapinfo/dumper'
4
+ require 'heapinfo/helper'
5
+ require 'heapinfo/nil'
6
+ require 'heapinfo/process_info'
7
+
3
8
  module HeapInfo
4
9
  # Main class of heapinfo.
5
10
  class Process
@@ -110,15 +115,14 @@ module HeapInfo
110
115
  # #=> 0x9637a0 after :heap
111
116
  def offset(addr, sym = nil)
112
117
  return unless load?
113
- segment = @info.send(sym) if HeapInfo::ProcessInfo::EXPORT.include?(sym)
114
- segment = nil unless segment.is_a?(HeapInfo::Segment)
118
+ segment = @info.to_segment(sym)
115
119
  if segment.nil?
116
120
  sym, segment = @info.segments
117
121
  .select { |_, seg| seg.base <= addr }
118
122
  .min_by { |_, seg| addr - seg }
119
123
  end
120
- return puts "Invalid address #{Helper.hex(addr)}" if segment.nil?
121
- puts Helper.color(Helper.hex(addr - segment)) + ' after ' + Helper.color(sym, sev: :sym)
124
+ return $stdout.puts "Invalid address #{Helper.hex(addr)}" if segment.nil?
125
+ $stdout.puts Helper.color(Helper.hex(addr - segment)) + ' after ' + Helper.color(sym, sev: :sym)
122
126
  end
123
127
  alias off offset
124
128
 
@@ -147,6 +151,19 @@ module HeapInfo
147
151
  dumper.x(count, address)
148
152
  end
149
153
 
154
+ # Gdb-like command
155
+ #
156
+ # Dump a string until reach the null-byte.
157
+ # @param [String, Symbol, Integer] address The base address to be dumped.
158
+ # See {#dump}.
159
+ #
160
+ # @return [String]
161
+ # The string *without* null-byte.
162
+ def s(address)
163
+ return Nil.new unless load?
164
+ dumper.cstring(address)
165
+ end
166
+
150
167
  # Gdb-like command.
151
168
  #
152
169
  # Search a specific value/string/regexp in memory.
@@ -176,23 +193,51 @@ module HeapInfo
176
193
  return Nil.new unless load?
177
194
  dumper.find(pattern, from, length, rel)
178
195
  end
179
-
180
- # +search+ is more intutive to me
181
196
  alias search find
182
197
 
183
- # Pretty dump of bins layouts.
198
+ # Find pattern in all segments with pretty output.
199
+ #
200
+ # @param [Integer, String, Regexp] pattern
201
+ # The desired search pattern, can be value(+Integer+), string, or regular expression.
202
+ # @param [Symbol, Array<Symbol>] segment
203
+ # Only find pattern in these symbols.
204
+ #
205
+ # @return [void]
206
+ def find_all(pattern, segment = :all)
207
+ return Nil.new unless load?
208
+ segments = segment == :all ? %i[elf heap libc ld stack] : Array(segment)
209
+ result = findall_raw(pattern, segments).reject { |(_, _, ary)| ary.empty? }
210
+ target = pattern.is_a?(Integer) ? Helper.hex(pattern) : pattern.inspect
211
+ str = ["Searching #{Helper.color(target)}:\n"]
212
+ str.concat(result.map do |(sym, base, ary)|
213
+ "In #{Helper.color(sym, sev: :bin)} (#{Helper.color(Helper.hex(base))}):\n" +
214
+ ary.map { |v| " #{Helper.color(sym, sev: :bin)}+#{Helper.color(Helper.hex(v))}\n" }.join
215
+ end)
216
+ $stdout.puts str
217
+ end
218
+ alias findall find_all
219
+
220
+ # Pretty dump of bins' layouts.
184
221
  #
185
222
  # The request layouts will output to +stdout+.
186
223
  # @param [Array<Symbol>] args Bin type(s) you want to see.
187
224
  # @return [void]
188
225
  # @example
189
226
  # h.layouts(:fast, :unsorted, :small)
227
+ # # ...
228
+ # h.layouts(:tcache)
229
+ # # ...
230
+ # h.layouts(:all) # show all bin(s), includes tcache
190
231
  def layouts(*args)
191
232
  return unless load?
192
- puts libc.main_arena.layouts(*args)
233
+ str = ''
234
+ str << libc.tcache.layouts if libc.tcache? && (%w[all tcache] & args.map(&:to_s)).any?
235
+ str << libc.main_arena.layouts(*args)
236
+ $stdout.puts str
193
237
  end
194
238
 
195
239
  # Show simple information of target process.
240
+ #
196
241
  # Contains program names, pid, and segments' info.
197
242
  #
198
243
  # @return [String]
@@ -257,7 +302,7 @@ module HeapInfo
257
302
 
258
303
  def clear_process
259
304
  ProcessInfo::EXPORT.each do |m|
260
- self.class.send(:define_method, m) { Nil.new }
305
+ self.class.__send__(:define_method, m) { Nil.new }
261
306
  end
262
307
  false
263
308
  end
@@ -265,13 +310,21 @@ module HeapInfo
265
310
  def load_info! # :nodoc:
266
311
  @info = ProcessInfo.new(self)
267
312
  ProcessInfo::EXPORT.each do |m|
268
- self.class.send(:define_method, m) { @info.send(m) }
313
+ self.class.__send__(:define_method, m) { @info.__send__(m) }
269
314
  end
270
315
  @dumper = Dumper.new(mem_filename) do |sym|
271
- @info.send(sym) if @info.respond_to?(sym)
316
+ @info.__send__(sym) if @info.respond_to?(sym)
272
317
  end
273
318
  end
274
319
 
320
+ def findall_raw(pattern, segments)
321
+ segments.map do |sym|
322
+ seg = @info.to_segment(sym)
323
+ next unless seg
324
+ [sym, seg.base, dumper.scan(pattern, sym, :unlimited)]
325
+ end.compact
326
+ end
327
+
275
328
  def mem_filename
276
329
  "/proc/#{pid}/mem"
277
330
  end
@@ -1,5 +1,11 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
+ require 'stringio'
4
+
5
+ require 'heapinfo/segment'
6
+ require 'heapinfo/libc'
7
+ require 'heapinfo/helper'
8
+
3
9
  module HeapInfo
4
10
  # For {Process} to record basic process information.
5
11
  #
@@ -42,7 +48,13 @@ module HeapInfo
42
48
  @auxv = parse_auxv(Helper.auxv_of(@pid))
43
49
  ld_seg = maps.find { |m| m[0] == @auxv[:ld_base] } # nil if static-linked elf
44
50
  @ld = ld_seg.nil? ? Nil.new : Segment.new(@auxv[:ld_base], ld_seg.last)
45
- @libc = Libc.find(maps, match_maps(maps, options[:libc]), @bits, @ld.name, ->(*args) { process.dump(*args) })
51
+ @libc = Libc.find(
52
+ maps, match_maps(maps, options[:libc]),
53
+ bits: @bits,
54
+ ld_name: @ld.name,
55
+ dumper: ->(*args) { process.dump(*args) },
56
+ method_heap: method(:heap)
57
+ )
46
58
  end
47
59
 
48
60
  # Heap will not be mmapped if the process not use heap yet, so create a lazy loading method.
@@ -59,11 +71,22 @@ module HeapInfo
59
71
  # @return [Hash{Symbol => Segment}] The segments in hash format.
60
72
  def segments
61
73
  EXPORT.map do |sym|
62
- seg = send(sym)
74
+ seg = __send__(sym)
63
75
  [sym, seg] if seg.is_a?(Segment)
64
76
  end.compact.to_h
65
77
  end
66
78
 
79
+ # Convert symbol to segment.
80
+ #
81
+ # @return [HeapInfo::Segment?]
82
+ # The segment object.
83
+ def to_segment(sym)
84
+ return nil unless EXPORT.include?(sym)
85
+ seg = __send__(sym)
86
+ return nil unless seg.is_a?(Segment)
87
+ seg
88
+ end
89
+
67
90
  private
68
91
 
69
92
  def load_maps
@@ -1,3 +1,6 @@
1
+ require 'heapinfo/helper'
2
+ require 'heapinfo/nil'
3
+
1
4
  module HeapInfo
2
5
  # Record the base address and name in maps
3
6
  class Segment
@@ -0,0 +1,55 @@
1
+ require 'heapinfo/arena'
2
+
3
+ module HeapInfo
4
+ # Fetch tcache structure and show its content.
5
+ class Tcache
6
+ # #define TCACHE_MAX_BINS 64
7
+ MAX_BINS = 64
8
+ # Instantiate a {HeapInfo::Tcache} object.
9
+ #
10
+ # @param [Integer] base Base address of +tcache+.
11
+ # @param [Integer] size_t Either 8 or 4.
12
+ # @param [Proc] dumper For dumping more data.
13
+ def initialize(base, size_t, dumper)
14
+ @base = base
15
+ @size_t = size_t
16
+ @dumper = dumper
17
+ end
18
+
19
+ # Pretty dump of tcache entries.
20
+ #
21
+ # @return [String] Tcache entries that wrapper with color codes.
22
+ def layouts
23
+ entries.map(&:inspect).join
24
+ end
25
+
26
+ private
27
+
28
+ def entries
29
+ Array.new(MAX_BINS) do |idx|
30
+ TcacheEntry.new(@size_t, @base + 64 + @size_t * idx, @dumper, head: true).tap { |f| f.index = idx }
31
+ end
32
+ end
33
+ end
34
+
35
+ # A tcache entry.
36
+ #
37
+ # Though this class inherited from {Chunk} ({Fastbin}), tcache entries are *not* chunks.
38
+ class TcacheEntry < Fastbin
39
+ # For pretty inspect.
40
+ #
41
+ # @return [String]
42
+ # Empty string is returned if this entry contains nothing.
43
+ def inspect
44
+ return '' if fd_of(@base).zero? # empty
45
+ super
46
+ end
47
+
48
+ private
49
+
50
+ # Hack +fd_of+ method here then everything works.
51
+ def fd_of(ptr)
52
+ addr_of(ptr, 0)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ /*
2
+ @author: david942j
3
+ Getting libc's information, such as:
4
+ - main_arena offset
5
+ - is tcache enabled?
6
+ Sample Usage
7
+ > ./libc_info
8
+ > LD_LIBRARY_PATH=. ./libc_info
9
+ > ./ld-linux.so.2 --library-path . ./libc_info
10
+ */
11
+ #include <stddef.h>
12
+ #include <stdio.h>
13
+ #include <stdlib.h>
14
+ #include <string.h>
15
+
16
+ #define SZ sizeof(size_t)
17
+ #define PAGE_SIZE 0x1000
18
+
19
+ void *search_head(size_t e) {
20
+ e = (e >> 12) << 12;
21
+ while(strncmp((void*)e, "\177ELF", 4)) e -= PAGE_SIZE;
22
+ return (void*) e;
23
+ }
24
+
25
+ void* main_arena_offset() {
26
+ void **p = (void**)malloc(SZ * 128 * 2); // a large chunk
27
+ void *z = malloc(SZ); // prevent p merge with top chunk
28
+ *p = z; // prevent compiler optimize
29
+ free(p); // now *p must be the pointer of the (chunk_ptr) unsorted bin
30
+ z = (void*)((*p) - (4 + 4 + SZ * 10)); // mutex+flags+fastbin[]
31
+ void* a = search_head((size_t)__builtin_return_address(1));
32
+ return (void*)(z - a);
33
+ }
34
+
35
+ int tcache_enable() {
36
+ void **p = malloc(SZ * 32); // smallbin size
37
+ *p = (void*) 0xdeadbeefu;
38
+ // if tcache is enabled, this free will put p into tcache_entry;
39
+ // otherwise, either merge with top_chunk or put into unsorted_bin
40
+ free(p);
41
+ if(*p == 0) return 1; // tcache_entry, fd set as zero
42
+ return 0;
43
+ }
44
+
45
+ int main(int argc, char **argv) {
46
+ printf("{" \
47
+ "\"main_arena_offset\": %u," \
48
+ "\"tcache_enable\": %s" \
49
+ "}\n", main_arena_offset(), tcache_enable() ? "true" : "false");
50
+ return 0;
51
+ }
@@ -1,4 +1,4 @@
1
1
  module HeapInfo
2
2
  # Current gem version.
3
- VERSION = '1.1.0'.freeze
3
+ VERSION = '1.2.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heapinfo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-08 00:00:00.000000000 Z
11
+ date: 2018-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dentaku
@@ -16,84 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2'
19
+ version: '3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2'
26
+ version: '3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: codeclimate-test-reporter
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.6'
33
+ version: '12.3'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.6'
40
+ version: '12.3'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '12.3'
47
+ version: '3.7'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '12.3'
54
+ version: '3.7'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec
56
+ name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.7'
61
+ version: '0.54'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.7'
68
+ version: '0.54'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rubocop
70
+ name: simplecov
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.52'
75
+ version: 0.16.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.52'
82
+ version: 0.16.0
83
83
  - !ruby/object:Gem::Dependency
84
- name: simplecov
84
+ name: yard
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.13.0
89
+ version: '0.9'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.13.0
96
+ version: '0.9'
97
97
  description: |
98
98
  Create an interactive memory info interface while pwn / exploiting.
99
99
  Useful for rubiers writing exploit scripts.
@@ -123,7 +123,8 @@ files:
123
123
  - lib/heapinfo/process.rb
124
124
  - lib/heapinfo/process_info.rb
125
125
  - lib/heapinfo/segment.rb
126
- - lib/heapinfo/tools/get_arena.c
126
+ - lib/heapinfo/tcache.rb
127
+ - lib/heapinfo/tools/libc_info.c
127
128
  - lib/heapinfo/version.rb
128
129
  homepage: https://github.com/david942j/heapinfo
129
130
  licenses:
@@ -1,28 +0,0 @@
1
- /*
2
- @author: david942j
3
- use for get libc's main_arena offset
4
- Sample Usage
5
- > ./get_arena
6
- > LD_LIBRARY_PATH=. ./get_arena
7
- > ./ld-linux.so.2 --library-path . ./get_arena
8
- */
9
- #include <stdlib.h>
10
- #include <stdio.h>
11
- #include <stddef.h>
12
- #define SZ sizeof(size_t)
13
- #define PAGE_SIZE 0x1000
14
- void *search_head(size_t e) {
15
- e = (e >> 12) << 12;
16
- while(strncmp((void*)e, "\177ELF", 4)) e -= PAGE_SIZE;
17
- return (void*) e;
18
- }
19
- int main() {
20
- void **p = (void**)malloc(SZ*16); // small bin with chunk size SZ*18
21
- void *z = malloc(SZ); // prevent p merge with top chunk
22
- *p = z; // prevent compiler optimize
23
- free(p); // now *p must be the pointer of the (chunk_ptr) unsorted bin
24
- z = (void*)((*p) - (4 + 4 + SZ * 10 )); // mutex+flags+fastbin[]
25
- void* a = search_head((size_t)__builtin_return_address(0));
26
- printf("%p\n", z-a);
27
- return 0;
28
- }