heapinfo 0.0.0

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.
@@ -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]