heapinfo 0.0.0 → 0.0.1

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 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