heapinfo 0.0.0 → 0.0.1

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: 1ba7cc3de8a173a616d298c30836fa0bb5a2800a
4
- data.tar.gz: 792feedd7e5a6deea7b956082150cb7b4609d887
3
+ metadata.gz: 546784a7f3d841d2505b517a355afc2e1e914e5d
4
+ data.tar.gz: 166efbf709763a0a5a5a68d3e0f13515839bc125
5
5
  SHA512:
6
- metadata.gz: d97610801e28487dd52e3e100558e746e8d23784a7925be2f99e4e35dd101c9976cbb526e1f18c18105b4a6bef2b00a305139f5406bd02021cedb75df6394749
7
- data.tar.gz: 434774059d8ff5f5fcebc1fb81788a8fd9e78856f295c5bbfd3a045ae1b8b28cfa5237d4bd5cd330f71b319e1333efb65cfaffc557263721836a90f48687480f
6
+ metadata.gz: fb5882bc6a55e02abcfc7f11e646952ee50c1622cb8020103c6f1753163b49984d2be00e320eb0d61d601d3d30eb1a90d5dbd2b8053c83d80c9324159dc80f1e
7
+ data.tar.gz: 8789e7be0d5336870fc75100df74c5ea9151c0d4a9f6f7f9553756623915a4eef70b802f4a8511443f8a90322f50dfbfc0b60c697ebaab9400d1491705142901
data/README.md CHANGED
@@ -121,6 +121,8 @@ Provide a searcher of memory, easier to use than in (naive) gdb.
121
121
  Support search integer, string, and even regular expression.
122
122
 
