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