heapinfo 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,205 @@
1
+ #encoding: ascii-8bit
2
+ module HeapInfo
3
+ # Main class of heapinfo.
4
+ class Process
5
+ # The dafault options of libaries,
6
+ # use for matching glibc and ld segments in <tt>/proc/[pid]/maps</tt>
7
+ DEFAULT_LIB = {
8
+ libc: /libc[^\w]/,
9
+ ld: /\/ld-.+\.so/,
10
+ }
11
+ # @return [Fixnum, NilClass] return the pid of process, <tt>nil</tt> if no such process found
12
+ attr_reader :pid
13
+ attr_reader :status
14
+
15
+ # Instantiate a <tt>HeapInfo::Process</tt> object
16
+ # @param [String, Fixnum] prog Process name or pid, see <tt>HeapInfo::heapinfo</tt> for more information
17
+ # @param [Hash] options libraries' filename, see <tt>HeapInfo::heapinfo</tt> for more information
18
+ def initialize(prog, options = {})
19
+ @prog = prog
20
+ @options = DEFAULT_LIB.merge options
21
+ load!
22
+ return unless load?
23
+ @dumper = Dumper.new(@status, mem_filename)
24
+ end
25
+
26
+ # Use this method to wrapper all HeapInfo methods.
27
+ #
28
+ # Since <tt>::HeapInfo</tt> is a tool(debugger) for local usage,
29
+ # while exploiting remote service, all methods will not work properly.
30
+ # So I suggest to wrapper all methods inside <tt>#debug</tt>,
31
+ # which will ignore the block while the victim process is not found.
32
+ #
33
+ # @example
34
+ # h = heapinfo('./victim') # such process doesn't exist
35
+ # libc_base = leak_libc_base_of_victim # normal exploit
36
+ # h.debug {
37
+ # # for local to check if exploit correct
38
+ # fail('libc_base') unless libc_base == h.libc.base
39
+ # }
40
+ # # block of #debug will not execute if can't found process
41
+ def debug
42
+ return unless load!
43
+ yield if block_given?
44
+ end
45
+
46
+ # Dump the content of specific memory address.
47
+ #
48
+ # Note: This method require you have permission of attaching another process. If not, a warning message will present.
49
+ #
50
+ # @param [Mixed] args Will be parsed into <tt>[base, offset, length]</tt>, see Examples for more information.
51
+ # @return [String, HeapInfo::Nil] The content needed. When the request address is not readable or the process not exists, <tt>HeapInfo::Nil.new</tt> is returned.
52
+ #
53
+ # @example
54
+ # h = heapinfo('victim')
55
+ # h.dump(:heap) # &heap[0, 8]
56
+ # h.dump(:heap, 64) # &heap[0, 64]
57
+ # h.dump(:heap, 256, 64) # &heap[256, 64]
58
+ # h.dump('heap+256, 64' # &heap[256, 64]
59
+ # h.dump('heap+0x100', 64) # &heap[256, 64]
60
+ # h.dump(<segment>, 8) # semgent can be [heap, stack, (program|elf), libc, ld]
61
+ # h.dump(addr, 64) # addr[0, 64]
62
+ #
63
+ # # Invalid usage
64
+ # dump(:meow) # no such segment
65
+ # dump('heap-1, 64') # not support '-'
66
+ def dump(*args)
67
+ return Nil.new unless load?
68
+ @dumper.dump(*args)
69
+ end
70
+
71
+ # Return the dump result as chunks.
72
+ # see <tt>HeapInfo::Dumper#dump_chunks</tt> for more information.
73
+ #
74
+ # @return [HeapInfo::Chunks, HeapInfo::Nil] An array of chunk(s).
75
+ # @param [Mixed] args Same as arguments of <tt>#dump</tt>
76
+ def dump_chunks(*args)
77
+ return Nil.new unless load?
78
+ @dumper.dump_chunks(*args)
79
+ end
80
+
81
+ # Gdb-like command
82
+ #
83
+ # Show dump results like in gdb's command <tt>x</tt>,
84
+ # while will auto detect the current elf class to decide using <tt>gx</tt> or <tt>wx</tt>.
85
+ #
86
+ # The dump results wrapper with color codes and nice typesetting will output to <tt>stdout</tt> by default.
87
+ # @param [Integer] count The number of result need to dump, see examples for more information
88
+ # @param [Mixed] commands Same format as <tt>#dump(*args)</tt>, see <tt>#dump</tt> for more information
89
+ # @param [IO] io <tt>IO</tt> that use for printing, default is <tt>$stdout</tt>
90
+ # @return [NilClass] The return value of <tt>io.puts</tt>.
91
+ # @example
92
+ # h.x 8, :heap
93
+ # # 0x1f0d000: 0x0000000000000000 0x0000000000002011
94
+ # # 0x1f0d010: 0x00007f892a9f87b8 0x00007f892a9f87b8
95
+ # # 0x1f0d020: 0x0000000000000000 0x0000000000000000
96
+ # # 0x1f0d030: 0x0000000000000000 0x0000000000000000
97
+ # @example
98
+ # h.x 3, 0x400000
99
+ # # 0x400000: 0x00010102464c457f 0x0000000000000000
100
+ # # 0x400010: 0x00000001003e0002
101
+ def x(count, *commands, io: $stdout)
102
+ return unless load? and io.respond_to? :puts
103
+ @dumper.x(count, *commands, io: io)
104
+ end
105
+
106
+ # Gdb-like command.
107
+ #
108
+ # 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
+ # @param [Integer, String, Regexp] pattern The desired search pattern, can be value(<tt>Integer</tt>), string, or regular expression.
111
+ # @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
+ # @param [Integer] length The search length limit, default is unlimited, which will search until pattern found or reach unreadable memory.
113
+ # @return [Integer, NilClass] The first matched address, <tt>nil</tt> is returned when no such pattern found.
114
+ # @example
115
+ # h.find(0xdeadbeef, :heap)
116
+ # h.find(0xdeadbeef, 'heap+0x10', 0x1000)
117
+ def find(pattern, from, length = :unlimited)
118
+ return Nil.new unless load?
119
+ length = 1 << 40 if length.is_a? Symbol
120
+ @dumper.find(pattern, from, length)
121
+ end
122
+
123
+ # <tt>search</tt> is more intutive to me
124
+ alias :search :find
125
+
126
+ # Pretty dump of bins layouts.
127
+ #
128
+ # The request layouts will output to <tt>stdout</tt> by default.
129
+ # @param [Array<Symbol>] args Bin type(s) you want to see.
130
+ # @param [IO] io <tt>IO</tt> that use for printing, default is <tt>$stdout</tt>
131
+ # @return [NilClass] The return value of <tt>io.puts</tt>.
132
+ # @example
133
+ # h.layouts :fastbin, :unsorted_bin, :smallbin
134
+ def layouts(*args, io: $stdout)
135
+ return unless load? and io.respond_to? :puts
136
+ io.puts self.libc.main_arena.layouts(*args)
137
+ end
138
+
139
+ # Show simple information of target process.
140
+ # Contains program names, pid, and segments' info.
141
+ #
142
+ # @return [String]
143
+ # @example
144
+ # puts h
145
+ def to_s
146
+ return "Process not found" unless load?
147
+ "Program: #{Helper.color program.name} PID: #{Helper.color pid}\n" +
148
+ program.to_s +
149
+ heap.to_s +
150
+ stack.to_s +
151
+ libc.to_s +
152
+ ld.to_s
153
+ end
154
+
155
+ private
156
+ attr_reader :dumper
157
+ def load?
158
+ @pid != nil
159
+ end
160
+
161
+ def load! # force load is not efficient
162
+ @pid = fetch_pid
163
+ return false if @pid.nil? # still can't load
164
+ load_status @options
165
+ true
166
+ end
167
+
168
+ def fetch_pid
169
+ pid = nil
170
+ if @prog.is_a? String
171
+ pid = Helper.pidof @prog
172
+ elsif @prog.is_a? Integer
173
+ pid = @prog
174
+ end
175
+ pid
176
+ end
177
+
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]}
192
+ 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
200
+ end
201
+ def mem_filename
202
+ "/proc/#{pid}/mem"
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,34 @@
1
+ module HeapInfo
2
+ class Segment
3
+
4
+ # Base address of segment
5
+ attr_reader :base
6
+ # Name of segment
7
+ attr_reader :name
8
+ # Instantiate a <tt>HeapInfo::Segment</tt> object
9
+ # @param [Integer] base Base address
10
+ # @param [String] name Name of segment
11
+ def initialize(base, name)
12
+ @base = base
13
+ @name = name
14
+ end
15
+
16
+ # Hook <tt>#to_s</tt> for pretty printing
17
+ # @return [String] Information of name and base address wrapper with color codes.
18
+ def to_s
19
+ "%-28s\tbase @ #{Helper.color("%#x" % base)}\n" % Helper.color(name.split('/')[-1])
20
+ end
21
+
22
+ # Helper for create an <tt>Segment</tt>
23
+ #
24
+ # Search the specific <tt>pattern</tt> in <tt>maps</tt> and return a <tt>HeapInfo::Segment</tt> object.
25
+ #
26
+ # @param [Array] maps <tt>maps</tt> is in the form of the return value of <tt>HeapInfo::Helper.parse_maps</tt>
27
+ # @param [Regexp, String] pattern The segment name want to match in maps. If <tt>String</tt> is given, the pattern is matched as a substring.
28
+ # @return [HeapInfo::Segment, NilClass] The request <tt>Segment</tt> object. If the pattern is not matched, <tt>nil</tt> will be returned.
29
+ def self.find(maps, pattern)
30
+ needs = maps.select{|m| pattern.is_a?(Regexp) ? m[3] =~ pattern : m[3].include?(pattern)}
31
+ self.new needs.map{|m| m[0]}.min, needs[0][3] unless needs.empty?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ /*
2
+ @author: david942j
3
+ use for get libc's main_arena offset
4
+ Sample Usage
5
+ > ./get_arena
6
+ > LD_LIBRARY_PATH=. ./get_arena
7
+ > ./ld-linux.so.2 --library-path . ./get_arena
8
+ */
9
+ #include <stdlib.h>
10
+ #include <stdio.h>
11
+ #include <stddef.h>
12
+ #define SZ sizeof(size_t)
13
+ #define PAGE_SIZE 0x1000
14
+ void *search_head(size_t e) {
15
+ e = (e >> 12) << 12;
16
+ while(strncmp((void*)e, "\177ELF", 4)) e -= PAGE_SIZE;
17
+ return (void*) e;
18
+ }
19
+ int main() {
20
+ void **p = (void**)malloc(SZ*16); // small bin with chunk size SZ*18
21
+ void *z = malloc(SZ); // prevent p merge with top chunk
22
+ *p = z; // prevent compiler optimize
23
+ free(p); // now *p must be the pointer of the (chunk_ptr) unsorted bin
24
+ //TODO: check if this offset change in different version glibc
25
+ z = (void*)((*p) - (4 + 4 + SZ * 10 )); // mutex+flags+fastbin[]
26
+ void* a = search_head((size_t)__builtin_return_address(0));
27
+ printf("%p\n", z-a);
28
+ return 0;
29
+ }
@@ -0,0 +1,3 @@
1
+ module HeapInfo
2
+ VERSION = '0.0.0'.freeze
3
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: ascii-8bit
2
+ require 'heapinfo'
3
+ describe HeapInfo::Chunk do
4
+ describe '32bit' do
5
+ before(:all) do
6
+ @fast = [0, 0x47, 0x1337].pack("L*").to_chunk(bits: 32)
7
+ @small = [0, 0x48, 0xabcdef].pack("L*").to_chunk(bits: 32)
8
+ end
9
+ it 'basic' do
10
+ expect(@fast.size_t).to be 4
11
+ expect(@fast.size).to be 0x40
12
+ expect(@fast.flags).to eq [:non_main_arena, :mmapped, :prev_inuse]
13
+ expect(@fast.bintype).to eq :fast
14
+ expect(@fast.data).to eq [0x1337].pack("L*")
15
+ expect(@small.bintype).to eq :small
16
+ end
17
+
18
+ it 'to_s' do
19
+ expect(@small.to_s).to eq "\e[38;5;155m#<HeapInfo::Chunk:0>\n\e[0mflags = []\nsize = \e[38;5;12m0x48\e[0m (small)\nprev_size = \e[38;5;12m0\e[0m\ndata = \e[38;5;1m\"\\xEF\\xCD\\xAB\\x00\"\e[0m...\n"
20
+ end
21
+ end
22
+
23
+ describe '64bit' do
24
+ before(:all) do
25
+ @fast = [0, 0x87, 0x1337].pack("Q*").to_chunk # default 64bits
26
+ @small = [0, 0x90, 0xdead].pack("Q*").to_chunk
27
+ end
28
+ it 'basic' do
29
+ expect(@fast.size_t).to be 8
30
+ expect(@fast.size).to be 0x80
31
+ expect(@fast.flags).to eq [:non_main_arena, :mmapped, :prev_inuse]
32
+ expect(@fast.bintype).to eq :fast
33
+ expect(@fast.data).to eq [0x1337].pack("Q*")
34
+ expect(@small.bintype).to eq :small
35
+ end
36
+ it 'to_s' do
37
+ expect(@small.to_s).to eq "\e[38;5;155m#<HeapInfo::Chunk:0>\n\e[0mflags = []\nsize = \e[38;5;12m0x90\e[0m (small)\nprev_size = \e[38;5;12m0\e[0m\ndata = \e[38;5;1m\"\\xAD\\xDE\\x00\\x00\\x00\\x00\\x00\\x00\"\e[0m...\n"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: ascii-8bit
2
+ require 'heapinfo'
3
+ describe HeapInfo::Chunks do
4
+ before(:each) do
5
+ @chunks = HeapInfo::Chunks.new
6
+ @chunks << 0; @chunks << 1; @chunks << 2
7
+ end
8
+ it '<<' do
9
+ expect(@chunks.size).to be 3
10
+ @chunks << ("\x00"*16).to_chunk
11
+ expect(@chunks.size).to be 4
12
+ end
13
+ it 'each' do
14
+ @chunks.each_with_index{|c, idx|
15
+ expect(c).to be idx
16
+ }
17
+ end
18
+ it 'to_s' do
19
+ expect(@chunks.to_s).to eq @chunks.instance_variable_get(:@chunks).map(&:to_s).join("\n")
20
+ end
21
+ it 'size' do
22
+ expect(@chunks.size).to eq @chunks.instance_variable_get(:@chunks).size
23
+ expect(@chunks.length).to eq @chunks.instance_variable_get(:@chunks).length
24
+ end
25
+ end
@@ -0,0 +1,79 @@
1
+ # encoding: ascii-8bit
2
+ require 'heapinfo'
3
+ describe HeapInfo::Dumper do
4
+ describe 'dump' do
5
+ before(:each) do
6
+ @mem_filename = '/proc/self/mem'
7
+ end
8
+ it 'simple' do
9
+ dumper = HeapInfo::Dumper.new(nil, @mem_filename)
10
+ expect(dumper.dump(0x400000, 4)).to eq "\x7fELF"
11
+ end
12
+ it 'segment' do
13
+ segments = {elf: HeapInfo::Segment.new(0x400000, 'elf')}
14
+ dumper = HeapInfo::Dumper.new(segments, @mem_filename)
15
+ expect(dumper.dump(:elf, 4)).to eq "\x7fELF"
16
+ end
17
+ it 'invalid' do
18
+ dumper = HeapInfo::Dumper.new({}, @mem_filename)
19
+ expect(dumper.dump(:zzz, 1)).to be nil
20
+ expect(dumper.dump(0x12345, 1)).to be nil
21
+ end
22
+ end
23
+
24
+ it 'dumpable?' do
25
+ dumper = HeapInfo::Dumper.new({}, '/proc/self/mem')
26
+ expect(dumper.send(:dumpable?)).to be true
27
+ # a little hack
28
+ dumper.instance_variable_set(:@filename, '/proc/1/mem')
29
+ expect(dumper.send(:dumpable?)).to be false
30
+ expect(dumper.dump).to be nil # show need permission
31
+ dumper.instance_variable_set(:@filename, '/proc/-1/mem')
32
+ expect {dumper.send(:dumpable?)}.to raise_error ArgumentError
33
+ end
34
+
35
+ describe 'find' do
36
+ before(:all) do
37
+ @dumper = HeapInfo::Dumper.new({elf: HeapInfo::Segment.new(0x400000, ''), bits: 64}, '/proc/self/mem')
38
+ end
39
+ it 'simple' do
40
+ expect(@dumper.find("ELF", :elf, 4)).to eq 0x400001
41
+ expect(@dumper.find("ELF", :elf, 3)).to be nil
42
+ end
43
+ it 'regexp' do
44
+ addr = @dumper.find(/ru.y/, :elf, 0x1000)
45
+ expect(@dumper.dump(addr, 4) =~ /ru.y/).to eq 0
46
+ end
47
+ it 'invalid' do
48
+ expect(@dumper.find(nil, :elf, 1)).to be nil
49
+ end
50
+ it 'parser' do
51
+ expect(@dumper.find("ELF", ':elf + 1', 3)).to eq 0x400001
52
+ end
53
+ end
54
+
55
+ describe 'parse_cmd' do
56
+ it 'normal' do
57
+ expect(HeapInfo::Dumper.parse_cmd [0x30]).to eq [0x30, 0, 8]
58
+ expect(HeapInfo::Dumper.parse_cmd [0x30, 3]).to eq [0x30, 0, 3]
59
+ expect(HeapInfo::Dumper.parse_cmd [0x30, 2, 3]).to eq [0x30, 2, 3]
60
+ end
61
+ it 'symbol' do
62
+ expect(HeapInfo::Dumper.parse_cmd [:heap]).to eq [:heap,0 , 8]
63
+ expect(HeapInfo::Dumper.parse_cmd [:heap, 10]).to eq [:heap,0 , 10]
64
+ expect(HeapInfo::Dumper.parse_cmd [:heap, 3, 10]).to eq [:heap,3 , 10]
65
+ end
66
+ it 'string' do
67
+ expect(HeapInfo::Dumper.parse_cmd ['heap']).to eq [:heap, 0, 8]
68
+ expect(HeapInfo::Dumper.parse_cmd ['heap, 10']).to eq [:heap, 0, 10]
69
+ expect(HeapInfo::Dumper.parse_cmd ['heap, 0x33, 10']).to eq [:heap, 51, 10]
70
+ expect(HeapInfo::Dumper.parse_cmd ['heap+0x15, 10']).to eq [:heap, 0x15, 10]
71
+ expect(HeapInfo::Dumper.parse_cmd ['heap + 0x15, 10']).to eq [:heap, 0x15, 10]
72
+ expect(HeapInfo::Dumper.parse_cmd ['heap + 0x15']).to eq [:heap, 0x15, 8]
73
+ end
74
+ it 'mixed' do
75
+ expect(HeapInfo::Dumper.parse_cmd ['heap+ 0x10', 10]).to eq [:heap, 0x10, 10]
76
+ expect(HeapInfo::Dumper.parse_cmd ['heap', 10]).to eq [:heap, 0, 10]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ 08048000-08049000 r-xp 00000000 ca:01 464143 /home/heapinfo/examples/uaf/uaf
2
+ 08049000-0804a000 r--p 00000000 ca:01 464143 /home/heapinfo/examples/uaf/uaf
3
+ 0804a000-0804b000 rw-p 00001000 ca:01 464143 /home/heapinfo/examples/uaf/uaf
4
+ f73d4000-f73d7000 rw-p 00000000 00:00 0
5
+ f73d7000-f73f3000 r-xp 00000000 ca:01 160460 /usr/lib32/libgcc_s.so.1
6
+ f73f3000-f73f4000 rw-p 0001b000 ca:01 160460 /usr/lib32/libgcc_s.so.1
7
+ f73f4000-f7438000 r-xp 00000000 ca:01 402366 /lib32/libm-2.19.so
8
+ f7438000-f7439000 r--p 00043000 ca:01 402366 /lib32/libm-2.19.so
9
+ f7439000-f743a000 rw-p 00044000 ca:01 402366 /lib32/libm-2.19.so
10
+ f743a000-f75df000 r-xp 00000000 ca:01 463662 /lib32/libc-2.19.so
11
+ f75df000-f75e1000 r--p 001a5000 ca:01 463662 /lib32/libc-2.19.so
12
+ f75e1000-f75e2000 rw-p 001a7000 ca:01 463662 /lib32/libc-2.19.so
13
+ f75e2000-f75e5000 rw-p 00000000 00:00 0
14
+ f75e5000-f76c1000 r-xp 00000000 ca:01 137147 /usr/lib32/libstdc++.so.6.0.19
15
+ f76c1000-f76c5000 r--p 000dc000 ca:01 137147 /usr/lib32/libstdc++.so.6.0.19
16
+ f76c5000-f76c6000 rw-p 000e0000 ca:01 137147 /usr/lib32/libstdc++.so.6.0.19
17
+ f76c6000-f76ce000 rw-p 00000000 00:00 0
18
+ f76db000-f76dd000 rw-p 00000000 00:00 0
19
+ f76dd000-f76de000 r-xp 00000000 00:00 0 [vdso]
20
+ f76de000-f76fe000 r-xp 00000000 ca:01 463655 /lib32/ld-2.19.so
21
+ f76fe000-f76ff000 r--p 0001f000 ca:01 463655 /lib32/ld-2.19.so
22
+ f76ff000-f7700000 rw-p 00020000 ca:01 463655 /lib32/ld-2.19.so
23
+ ffdd7000-ffdf8000 rw-p 00000000 00:00 0 [stack]