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 +4 -4
- data/README.md +2 -0
- data/lib/heapinfo.rb +3 -7
- data/lib/heapinfo/arena.rb +4 -4
- data/lib/heapinfo/cache.rb +67 -0
- data/lib/heapinfo/chunk.rb +2 -2
- data/lib/heapinfo/dumper.rb +21 -19
- data/lib/heapinfo/ext/string.rb +1 -1
- data/lib/heapinfo/libc.rb +19 -8
- data/lib/heapinfo/process.rb +21 -33
- data/lib/heapinfo/process_info.rb +54 -0
- data/lib/heapinfo/version.rb +1 -1
- data/spec/cache_spec.rb +46 -0
- data/spec/dumper_spec.rb +11 -6
- data/spec/process_spec.rb +1 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 546784a7f3d841d2505b517a355afc2e1e914e5d
|
4
|
+
data.tar.gz: 166efbf709763a0a5a5a68d3e0f13515839bc125
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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'
|
data/lib/heapinfo/arena.rb
CHANGED
@@ -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,
|
176
|
-
work.call(@bk, :bk_of,
|
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
|
data/lib/heapinfo/chunk.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|
data/lib/heapinfo/dumper.rb
CHANGED
@@ -6,10 +6,10 @@ module HeapInfo
|
|
6
6
|
|
7
7
|
# Instantiate a <tt>HeapInfo::Dumper</tt> object
|
8
8
|
#
|
9
|
-
# @param [
|
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(
|
12
|
-
@
|
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,
|
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 =
|
34
|
-
mem = file.
|
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: @
|
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
|
-
|
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
|
-
@
|
203
|
+
@info.bits / 8
|
202
204
|
end
|
203
205
|
end
|
204
206
|
end
|
data/lib/heapinfo/ext/string.rb
CHANGED
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 @
|
9
|
+
return @offset[:main_arena] if @offset[:main_arena]
|
5
10
|
return nil unless exhaust_search :main_arena
|
6
|
-
@
|
11
|
+
@offset[:main_arena]
|
7
12
|
end
|
8
13
|
|
9
14
|
def main_arena
|
10
|
-
return @
|
15
|
+
return @main_arena.reload! if @main_arena
|
11
16
|
off = main_arena_offset
|
12
17
|
return if off.nil?
|
13
|
-
@
|
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
|
-
|
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
|
-
|
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
|
data/lib/heapinfo/process.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
module HeapInfo
|
3
3
|
# Main class of heapinfo.
|
4
4
|
class Process
|
5
|
-
# The
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
159
|
+
attr_accessor :dumper
|
157
160
|
def load?
|
158
161
|
@pid != nil
|
159
162
|
end
|
160
163
|
|
161
|
-
def load! #
|
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
|
-
|
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
|
179
|
-
|
180
|
-
|
181
|
-
|
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(@
|
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
|
data/lib/heapinfo/version.rb
CHANGED
data/spec/cache_spec.rb
ADDED
@@ -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
|
-
|
14
|
-
dumper = HeapInfo::Dumper.new(
|
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(
|
19
|
-
expect
|
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(
|
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
|
-
|
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.
|
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
|
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
|