123
123
  ```ruby
124
+ h.find(0xdeadbeef, 'heap+0x10', 0x1000)
125
+ # => 6299664 # 0x602010
124
126
  h.find(/E.F/, 0x400000, 4)
125
127
  # => 4194305 # 0x400001
126
128
  h.find(/E.F/, 0x400000, 3)
data/lib/heapinfo.rb CHANGED
@@ -12,13 +12,7 @@ module HeapInfo
12
12
  # Directory for writing some tmp files when working,
13
13
  # make sure /tmp is writable
14
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)
15
+ FileUtils.mkdir_p TMP_DIR
22
16
 
23
17
  # Entry point for using HeapInfo.
24
18
  # Show segments info of the process after loaded
@@ -65,6 +59,8 @@ end
65
59
 
66
60
  require 'heapinfo/helper'
67
61
  require 'heapinfo/nil'
62
+ require 'heapinfo/cache'
63
+ require 'heapinfo/process_info'
68
64
  require 'heapinfo/process'
69
65
  require 'heapinfo/segment'
70
66
  require 'heapinfo/libc'
@@ -11,13 +11,13 @@ module HeapInfo
11
11
  def initialize(base, bits, dumper)
12
12
  @base, @dumper = base, dumper
13
13
  @size_t = bits / 8
14
- reload
14
+ reload!
15
15
  end
16
16
 
17
17
  # Refresh all attributes
18
18
  # Retrive data using <tt>@dumper</tt>, load bins, top chunk etc.
19
19
  # @return [HeapInfo::Arena] self
20
- def reload
20
+ def reload!
21
21
  top_ptr = Helper.unpack(size_t, @dumper.call(@base + 8 + size_t * 10, size_t))
22
22
  @fastbin = []
23
23
  return self if top_ptr == 0 # arena not init yet
@@ -172,8 +172,8 @@ module HeapInfo
172
172
  sz += 1
173
173
  end
174
174
  end
175
- work.call(@fd, :fd_of, lambda{|ptr| list << ptr})
176
- work.call(@bk, :bk_of, lambda{|ptr| list.unshift ptr})
175
+ work.call(@fd, :fd_of, ->(ptr) { list << ptr })
176
+ work.call(@bk, :bk_of, ->(ptr) { list.unshift ptr })
177
177
  list
178
178
  end
179
179
  end
@@ -0,0 +1,67 @@
1
+ require 'digest'
2
+ module HeapInfo
3
+
4
+ # Self implment file-base cache manager.
5
+ #
6
+ # Values are recorded in files based on <tt>Marshal</tt>
7
+ module Cache
8
+ # Directory for caching files.
9
+ # e.g. HeapInfo will record main_arena_offset for glibc(s)
10
+ CACHE_DIR = File.join(ENV['HOME'], '.cache/heapinfo')
11
+
12
+ # Get the key for store libc offsets
13
+ #
14
+ # @param [String] libc_path The realpath to libc file
15
+ # @return [String] The key for cache read/write.
16
+ def self.key_libc_offset(libc_path)
17
+ File.join('libc', Digest::MD5.hexdigest(IO.binread(libc_path)), 'offset')
18
+ end
19
+
20
+ # Write cache to file.
21
+ #
22
+ # @param [String] key In file path format, only accept <tt>[\w\/]</tt> to prevent horrible things.
23
+ # @param [Object] value <tt>value</tt> will be stored with <tt>#Marshal::dump</tt>.
24
+ # @return [Boolean] true
25
+ def self.write(key, value)
26
+ filepath = realpath key
27
+ FileUtils.mkdir_p(File.dirname filepath)
28
+ IO.binwrite(filepath, Marshal::dump(value))
29
+ true
30
+ end
31
+
32
+ # Read cache from file.
33
+ #
34
+ # @param [String] key In file path format, only accept <tt>[\w\/]</tt> to prevent horrible things.
35
+ # @return [Object, NilClass] value that recorded, return <tt>nil</tt> when cache miss.
36
+ def self.read(key)
37
+ filepath = realpath key
38
+ return unless File.file? filepath
39
+ Marshal::load IO.binread filepath
40
+ rescue
41
+ nil # handle if file content invalid
42
+ end
43
+
44
+ # @param [String] key
45
+ # @return [String] Prepend with <tt>CACHE_DIR</tt>
46
+ def self.realpath(key)
47
+ raise ArgumentError.new('Invalid key(file path)') if key =~ /[^\w\/]/
48
+ File.join(CACHE_DIR, key)
49
+ end
50
+
51
+ # @return [Object] Not important.
52
+ def self.load
53
+ FileUtils.mkdir_p(CACHE_DIR)
54
+ rescue
55
+ # To prevent ~/ is not writable.
56
+ self.send :remove_const, :CACHE_DIR
57
+ self.const_set :CACHE_DIR, File.join(HeapInfo::TMP_DIR, '.cache/heapinfo')
58
+ end
59
+
60
+ # Clear the cache directory.
61
+ # @return [Object] Not important.
62
+ def self.clear_all
63
+ FileUtils.rm_rf CACHE_DIR
64
+ end
65
+ self.load
66
+ end
67
+ end
@@ -17,10 +17,10 @@ module HeapInfo
17
17
  # @param [Proc] dumper For dump more information of this chunk
18
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
19
  # @example
20
- # HeapInfo::Chunk.new 8, 0x602000, lambda{|addr, len| [0,0x21, 0xda4a].pack("Q*")[addr-0x602000, len]}
20
+ # HeapInfo::Chunk.new 8, 0x602000, ->(addr, len) { [0,0x21, 0xda4a].pack("Q*")[addr-0x602000, len] }
21
21
  # # create a chunk with chunk size 0x21
22
22
  def initialize(size_t, base, dumper, head: false)
23
- fail unless [4, 8].include? size_t
23
+ raise ArgumentError.new('size_t can be either 4 or 8') unless [4, 8].include? size_t
24
24
  self.class.send(:define_method, :dump){|*args| dumper.call(*args)}
25
25
  @size_t = size_t
26
26
  @base = base
@@ -6,10 +6,10 @@ module HeapInfo
6
6
 
7
7
  # Instantiate a <tt>HeapInfo::Dumper</tt> object
8
8
  #
9
- # @param [Hash] segments With values <tt>HeapInfo::Segment</tt>
9
+ # @param [HeapInfo::ProcessInfo] info process info object.
10
10
  # @param [String] mem_filename The filename that can be access for dump. Should be <tt>/proc/[pid]/mem</tt>.
11
- def initialize(segments, mem_filename)
12
- @segments, @filename = segments, mem_filename
11
+ def initialize(info, mem_filename)
12
+ @info, @filename = info, mem_filename
13
13
  need_permission unless dumpable?
14
14
  end
15
15
 
@@ -21,20 +21,14 @@ module HeapInfo
21
21
  # # => "\x7fELF"
22
22
  def dump(*args)
23
23
  return need_permission unless dumpable?
24
- base, offset, len = Dumper.parse_cmd(args)
25
- if base.instance_of?(Symbol) and @segments[base].instance_of?(Segment)
26
- addr = @segments[base].base
27
- elsif base.is_a? Integer
28
- addr = base
29
- else
30
- fail # invalid usage
31
- end
24
+ base, len = base_len_of(*args)
32
25
  file = mem_f
33
- file.pos = addr + offset
34
- mem = file.read len
26
+ file.pos = base
27
+ mem = file.readpartial len
35
28
  file.close
36
29
  mem
37
- rescue
30
+ rescue => e
31
+ raise e if e.is_a? ArgumentError
38
32
  nil
39
33
  end
40
34
 
@@ -46,7 +40,7 @@ module HeapInfo
46
40
  # @param [Mixed] args Same as arguments of <tt>#dump</tt>
47
41
  def dump_chunks(*args)
48
42
  base = base_of(*args)
49
- dump(*args).to_chunks(bits: @segments[:bits], base: base)
43
+ dump(*args).to_chunks(bits: @info.bits, base: base)
50
44
  end
51
45
 
52
46
  # Show dump results like in gdb's command <tt>x</tt>.
@@ -165,10 +159,18 @@ module HeapInfo
165
159
  File.open(@filename)
166
160
  end
167
161
 
162
+ def base_len_of(*args)
163
+ base, offset, len = Dumper.parse_cmd(args)
164
+ if HeapInfo::ProcessInfo::EXPORT.include?(base) and (segment = @info.send(base)).is_a? Segment
165
+ base = segment.base
166
+ elsif not base.is_a? Integer
167
+ raise ArgumentError.new("Invalid base: #{base}") # invalid usage
168
+ end
169
+ [base + offset, len]
170
+ end
171
+
168
172
  def base_of(*args)
169
- base, offset, _ = Dumper.parse_cmd(args)
170
- base = @segments[base].base if @segments[base].is_a? Segment
171
- base + offset
173
+ base_len_of(*args)[0]
172
174
  end
173
175
 
174
176
  def find_integer(value, from, length)
@@ -198,7 +200,7 @@ module HeapInfo
198
200
  from + idx
199
201
  end
200
202
  def size_t
201
- @segments[:bits] / 8
203
+ @info.bits / 8
202
204
  end
203
205
  end
204
206
  end
@@ -5,7 +5,7 @@ module HeapInfo
5
5
  module InstanceMethods
6
6
  def to_chunk(bits: 64, base: 0)
7
7
  size_t = bits / 8
8
- dumper = lambda{|addr, len| self[addr-base, len]}
8
+ dumper = ->(addr, len) { self[addr-base, len] }
9
9
  Chunk.new(size_t, base, dumper)
10
10
  end
11
11
 
data/lib/heapinfo/libc.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  module HeapInfo
2
2
  class Libc < Segment
3
+ def initialize(*args)
4
+ super
5
+ @offset = {}
6
+ end
7
+
3
8
  def main_arena_offset
4
- return @_main_arena_offset if @_main_arena_offset
9
+ return @offset[:main_arena] if @offset[:main_arena]
5
10
  return nil unless exhaust_search :main_arena
6
- @_main_arena_offset
11
+ @offset[:main_arena]
7
12
  end
8
13
 
9
14
  def main_arena
10
- return @_main_arena.reload if @_main_arena
15
+ return @main_arena.reload! if @main_arena
11
16
  off = main_arena_offset
12
17
  return if off.nil?
13
- @_main_arena = Arena.new(off + self.base, process.bits, lambda{|*args|process.send(:dumper).dump(*args)})
18
+ @main_arena = Arena.new(off + self.base, process.bits, ->(*args) { process.dump(*args) })
14
19
  end
15
20
 
16
21
  def self.find(maps, name, process)
@@ -25,11 +30,18 @@ module HeapInfo
25
30
  # only for searching offset of main_arena now
26
31
  def exhaust_search(symbol)
27
32
  return false if symbol != :main_arena
28
- # TODO: read from cache
29
- @_main_arena_offset = resolve_main_arena_offset
33
+ read_main_arena_offset
30
34
  true
31
35
  end
32
36
 
37
+ def read_main_arena_offset
38
+ key = HeapInfo::Cache::key_libc_offset(self.name)
39
+ @offset = HeapInfo::Cache::read(key) || {}
40
+ return @offset[:main_arena] if @offset.key? :main_arena
41
+ @offset[:main_arena] = resolve_main_arena_offset
42
+ HeapInfo::Cache::write key, @offset
43
+ end
44
+
33
45
  def resolve_main_arena_offset
34
46
  tmp_elf = HeapInfo::TMP_DIR + "/get_arena"
35
47
  libc_file = HeapInfo::TMP_DIR + "/libc.so.6"
@@ -37,8 +49,7 @@ module HeapInfo
37
49
  flags = "-w #{@process.bits == 32 ? '-m32' : ''}"
38
50
  %x(cp #{self.name} #{libc_file} && \
39
51
  cp #{@process.ld.name} #{ld_file} && \
40
- chdir #{File.expand_path('../tools', __FILE__)} && \
41
- gcc #{flags} get_arena.c -o #{tmp_elf} 2>&1 > /dev/null && \
52
+ gcc #{flags} #{File.expand_path('../tools/get_arena.c', __FILE__)} -o #{tmp_elf} 2>&1 > /dev/null && \
42
53
  #{ld_file} --library-path #{HeapInfo::TMP_DIR} #{tmp_elf} && \
43
54
  rm #{tmp_elf} #{libc_file} #{ld_file}).to_i(16)
44
55
  end
@@ -2,7 +2,7 @@
2
2
  module HeapInfo
3
3
  # Main class of heapinfo.
4
4
  class Process
5
- # The dafault options of libaries,
5
+ # The default options of libaries,
6
6
  # use for matching glibc and ld segments in <tt>/proc/[pid]/maps</tt>
7
7
  DEFAULT_LIB = {
8
8
  libc: /libc[^\w]/,
@@ -10,7 +10,6 @@ module HeapInfo
10
10
  }
11
11
  # @return [Fixnum, NilClass] return the pid of process, <tt>nil</tt> if no such process found
12
12
  attr_reader :pid
13
- attr_reader :status
14
13
 
15
14
  # Instantiate a <tt>HeapInfo::Process</tt> object
16
15
  # @param [String, Fixnum] prog Process name or pid, see <tt>HeapInfo::heapinfo</tt> for more information
@@ -20,7 +19,6 @@ module HeapInfo
20
19
  @options = DEFAULT_LIB.merge options
21
20
  load!
22
21
  return unless load?
23
- @dumper = Dumper.new(@status, mem_filename)
24
22
  end
25
23
 
26
24
  # Use this method to wrapper all HeapInfo methods.
@@ -65,7 +63,7 @@ module HeapInfo
65
63
  # dump('heap-1, 64') # not support '-'
66
64
  def dump(*args)
67
65
  return Nil.new unless load?
68
- @dumper.dump(*args)
66
+ dumper.dump(*args)
69
67
  end
70
68
 
71
69
  # Return the dump result as chunks.
@@ -75,7 +73,7 @@ module HeapInfo
75
73
  # @param [Mixed] args Same as arguments of <tt>#dump</tt>
76
74
  def dump_chunks(*args)
77
75
  return Nil.new unless load?
78
- @dumper.dump_chunks(*args)
76
+ dumper.dump_chunks(*args)
79
77
  end
80
78
 
81
79
  # Gdb-like command
@@ -100,24 +98,29 @@ module HeapInfo
100
98
  # # 0x400010: 0x00000001003e0002
101
99
  def x(count, *commands, io: $stdout)
102
100
  return unless load? and io.respond_to? :puts
103
- @dumper.x(count, *commands, io: io)
101
+ dumper.x(count, *commands, io: io)
104
102
  end
105
103
 
106
104
  # Gdb-like command.
107
105
  #
108
106
  # Search a specific value/string/regexp in memory.
109
- # <tt>#find</tt> only return the first matched address, if want to find all adress, use <tt>#find_all</tt> instead.
110
107
  # @param [Integer, String, Regexp] pattern The desired search pattern, can be value(<tt>Integer</tt>), string, or regular expression.
111
108
  # @param [Integer, String, Symbol] from Start address for searching, can be segment(<tt>Symbol</tt>) or segments with offset. See examples for more information.
112
109
  # @param [Integer] length The search length limit, default is unlimited, which will search until pattern found or reach unreadable memory.
113
110
  # @return [Integer, NilClass] The first matched address, <tt>nil</tt> is returned when no such pattern found.
114
111
  # @example
115
- # h.find(0xdeadbeef, :heap)
116
112
  # h.find(0xdeadbeef, 'heap+0x10', 0x1000)
113
+ # # => 6299664 # 0x602010
114
+ # h.find(/E.F/, 0x400000, 4)
115
+ # # => 4194305 # 0x400001
116
+ # h.find(/E.F/, 0x400000, 3)
117
+ # # => nil
118
+ # sh_offset = h.find('/bin/sh', :libc) - h.libc.base
119
+ # # => 1559771 # 0x17ccdb
117
120
  def find(pattern, from, length = :unlimited)
118
121
  return Nil.new unless load?
119
122
  length = 1 << 40 if length.is_a? Symbol
120
- @dumper.find(pattern, from, length)
123
+ dumper.find(pattern, from, length)
121
124
  end
122
125
 
123
126
  # <tt>search</tt> is more intutive to me
@@ -153,15 +156,16 @@ module HeapInfo
153
156
  end
154
157
 
155
158
  private
156
- attr_reader :dumper
159
+ attr_accessor :dumper
157
160
  def load?
158
161
  @pid != nil
159
162
  end
160
163
 
161
- def load! # force load is not efficient
164
+ def load! # try to load
165
+ return if @pid
162
166
  @pid = fetch_pid
163
167
  return false if @pid.nil? # still can't load
164
- load_status @options
168
+ load_info!
165
169
  true
166
170
  end
167
171
 
@@ -175,28 +179,12 @@ module HeapInfo
175
179
  pid
176
180
  end
177
181
 
178
- def load_status(options)
179
- elf = Helper.exe_of pid
180
- maps = Helper.parse_maps Helper.maps_of pid
181
- @status = {
182
- program: Segment.find(maps, File.readlink("/proc/#{pid}/exe")),
183
- libc: Libc.find(maps, match_maps(maps, options[:libc]), self),
184
- heap: Segment.find(maps, '[heap]'),
185
- stack: Segment.find(maps, '[stack]'),
186
- ld: Segment.find(maps, match_maps(maps, options[:ld])),
187
- bits: bits_of(elf),
188
- }
189
- @status[:elf] = @status[:program] #alias
190
- @status.keys.each do |m|
191
- self.class.send(:define_method, m) {@status[m]}
182
+ def load_info!
183
+ @info = ProcessInfo.new(self)
184
+ ProcessInfo::EXPORT.each do |m|
185
+ self.class.send(:define_method, m) {@info.send(m)}
192
186
  end
193
- @dumper = Dumper.new(@status, mem_filename)
194
- end
195
- def match_maps(maps, pattern)
196
- maps.map{|s| s[3]}.find{|seg| pattern.is_a?(Regexp) ? seg =~ pattern : seg.include?(pattern)}
197
- end
198
- def bits_of(elf)
199
- elf[4] == "\x01" ? 32 : 64
187
+ @dumper = Dumper.new(@info, mem_filename)
200
188
  end
201
189
  def mem_filename
202
190
  "/proc/#{pid}/mem"
@@ -0,0 +1,54 @@
1
+ #encoding: ascii-8bit
2
+ module HeapInfo
3
+ # for <tt>Process</tt> to record current info(s)
4
+ # <tt>Process</tt> has a <tt>process_info</tt> object iff the process found (pid not <tt>nil</tt>).
5
+ # Mainly records segments' base.
6
+ class ProcessInfo
7
+ # Methods to be transparent to <tt>process</tt>.
8
+ # e.g. <tt>process.libc alias to process.info.libc</tt>
9
+ EXPORT = %i(libc ld heap elf program stack bits)
10
+
11
+ attr_reader :bits, :program, :stack, :libc, :ld
12
+ alias :elf :program
13
+
14
+ # Instantiate a <tt>ProcessInfo</tt> object
15
+ #
16
+ # @param [HeapInfo::Process] process Load information from maps/memory for <tt>process</tt>
17
+ def initialize(process)
18
+ @pid = process.pid
19
+ options = process.instance_variable_get(:@options)
20
+ maps!
21
+ @bits = bits_of Helper.exe_of @pid
22
+ @elf = @program = Segment.find(maps, File.readlink("/proc/#{@pid}/exe"))
23
+ @stack = Segment.find(maps, '[stack]')
24
+ # well.. stack is a strange case because it will grow in runtime..
25
+ # should i detect stack base growing..?
26
+ @libc = Libc.find(maps, match_maps(maps, options[:libc]), process)
27
+ @ld = Segment.find(maps, match_maps(maps, options[:ld]))
28
+ end
29
+
30
+ # Heap will not be mmapped if the process not use heap yet, so create a lazy loading method.
31
+ # Will re-read maps when heap segment not found yet.
32
+ #
33
+ # @return [HeapInfo::Segment] The <tt>Segment</tt> of heap
34
+ def heap # special handle because heap might not be initialized in the beginning
35
+ @heap ||= Segment.find(maps!, '[heap]')
36
+ end
37
+
38
+ private
39
+ attr_reader :maps
40
+
41
+ # force reload maps
42
+ def maps!
43
+ @maps = Helper.parse_maps Helper.maps_of @pid
44
+ end
45
+
46
+ def bits_of(elf)
47
+ elf[4] == "\x01" ? 32 : 64
48
+ end
49
+
50
+ def match_maps(maps, pattern)
51
+ maps.map{|s| s[3]}.find{|seg| pattern.is_a?(Regexp) ? seg =~ pattern : seg.include?(pattern)}
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module HeapInfo
2
- VERSION = '0.0.0'.freeze
2
+ VERSION = '0.0.1'.freeze
3
3
  end
@@ -0,0 +1,46 @@
1
+ # encoding: ascii-8bit
2
+ require 'heapinfo'
3
+ describe HeapInfo::Cache do
4
+ before(:all) do
5
+ @prefix = 'testcx1dd/'
6
+ end
7
+ after(:each) do
8
+ FileUtils.rm_rf File.join(HeapInfo::Cache::CACHE_DIR, @prefix)
9
+ end
10
+ it 'handle unwritable' do
11
+ org = HeapInfo::Cache::CACHE_DIR
12
+ HeapInfo::Cache.send :remove_const, :CACHE_DIR
13
+ no = '/tmp/no_permission'
14
+ FileUtils.mkdir_p no
15
+ File.chmod 0444, no # no write permission
16
+ HeapInfo::Cache.const_set :CACHE_DIR, no + '/.cache'
17
+ HeapInfo::Cache.send :load
18
+ expect(HeapInfo::Cache::CACHE_DIR).to eq HeapInfo::TMP_DIR + '/.cache/heapinfo'
19
+ HeapInfo::Cache.send :remove_const, :CACHE_DIR
20
+ HeapInfo::Cache.const_set :CACHE_DIR, org
21
+ FileUtils.rm_rf no
22
+ end
23
+
24
+ it 'write' do
25
+ expect(HeapInfo::Cache::write @prefix + '123', {a: 1}).to be true
26
+ end
27
+
28
+ it 'read' do
29
+ expect(HeapInfo::Cache::read @prefix + 'z/zzz').to be nil
30
+ end
31
+
32
+ it 'write and read' do
33
+ key = @prefix + 'fefw/z/zz/xdddd'
34
+ object = {a: {b: 'string', array: [3, '1', 2]}, 'd' => 3}
35
+ expect(HeapInfo::Cache::read key).to be nil
36
+ expect(HeapInfo::Cache::write key, object).to be true
37
+ expect(HeapInfo::Cache::read key).to eq object
38
+ end
39
+
40
+ it 'file corrupted' do
41
+ key = @prefix + 'corrupted'
42
+ HeapInfo::Cache::write key, 'ok'
43
+ IO.binwrite(File.join(HeapInfo::Cache::CACHE_DIR, key), 'not ok')
44
+ expect(HeapInfo::Cache::read key).to be nil
45
+ end
46
+ end
data/spec/dumper_spec.rb CHANGED
@@ -10,19 +10,19 @@ describe HeapInfo::Dumper do
10
10
  expect(dumper.dump(0x400000, 4)).to eq "\x7fELF"
11
11
  end
12
12
  it 'segment' do
13
- segments = {elf: HeapInfo::Segment.new(0x400000, 'elf')}
14
- dumper = HeapInfo::Dumper.new(segments, @mem_filename)
13
+ class S;def elf; HeapInfo::Segment.new(0x400000, 'elf'); end; end
14
+ dumper = HeapInfo::Dumper.new(S.new, @mem_filename)
15
15
  expect(dumper.dump(:elf, 4)).to eq "\x7fELF"
16
16
  end
17
17
  it 'invalid' do
18
- dumper = HeapInfo::Dumper.new({}, @mem_filename)
19
- expect(dumper.dump(:zzz, 1)).to be nil
18
+ dumper = HeapInfo::Dumper.new(HeapInfo::Nil.new, @mem_filename)
19
+ expect {dumper.dump(:zzz, 1)}.to raise_error ArgumentError
20
20
  expect(dumper.dump(0x12345, 1)).to be nil
21
21
  end
22
22
  end
23
23
 
24
24
  it 'dumpable?' do
25
- dumper = HeapInfo::Dumper.new({}, '/proc/self/mem')
25
+ dumper = HeapInfo::Dumper.new(HeapInfo::Nil.new, '/proc/self/mem')
26
26
  expect(dumper.send(:dumpable?)).to be true
27
27
  # a little hack
28
28
  dumper.instance_variable_set(:@filename, '/proc/1/mem')
@@ -34,7 +34,8 @@ describe HeapInfo::Dumper do
34
34
 
35
35
  describe 'find' do
36
36
  before(:all) do
37
- @dumper = HeapInfo::Dumper.new({elf: HeapInfo::Segment.new(0x400000, ''), bits: 64}, '/proc/self/mem')
37
+ class S;def elf; HeapInfo::Segment.new(0x400000, ''); end; def bits; 64; end; end
38
+ @dumper = HeapInfo::Dumper.new(S.new, '/proc/self/mem')
38
39
  end
39
40
  it 'simple' do
40
41
  expect(@dumper.find("ELF", :elf, 4)).to eq 0x400001
@@ -50,6 +51,10 @@ describe HeapInfo::Dumper do
50
51
  it 'parser' do
51
52
  expect(@dumper.find("ELF", ':elf + 1', 3)).to eq 0x400001
52
53
  end
54
+ it 'reach end' do
55
+ # check dumper won't return nil when remain readable memory less than one page
56
+ expect(@dumper.find("\x00", 0x601010, 0x1000).nil?).to be false
57
+ end
53
58
  end
54
59
 
55
60
  describe 'parse_cmd' do
data/spec/process_spec.rb CHANGED
@@ -26,6 +26,7 @@ describe HeapInfo::Process do
26
26
 
27
27
  describe 'victim' do
28
28
  before(:all) do
29
+ HeapInfo::Cache.send :clear_all # force cache miss, to make sure coverage
29
30
  @victim = HeapInfo::TMP_DIR + '/victim'
30
31
  %x(g++ #{File.expand_path('../files/victim.cpp', __FILE__)} -o #{@victim} 2>&1 > /dev/null)
31
32
  pid = fork
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heapinfo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
@@ -10,8 +10,7 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2016-05-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: create an interactive heap info interface while exploiting binary (with
14
- glibc) with ruby
13
+ description: create an interactive memory info interface while pwn / exploiting
15
14
  email:
16
15
  - david942j@gmail.com
17
16
  executables: []
@@ -21,6 +20,7 @@ files:
21
20
  - README.md
22
21
  - lib/heapinfo.rb
23
22
  - lib/heapinfo/arena.rb
23
+ - lib/heapinfo/cache.rb
24
24
  - lib/heapinfo/chunk.rb
25
25
  - lib/heapinfo/chunks.rb
26
26
  - lib/heapinfo/dumper.rb
@@ -29,9 +29,11 @@ files:
29
29
  - lib/heapinfo/libc.rb
30
30
  - lib/heapinfo/nil.rb
31
31
  - lib/heapinfo/process.rb
32
+ - lib/heapinfo/process_info.rb
32
33
  - lib/heapinfo/segment.rb
33
34
  - lib/heapinfo/tools/get_arena.c
34
35
  - lib/heapinfo/version.rb
36
+ - spec/cache_spec.rb
35
37
  - spec/chunk_spec.rb
36
38
  - spec/chunks_spec.rb
37
39
  - spec/dumper_spec.rb
@@ -73,6 +75,7 @@ test_files:
73
75
  - spec/files/64bit_maps
74
76
  - spec/files/victim.cpp
75
77
  - spec/helper_spec.rb
78
+ - spec/cache_spec.rb
76
79
  - spec/string_spec.rb
77
80
  - spec/spec_helper.rb
78
81
  - spec/nil_spec.rb