heapinfo 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ module HeapInfo
2
+ # Self-defined array for collecting chunk(s)
3
+ class Chunks
4
+ # Instantiate a <tt>HeapInfo::Chunks</tt> object
5
+ def initialize
6
+ @chunks = []
7
+ end
8
+
9
+ def method_missing(method_sym, *arguments, &block)
10
+ return super unless @chunks.respond_to? method_sym
11
+ @chunks.send(method_sym, *arguments, &block)
12
+ end
13
+
14
+ # Hook <tt>#to_s</tt> for pretty printing.
15
+ # @return [String]
16
+ def to_s
17
+ @chunks.map(&:to_s).join("\n")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,204 @@
1
+ module HeapInfo
2
+ # Class for memory dump relation works
3
+ class Dumper
4
+ # Default dump length
5
+ DUMP_BYTES = 8
6
+
7
+ # Instantiate a <tt>HeapInfo::Dumper</tt> object
8
+ #
9
+ # @param [Hash] segments With values <tt>HeapInfo::Segment</tt>
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
13
+ need_permission unless dumpable?
14
+ end
15
+
16
+ # A helper for <tt>HeapInfo::Process</tt> to dump memory.
17
+ # @param [Mixed] args The use input commands, see examples of <tt>HeapInfo::Process#dump</tt>.
18
+ # @return [String, NilClass] Dump results. If error happend, <tt>nil</tt> is returned.
19
+ # @example
20
+ # p dump(:elf, 4)
21
+ # # => "\x7fELF"
22
+ def dump(*args)
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
32
+ file = mem_f
33
+ file.pos = addr + offset
34
+ mem = file.read len
35
+ file.close
36
+ mem
37
+ rescue
38
+ nil
39
+ end
40
+
41
+ # Return the dump result as chunks.
42
+ # see <tt>HeapInfo::Chunks</tt> and <tt>HeapInfo::Chunk</tt> for more information.
43
+ #
44
+ # Note: Same as <tt>dump</tt>, need permission of attaching another process.
45
+ # @return [HeapInfo::Chunks] An array of chunk(s).
46
+ # @param [Mixed] args Same as arguments of <tt>#dump</tt>
47
+ def dump_chunks(*args)
48
+ base = base_of(*args)
49
+ dump(*args).to_chunks(bits: @segments[:bits], base: base)
50
+ end
51
+
52
+ # Show dump results like in gdb's command <tt>x</tt>.
53
+ #
54
+ # Details are in <tt>HeapInfo:Process#x</tt>
55
+ # @param [Integer] count The number of result need to dump.
56
+ # @param [Mixed] commands Same format as <tt>#dump(*args)</tt>.
57
+ # @param [IO] io <tt>IO</tt> that use for printing.
58
+ # @return [NilClass] The return value of <tt>io.puts</tt>.
59
+ # @example
60
+ # x 3, 0x400000
61
+ # # 0x400000: 0x00010102464c457f 0x0000000000000000
62
+ # # 0x400010: 0x00000001003e0002
63
+ def x(count, *commands, io: $stdout)
64
+ commands = commands + [count * size_t]
65
+ base = base_of(*commands)
66
+ res = dump(*commands).unpack(size_t == 4 ? "L*" : "Q*")
67
+ str = res.group_by.with_index{|_, i| i / (16 / size_t) }.map do |round, values|
68
+ "%#x:\t" % (base + round * 16) + values.map{|v| Helper.color "0x%0#{size_t * 2}x" % v}.join("\t")
69
+ end.join("\n")
70
+ io.puts str
71
+ end
72
+
73
+ # Search a specific value/string/regexp in memory.
74
+ # <tt>#find</tt> only return the first matched address.
75
+ # @param [Integer, String, Regexp] pattern The desired search pattern, can be value(<tt>Integer</tt>), string, or regular expression.
76
+ # @param [Integer, String, Symbol] from Start address for searching, can be segment(<tt>Symbol</tt>) or segments with offset. See examples for more information.
77
+ # @param [Integer] length The length limit for searching.
78
+ # @return [Integer, NilClass] The first matched address, <tt>nil</tt> is returned when no such pattern found.
79
+ # @example
80
+ # find(/E.F/, :elf)
81
+ # # => 4194305
82
+ # find(0x4141414141414141, 'heap+0x10', 0x1000)
83
+ # # => 6291472
84
+ # find('/bin/sh', :libc)
85
+ # # => 140662379588827
86
+ def find(pattern, from, length)
87
+ from = base_of(from)
88
+ length = 1 << 40 if length.is_a? Symbol
89
+ case pattern
90
+ when Integer; find_integer(pattern, from, length)
91
+ when String; find_string(pattern, from, length)
92
+ when Regexp; find_regexp(pattern, from, length)
93
+ else; nil
94
+ end
95
+ end
96
+
97
+
98
+ # Parse the dump command into <tt>[base, offset, length]</tt>
99
+ # @param [Array] args The command, see examples for more information
100
+ # @return [Array<Symbol, Integer>] <tt>[base, offset, length]</tt>, while <tt>base</tt> can be a [Symbol] or an [Integer]. <tt>length</tt> has default value equal to <tt>8</tt>.
101
+ # @example
102
+ # HeapInfo::Dumper.parse_cmd([:heap, 32, 10])
103
+ # # [:heap, 32, 10]
104
+ # HeapInfo::Dumper.parse_cmd(['heap+0x10, 10'])
105
+ # # [:heap, 16, 10]
106
+ # HeapInfo::Dumper.parse_cmd(['heap+0x10'])
107
+ # # [:heap, 16, 8]
108
+ # HeapInfo::Dumper.parse_cmd([0x400000, 4])
109
+ # # [0x400000, 0, 4]
110
+ def self.parse_cmd(args)
111
+ args = split_cmd args
112
+ return :fail unless args.size == 3
113
+ len = args[2].nil? ? DUMP_BYTES : Integer(args[2])
114
+ offset = Integer(args[1])
115
+ base = args[0]
116
+ base = Helper.integer?(base) ? Integer(base) : base.delete(':').to_sym
117
+ [base, offset, len]
118
+ end
119
+
120
+ # Helper for <tt>#parse_cmd</tt>.
121
+ #
122
+ # Split commands to exactly three parts: <tt>[base, offset, length]</tt>
123
+ # <tt>length</tt> is <tt>nil</tt> if not present.
124
+ # @param [Array] args
125
+ # @return [Array<String>] <tt>[base, offset, length]</tt> in string expression.
126
+ # @example
127
+ # HeapInfo::Dumper.split_cmd([:heap, 32, 10])
128
+ # # ['heap', '32', '10']
129
+ # HeapInfo::Dumper.split_cmd([':heap+0x10, 10'])
130
+ # # [':heap', '0x10', '10']
131
+ # HeapInfo::Dumper.split_cmd([':heap+0x10'])
132
+ # # [':heap', '0x10', nil]
133
+ # HeapInfo::Dumper.split_cmd([0x400000, 4])
134
+ # # ['4194304', 0, '4']
135
+ def self.split_cmd(args)
136
+ args = args.join(',').delete(' ').split(',').reject(&:empty?) # 'heap, 100', 32 => 'heap', '100', '32'
137
+ return [] if args.empty?
138
+ if args[0].include? '+' # 'heap+0x1'
139
+ args.unshift(*args.shift.split('+', 2))
140
+ elsif args.size <= 2 # no offset given
141
+ args.insert(1, 0)
142
+ end
143
+ args << nil if args.size <= 2 # no length given
144
+ args[0, 3]
145
+ end
146
+
147
+ private
148
+ def need_permission
149
+ puts Helper.color(%q(Could not attach to process. Check the setting of /proc/sys/kernel/yama/ptrace_scope, or try again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf), sev: :fatal)
150
+ end
151
+
152
+ # use /proc/[pid]/mem for memory dump, must sure have permission
153
+ def dumpable?
154
+ mem_f.close
155
+ true
156
+ rescue => e
157
+ if e.is_a? Errno::EACCES
158
+ false
159
+ else
160
+ throw e
161
+ end
162
+ end
163
+
164
+ def mem_f
165
+ File.open(@filename)
166
+ end
167
+
168
+ 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
172
+ end
173
+
174
+ def find_integer(value, from, length)
175
+ find_string([value].pack(size_t == 4 ? "L*" : "Q*"), from, length)
176
+ end
177
+
178
+ def find_string(string, from ,length)
179
+ batch_dumper(from, length) {|str| str.index string}
180
+ end
181
+
182
+ def find_regexp(pattern, from ,length)
183
+ batch_dumper(from, length) {|str| str =~ pattern}
184
+ end
185
+
186
+ def batch_dumper(from, remain_size)
187
+ page_size = 0x1000
188
+ while remain_size > 0
189
+ dump_size = [remain_size, page_size].min
190
+ str = dump(from, dump_size)
191
+ break if str.nil? # unreadable
192
+ break unless (idx = yield(str)).nil?
193
+ break if str.length < dump_size # remain is unreadable
194
+ remain_size -= str.length
195
+ from += str.length
196
+ end
197
+ return if idx.nil?
198
+ from + idx
199
+ end
200
+ def size_t
201
+ @segments[:bits] / 8
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,29 @@
1
+ module HeapInfo
2
+ module Ext
3
+ module String
4
+ # Methods to be mixed into String
5
+ module InstanceMethods
6
+ def to_chunk(bits: 64, base: 0)
7
+ size_t = bits / 8
8
+ dumper = lambda{|addr, len| self[addr-base, len]}
9
+ Chunk.new(size_t, base, dumper)
10
+ end
11
+
12
+ def to_chunks(bits: 64, base: 0)
13
+ size_t = bits / 8
14
+ chunks = Chunks.new
15
+ cur = 0
16
+ while cur + size_t * 2 <= self.length
17
+ now_chunk = self[cur, size_t * 2].to_chunk
18
+ sz = now_chunk.size
19
+ chunks << self[cur, sz + 1].to_chunk(bits: bits, base: base + cur) # +1 for dump prev_inuse
20
+ cur += sz
21
+ end
22
+ chunks
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ ::String.send(:include, HeapInfo::Ext::String::InstanceMethods)
@@ -0,0 +1,123 @@
1
+ module HeapInfo
2
+ # Some helper functions
3
+ module Helper
4
+ # Get the process id of a process
5
+ # @param [String] prog The request process name
6
+ # @return [Fixnum] process id
7
+ def self.pidof(prog)
8
+ # plz, don't cmd injection your self :p
9
+ pid = `pidof #{prog}`.strip.to_i
10
+ return nil if pid == 0 # process not exists yet
11
+ throw "pidof #{prog} fail" unless pid.between?(2, 65535)
12
+ pid
13
+ #TODO: handle when multi processes exists
14
+ end
15
+
16
+ # Create read <tt>/proc/[pid]/*</tt> methods
17
+ %w(exe maps).each do |method|
18
+ self.define_singleton_method("#{method}_of".to_sym) do |pid|
19
+ begin
20
+ IO.binread("/proc/#{pid}/#{method}")
21
+ rescue
22
+ throw "reading /proc/#{pid}/#{method} error"
23
+ end
24
+ end
25
+ end
26
+
27
+ # Parse the contents of <tt>/proc/[pid]/maps</tt>.
28
+ #
29
+ # @param [String] content The file content of <tt>/proc/[pid]/maps</tt>
30
+ # @return [Array] In form of <tt>[[start, end, permission, name], ...]</tt>. See examples.
31
+ # @example
32
+ # HeapInfo::Helper.parse_maps(<<EOS
33
+ # 00400000-0040b000 r-xp 00000000 ca:01 271708 /bin/cat
34
+ # 00bc4000-00be5000 rw-p 00000000 00:00 0 [heap]
35
+ # 7f2788315000-7f2788316000 r--p 00022000 ca:01 402319 /lib/x86_64-linux-gnu/ld-2.19.so
36
+ # EOS
37
+ # )
38
+ # # [[0x400000, 0x40b000, 'r-xp', '/bin/cat'],
39
+ # # [0xbc4000, 0xbe5000, 'rw-p', '[heap]'],
40
+ # # [0x7f2788315000, 0x7f2788316000, 'r--p', '/lib/x86_64-linux-gnu/ld-2.19.so']]
41
+ def self.parse_maps(content)
42
+ lines = content.split("\n")
43
+ lines.map do |line|
44
+ s = line.scan(/^([0-9a-f]+)-([0-9a-f]+)\s([rwxp-]{4})[^\/|\[]*([\/|\[].+)$/)[0]
45
+ next nil if s.nil?
46
+ s[0],s[1] = s[0,2].map{|h|h.to_i(16)}
47
+ s
48
+ end.compact
49
+ end
50
+
51
+ # Color codes for pretty print
52
+ COLOR_CODE = {
53
+ esc_m: "\e[0m",
54
+ normal_s: "\e[38;5;1m", # red
55
+ integer: "\e[38;5;12m", # light blue
56
+ fatal: "\e[38;5;197m", # dark red
57
+ bin: "\e[38;5;120m", # light green
58
+ klass: "\e[38;5;155m", # pry like
59
+ sym: "\e[38;5;229m", # pry like
60
+ }
61
+ # Wrapper color codes for for pretty inspect
62
+ # @param [String] s Contents for wrapper
63
+ # @param [Symbol?] sev Specific which kind of color want to use, valid symbols are defined in <tt>#COLOR_CODE</tt>.
64
+ # If this argument is not present, will detect according to the content of <tt>s</tt>
65
+ # @return [String] wrapper with color codes.
66
+ def self.color(s, sev: nil)
67
+ s = s.to_s
68
+ color = ''
69
+ cc = COLOR_CODE
70
+ if cc.keys.include?(sev)
71
+ color = cc[sev]
72
+ elsif s =~ /^(0x)?[0-9a-f]+$/ # integers
73
+ color = cc[:integer]
74
+ else #normal string
75
+ color = cc[:normal_s]
76
+ end
77
+ "#{color}#{s.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
78
+ end
79
+
80
+ # Unpack strings to integer.
81
+ #
82
+ # Like the <tt>p32</tt> and <tt>p64</tt> in pwntools.
83
+ #
84
+ # @param [Integer] size_t Either 4 or 8.
85
+ # @param [String] data String to be unpack.
86
+ # @return [Integer] Unpacked result.
87
+ # @example
88
+ # HeapInfo::Helper.unpack(4, "\x12\x34\x56\x78")
89
+ # # 0x78563412
90
+ # HeapInfo::Helper.unpack(8, "\x12\x34\x56\x78\xfe\xeb\x90\x90")
91
+ # # 0x9090ebfe78563412
92
+ def self.unpack(size_t, data)
93
+ data.unpack(size_t == 4 ? 'L*' : 'Q*')[0]
94
+ end
95
+
96
+ # Retrieve pure class name(without module) of an object
97
+ # @param [Object] obj Any instance
98
+ # @return [String] Class name of <tt>obj</tt>
99
+ # @example
100
+ # # suppose obj is an instance of HeapInfo::Chunk
101
+ # Helper.class_name(obj)
102
+ # # => 'Chunk'
103
+ def self.class_name(obj)
104
+ obj.class.name.split('::').last || obj.class.name
105
+ end
106
+
107
+ # For checking a string is actually an integer
108
+ # @param [String] str String to be checked
109
+ # @return [Boolean] If <tt>str</tt> can be converted into integer
110
+ # @example
111
+ # Helper.integer? '1234'
112
+ # # => true
113
+ # Helper.integer? '0x1234'
114
+ # # => true
115
+ # Helper.integer? '0xheapoverflow'
116
+ # # => false
117
+ def self.integer?(str)
118
+ !!Integer(str)
119
+ rescue ArgumentError, TypeError
120
+ false
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,46 @@
1
+ module HeapInfo
2
+ class Libc < Segment
3
+ def main_arena_offset
4
+ return @_main_arena_offset if @_main_arena_offset
5
+ return nil unless exhaust_search :main_arena
6
+ @_main_arena_offset
7
+ end
8
+
9
+ def main_arena
10
+ return @_main_arena.reload if @_main_arena
11
+ off = main_arena_offset
12
+ return if off.nil?
13
+ @_main_arena = Arena.new(off + self.base, process.bits, lambda{|*args|process.send(:dumper).dump(*args)})
14
+ end
15
+
16
+ def self.find(maps, name, process)
17
+ obj = super(maps, name)
18
+ obj.send(:process=, process)
19
+ obj
20
+ end
21
+
22
+
23
+ private
24
+ attr_accessor :process
25
+ # only for searching offset of main_arena now
26
+ def exhaust_search(symbol)
27
+ return false if symbol != :main_arena
28
+ # TODO: read from cache
29
+ @_main_arena_offset = resolve_main_arena_offset
30
+ true
31
+ end
32
+
33
+ def resolve_main_arena_offset
34
+ tmp_elf = HeapInfo::TMP_DIR + "/get_arena"
35
+ libc_file = HeapInfo::TMP_DIR + "/libc.so.6"
36
+ ld_file = HeapInfo::TMP_DIR + "/ld.so"
37
+ flags = "-w #{@process.bits == 32 ? '-m32' : ''}"
38
+ %x(cp #{self.name} #{libc_file} && \
39
+ 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 && \
42
+ #{ld_file} --library-path #{HeapInfo::TMP_DIR} #{tmp_elf} && \
43
+ rm #{tmp_elf} #{libc_file} #{ld_file}).to_i(16)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ module HeapInfo
2
+ # Self defined <tt>nil</tt> like class.
3
+ #
4
+ # Be the return values of <tt>#dump</tt> or <tt>#dump_chunks</tt>, to prevent use the return value for calculating accidentally while exploiting remote.
5
+ class Nil
6
+ %i(nil? inspect to_s).each do |method_sym|
7
+ define_method(method_sym){|*args, &block| nil.send(method_sym, *args, &block)}
8
+ end
9
+
10
+ # Hook all missing methods
11
+ # @return [HeapInfo::Nil] return <tt>self</tt> so that it can be a <tt>nil</tt> chain.
12
+ # @example
13
+ # # h.dump would return Nil when process not found
14
+ # p h.dump(:heap)[8,8].unpack("Q*)
15
+ # # => nil
16
+ def method_missing(method_sym, *args, &block)
17
+ return nil.send(method_sym, *args, &block) if nil.respond_to? method_sym
18
+ self
19
+ end
20
+
21
+ # To prevent error raised when using <tt>puts Nil.new</tt>
22
+ # @return [Array] Empty array
23
+ def to_ary
24
+ []
25
+ end
26
+ end
27
+ end