metasm 1.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.
- data/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- metadata +255 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# this is a rubstop-api compatible Gdb stub
|
|
8
|
+
# it can connect to a gdb server and interface with the lindebug frontend
|
|
9
|
+
# linux/x86 only
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
require 'socket'
|
|
13
|
+
require 'metasm'
|
|
14
|
+
|
|
15
|
+
class GdbRemoteString < Metasm::VirtualString
|
|
16
|
+
attr_accessor :gdbg
|
|
17
|
+
|
|
18
|
+
def initialize(gdbg, addr_start=0, length=0xffff_ffff)
|
|
19
|
+
@gdbg = gdbg
|
|
20
|
+
@pagelength = 512
|
|
21
|
+
super(addr_start, length)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def dup(addr=@addr_start, len=@length)
|
|
25
|
+
self.class.new(@gdbg, addr, len)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def rewrite_at(addr, data)
|
|
29
|
+
len = data.length
|
|
30
|
+
off = 0
|
|
31
|
+
while len > @pagelength
|
|
32
|
+
@gdbg.setmem(addr+off, data[off, @pagelength])
|
|
33
|
+
off += @pagelength
|
|
34
|
+
len -= @pagelength
|
|
35
|
+
end
|
|
36
|
+
@gdbg.setmem(addr+off, data[off, len])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_page(addr)
|
|
40
|
+
@gdbg.getmem(addr, @pagelength)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Rubstop
|
|
45
|
+
EFLAGS = {0 => 'c', 2 => 'p', 4 => 'a', 6 => 'z', 7 => 's', 9 => 'i', 10 => 'd', 11 => 'o'}
|
|
46
|
+
GDBREGS = %w[eax ecx edx ebx esp ebp esi edi eip eflags cs ss ds es fs gs] # XXX [77] = 'orig_eax'
|
|
47
|
+
# define accessors for registers
|
|
48
|
+
GDBREGS.compact.each { |reg|
|
|
49
|
+
define_method(reg) { regs_cache[reg] }
|
|
50
|
+
define_method(reg + '=') { |v| regs_cache[reg] = v ; regs_dirty }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# compute the hex checksum used in gdb protocol
|
|
54
|
+
def gdb_csum(buf)
|
|
55
|
+
'%02x' % (buf.unpack('C*').inject(0) { |cs, c| cs + c } & 0xff)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# send the buffer, waits ack
|
|
59
|
+
# return true on success
|
|
60
|
+
def gdb_send(cmd, buf='')
|
|
61
|
+
buf = cmd + buf
|
|
62
|
+
buf = '$' << buf << '#' << gdb_csum(buf)
|
|
63
|
+
log "gdb_send(#{buf[0, 32].inspect}#{'...' if buf.length > 32})" if $DEBUG
|
|
64
|
+
|
|
65
|
+
5.times {
|
|
66
|
+
@io.write buf
|
|
67
|
+
loop do
|
|
68
|
+
if not IO.select([@io], nil, nil, 1)
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
raise Errno::EPIPE if not ack = @io.read(1)
|
|
72
|
+
case ack
|
|
73
|
+
when '+'
|
|
74
|
+
return true
|
|
75
|
+
when '-'
|
|
76
|
+
log "gdb_send: ack neg" if $DEBUG
|
|
77
|
+
break
|
|
78
|
+
when nil; return
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
}
|
|
82
|
+
log "send error #{cmd.inspect} (no ack)"
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# return buf, or nil on error / csum error
|
|
87
|
+
def gdb_readresp
|
|
88
|
+
state = :nosync
|
|
89
|
+
buf = ''
|
|
90
|
+
cs = ''
|
|
91
|
+
while state != :done
|
|
92
|
+
# XXX timeout etc
|
|
93
|
+
raise Errno::EPIPE if not c = @io.read(1)
|
|
94
|
+
case state
|
|
95
|
+
when :nosync
|
|
96
|
+
if c == '$'
|
|
97
|
+
state = :data
|
|
98
|
+
end
|
|
99
|
+
when :data
|
|
100
|
+
if c == '#'
|
|
101
|
+
state = :csum1
|
|
102
|
+
else
|
|
103
|
+
buf << c
|
|
104
|
+
end
|
|
105
|
+
when :csum1
|
|
106
|
+
cs << c
|
|
107
|
+
state = :csum2
|
|
108
|
+
when :csum2
|
|
109
|
+
cs << c
|
|
110
|
+
state = :done
|
|
111
|
+
if cs.downcase != gdb_csum(buf).downcase
|
|
112
|
+
log "transmit error"
|
|
113
|
+
@io.write '-'
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
@io.write '+'
|
|
119
|
+
|
|
120
|
+
if buf =~ /^E(..)$/
|
|
121
|
+
e = $1.to_i(16)
|
|
122
|
+
log "error #{e} (#{Metasm::PTrace::ERRNO.index(e)})"
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
log "gdb_readresp: got #{buf[0, 64].inspect}#{'...' if buf.length > 64}" if $DEBUG
|
|
126
|
+
|
|
127
|
+
buf
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def gdb_msg(*a)
|
|
131
|
+
if gdb_send(*a)
|
|
132
|
+
gdb_readresp
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# rle: build the regexp that will match repetitions of a character, skipping counts leading to invalid char
|
|
137
|
+
rng = [3..(125-29)]
|
|
138
|
+
[?+, ?-, ?#, ?$].sort.each { |invalid|
|
|
139
|
+
invalid -= 29
|
|
140
|
+
rng.each_with_index { |r, i|
|
|
141
|
+
if r.include? invalid
|
|
142
|
+
replace = [r.begin..invalid-1, invalid+1..r.end]
|
|
143
|
+
replace.delete_if { |r_| r_.begin > r_.end }
|
|
144
|
+
rng[i, 1] = replace
|
|
145
|
+
end
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
repet = rng.reverse.map { |r| "\\1{#{r.begin},#{r.end}}" }.join('|')
|
|
149
|
+
RLE_RE = /(.)(#{repet})/
|
|
150
|
+
|
|
151
|
+
# rle-compress a buffer
|
|
152
|
+
# a character followed by '*' followed by 'x' is asc(x)-28 repetitions of the char
|
|
153
|
+
# eg '0* ' => '0' * (asc(' ') - 28) = '0000'
|
|
154
|
+
# for the count character, it must be 32 <= char < 126 and not be '+' '-' '#' or '$'
|
|
155
|
+
def rle(buf)
|
|
156
|
+
buf.gsub(RLE_RE) {
|
|
157
|
+
chr, len = $1, $2.length+1
|
|
158
|
+
chr + '*' + (len+28).chr
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
# decompress rle-encoded data
|
|
162
|
+
def unrle(buf) buf.gsub(/(.)\*(.)/) { $1 * ($2[0]-28) } end
|
|
163
|
+
# send an integer as a long hex packed with leading 0 stripped
|
|
164
|
+
def hexl(int) [int].pack('N').unpack('H*').first.gsub(/^0+(.)/, '\1') end
|
|
165
|
+
# send a binary buffer as a rle hex-encoded
|
|
166
|
+
def hex(buf) buf.unpack('H*').first end
|
|
167
|
+
# decode an rle hex-encoded buffer
|
|
168
|
+
def unhex(buf)
|
|
169
|
+
buf = buf[/^[a-fA-F0-9]*/]
|
|
170
|
+
buf = '0' + buf if buf.length % 1 == 1
|
|
171
|
+
[buf].pack('H*')
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# on-demand local cache of registers
|
|
175
|
+
def regs_cache
|
|
176
|
+
readregs if @regs_cache.empty?
|
|
177
|
+
@regs_cache
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# retrieve remote regs
|
|
181
|
+
def readregs
|
|
182
|
+
sync_regs
|
|
183
|
+
if buf = gdb_msg('g')
|
|
184
|
+
regs = unhex(unrle(buf))
|
|
185
|
+
if regs.length < GDBREGS.length*4
|
|
186
|
+
# retry once, was probably a response to something else
|
|
187
|
+
puts "bad regs size!" if $DEBUG
|
|
188
|
+
buf = gdb_msg('g')
|
|
189
|
+
regs = unhex(unrle(buf)) if buf
|
|
190
|
+
if not buf or regs.length < GDBREGS.length*4
|
|
191
|
+
raise "regs buffer recv is too short !"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
@regs_dirty = false
|
|
195
|
+
@regs_cache = Hash[GDBREGS.zip(regs.unpack('L*'))]
|
|
196
|
+
end
|
|
197
|
+
@curinstr = nil if @regs_cache['eip'] != @oldregs['eip']
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# mark local cache of regs as modified, need to send it before continuing execution
|
|
201
|
+
def regs_dirty
|
|
202
|
+
@regs_dirty = true
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# send the local copy of regs if dirty
|
|
206
|
+
def sync_regs
|
|
207
|
+
if not @regs_cache.empty? and @regs_dirty
|
|
208
|
+
send_regs
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# send the local copy of regs
|
|
213
|
+
def send_regs
|
|
214
|
+
return if @regs_cache.empty?
|
|
215
|
+
regs = @regs_cache.values_at(*GDBREGS)
|
|
216
|
+
@regs_dirty = false
|
|
217
|
+
gdb_msg('G', hex(regs.pack('L*')))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# read memory (small blocks prefered)
|
|
221
|
+
def getmem(addr, len)
|
|
222
|
+
return '' if len == 0
|
|
223
|
+
if mem = gdb_msg('m', hexl(addr) << ',' << hexl(len))
|
|
224
|
+
unhex(unrle(mem))
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# write memory (small blocks prefered)
|
|
229
|
+
def setmem(addr, data)
|
|
230
|
+
len = data.length
|
|
231
|
+
return if len == 0
|
|
232
|
+
raise 'writemem error' if not gdb_msg('M', hexl(addr) << ',' << hexl(len) << ':' << rle(hex(data)))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# read arbitrary blocks of memory (chunks to getmem)
|
|
236
|
+
def [](addr, len)
|
|
237
|
+
@pgm.encoded[addr, len].data rescue ''
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# write arbitrary blocks of memory (chunks to getmem)
|
|
241
|
+
def []=(addr, len, str)
|
|
242
|
+
@pgm.encoded[addr, len] = str
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def curinstr
|
|
246
|
+
@curinstr ||= mnemonic_di
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def mnemonic_di(addr = eip)
|
|
250
|
+
@pgm.encoded.ptr = addr
|
|
251
|
+
di = @pgm.cpu.decode_instruction(@pgm.encoded, addr)
|
|
252
|
+
@curinstr = di if addr == @regs_cache['eip']
|
|
253
|
+
di
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def mnemonic(addr = eip)
|
|
257
|
+
mnemonic_di(addr).instruction
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def pre_run
|
|
261
|
+
@oldregs = regs_cache.dup
|
|
262
|
+
sync_regs
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def post_run
|
|
266
|
+
@regs_cache.clear
|
|
267
|
+
@curinstr = nil
|
|
268
|
+
@mem.invalidate
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def quiet
|
|
272
|
+
@quiet = true
|
|
273
|
+
begin
|
|
274
|
+
yield
|
|
275
|
+
ensure
|
|
276
|
+
@quiet = false
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def log_stopped(msg)
|
|
281
|
+
return if @quiet ||= false
|
|
282
|
+
case msg[0]
|
|
283
|
+
when ?T
|
|
284
|
+
sig = [msg[1, 2]].pack('H*')[0]
|
|
285
|
+
misc = msg[3..-1].split(';').inject({}) { |h, s| k, v = s.split(':', 2) ; h.update k => (v || true) }
|
|
286
|
+
str = "stopped by signal #{sig}"
|
|
287
|
+
str = "thread #{[misc['thread']].pack('H*').unpack('N').first} #{str}" if misc['thread']
|
|
288
|
+
log str
|
|
289
|
+
when ?S
|
|
290
|
+
sig = [msg[1, 2]].pack('H*')[0]
|
|
291
|
+
log "stopped by signal #{sig}"
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def cont
|
|
296
|
+
pre_run
|
|
297
|
+
do_singlestep if @wantbp
|
|
298
|
+
rmsg = gdb_msg('c')
|
|
299
|
+
post_run
|
|
300
|
+
ccaddr = eip-1
|
|
301
|
+
if @breakpoints[ccaddr] and self[ccaddr, 1] == "\xcc"
|
|
302
|
+
self[ccaddr, 1] = @breakpoints.delete ccaddr
|
|
303
|
+
mem.invalidate
|
|
304
|
+
self.eip = ccaddr
|
|
305
|
+
@wantbp = ccaddr if not @singleshot.delete ccaddr
|
|
306
|
+
sync_regs
|
|
307
|
+
end
|
|
308
|
+
log_stopped rmsg
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def singlestep
|
|
312
|
+
pre_run
|
|
313
|
+
do_singlestep
|
|
314
|
+
post_run
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def do_singlestep
|
|
318
|
+
gdb_msg('s')
|
|
319
|
+
if @wantbp
|
|
320
|
+
self[@wantbp, 1] = "\xcc"
|
|
321
|
+
@wantbp = nil
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def stepover
|
|
326
|
+
i = curinstr.instruction if curinstr
|
|
327
|
+
if i and (i.opname == 'call' or (i.prefix and i.prefix[:rep]))
|
|
328
|
+
eaddr = eip + curinstr.bin_length
|
|
329
|
+
bpx eaddr, true
|
|
330
|
+
quiet { cont }
|
|
331
|
+
else
|
|
332
|
+
singlestep
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def stepout
|
|
337
|
+
stepover until curinstr and curinstr.opcode.name == 'ret'
|
|
338
|
+
singlestep
|
|
339
|
+
rescue Interrupt
|
|
340
|
+
log 'interrupted'
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def bpx(addr, singleshot=false)
|
|
344
|
+
return if @breakpoints[addr]
|
|
345
|
+
@singleshot[addr] = true if singleshot
|
|
346
|
+
@breakpoints[addr] = self[addr, 1]
|
|
347
|
+
self[addr, 1] = "\xcc"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def kill
|
|
352
|
+
gdb_send('k')
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def detach
|
|
356
|
+
# TODO clear breakpoints
|
|
357
|
+
gdb_send('D')
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
attr_accessor :pgm, :breakpoints, :singleshot, :wantbp,
|
|
361
|
+
:symbols, :symbols_len, :filemap, :oldregs, :io, :mem
|
|
362
|
+
def initialize(io)
|
|
363
|
+
case io
|
|
364
|
+
when IO; @io = io
|
|
365
|
+
when /^udp:([^:]*):(\d+)$/; @io = UDPSocket.new ; @io.connect($1, $2)
|
|
366
|
+
when /^(?:tcp:)?([^:]*):(\d+)$/; @io = TCPSocket.open($1, $2)
|
|
367
|
+
else raise "unknown target #{io.inspect}"
|
|
368
|
+
end
|
|
369
|
+
@pgm = Metasm::ExeFormat.new Metasm::Ia32.new
|
|
370
|
+
@mem = GdbRemoteString.new self
|
|
371
|
+
@pgm.encoded = Metasm::EncodedData.new @mem
|
|
372
|
+
@regs_cache = {}
|
|
373
|
+
@regs_dirty = nil
|
|
374
|
+
@oldregs = {}
|
|
375
|
+
@breakpoints = {}
|
|
376
|
+
@singleshot = {}
|
|
377
|
+
@wantbp = nil
|
|
378
|
+
@symbols = {}
|
|
379
|
+
@symbols_len = {}
|
|
380
|
+
@filemap = {}
|
|
381
|
+
|
|
382
|
+
gdb_setup
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def gdb_setup
|
|
386
|
+
#gdb_msg('q', 'Supported')
|
|
387
|
+
#gdb_msg('Hc', '-1')
|
|
388
|
+
#gdb_msg('qC')
|
|
389
|
+
if not gdb_msg('?')
|
|
390
|
+
log "nobody on the line, waiting for someone to wake up"
|
|
391
|
+
IO.select([@io], nil, nil, nil)
|
|
392
|
+
log "who's there ?"
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def set_hwbp(type, addr, len=1, set=true)
|
|
397
|
+
set = (set ? 'Z' : 'z')
|
|
398
|
+
type = { 'r' => '3', 'w' => '2', 'x' => '1', 's' => '0' }[type] || raise("invalid hwbp type #{type}")
|
|
399
|
+
gdb_msg(set, type << ',' << hexl(addr) << ',' << hexl(len))
|
|
400
|
+
true
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def unset_hwbp(type, addr, len=1)
|
|
404
|
+
set_hwbp(type, addr, len, false)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def findfilemap(s)
|
|
409
|
+
@filemap.keys.find { |k| @filemap[k][0] <= s and @filemap[k][1] > s } || '???'
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def findsymbol(k)
|
|
413
|
+
file = findfilemap(k) + '!'
|
|
414
|
+
if s = @symbols[k] ? k : @symbols.keys.find { |s_| s_ < k and s_ + @symbols_len[s_].to_i > k }
|
|
415
|
+
file + @symbols[s] + (s == k ? '' : "+#{(k-s).to_s(16)}")
|
|
416
|
+
else
|
|
417
|
+
file + ('%08x' % k)
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def loadsyms(baseaddr, name)
|
|
422
|
+
@loadedsyms ||= {}
|
|
423
|
+
return if @loadedsyms[name] or self[baseaddr, 4] != "\x7fELF"
|
|
424
|
+
@loadedsyms[name] = true
|
|
425
|
+
|
|
426
|
+
set_status " loading symbols from #{name}..."
|
|
427
|
+
e = Metasm::LoadedELF.load self[baseaddr, 0x100_0000]
|
|
428
|
+
e.load_address = baseaddr
|
|
429
|
+
begin
|
|
430
|
+
e.decode
|
|
431
|
+
#e = Metasm::ELF.decode_file name rescue return # read from disk
|
|
432
|
+
rescue
|
|
433
|
+
log "failed to load symbols from #{name}: #$!"
|
|
434
|
+
($!.backtrace - caller).each { |l| log l.chomp }
|
|
435
|
+
@filemap[baseaddr.to_s(16)] = [baseaddr, baseaddr+0x1000]
|
|
436
|
+
return
|
|
437
|
+
rescue Interrupt
|
|
438
|
+
log "interrupted"
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
if e.tag['SONAME']
|
|
442
|
+
name = e.tag['SONAME']
|
|
443
|
+
return if name and @loadedsyms[name]
|
|
444
|
+
@loadedsyms[name] = true
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
last_s = e.segments.reverse.find { |s| s.type == 'LOAD' }
|
|
448
|
+
vlen = last_s.vaddr + last_s.memsz
|
|
449
|
+
vlen -= baseaddr if e.header.type == 'EXEC'
|
|
450
|
+
@filemap[name] = [baseaddr, baseaddr + vlen]
|
|
451
|
+
|
|
452
|
+
oldsyms = @symbols.length
|
|
453
|
+
e.symbols.each { |s|
|
|
454
|
+
next if not s.name or s.shndx == 'UNDEF'
|
|
455
|
+
sname = s.name
|
|
456
|
+
sname = 'weak_'+sname if s.bind == 'WEAK'
|
|
457
|
+
sname = 'local_'+sname if s.bind == 'LOCAL'
|
|
458
|
+
v = s.value
|
|
459
|
+
v = baseaddr + v if v < baseaddr
|
|
460
|
+
@symbols[v] = sname
|
|
461
|
+
@symbols_len[v] = s.size
|
|
462
|
+
}
|
|
463
|
+
if e.header.type == 'EXEC' and e.header.entry >= baseaddr and e.header.entry < baseaddr + vlen
|
|
464
|
+
@symbols[e.header.entry] = 'entrypoint'
|
|
465
|
+
end
|
|
466
|
+
set_status nil
|
|
467
|
+
log "loaded #{@symbols.length-oldsyms} symbols from #{name} at #{'%08x' % baseaddr}"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# scan val at the beginning of each page (custom gdb msg)
|
|
471
|
+
def pageheadsearch(val)
|
|
472
|
+
resp = gdb_msg('qy', hexl(val))
|
|
473
|
+
unhex(resp).unpack('L*')
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def scansyms
|
|
477
|
+
# TODO use qSymbol or something
|
|
478
|
+
pageheadsearch("\x7fELF".unpack('L').first).each { |addr| loadsyms(addr, '%08x'%addr) }
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# use qSymbol to retrieve a symbol value (uint)
|
|
482
|
+
def request_symbol(name)
|
|
483
|
+
resp = gdb_msg('qSymbol:', hex(name))
|
|
484
|
+
if resp and a = resp.split(':')[1]
|
|
485
|
+
unhex(a).unpack('N').first
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def loadallsyms
|
|
490
|
+
# kgdb: read kernel symbols from 'module_list'
|
|
491
|
+
# too bad module_list is not in ksyms
|
|
492
|
+
if mod = request_symbol('module_list')
|
|
493
|
+
int_at = lambda { |addr, off| @mem[addr+off, 4].unpack('L').first }
|
|
494
|
+
mod_size = lambda { int_at[mod, 0] }
|
|
495
|
+
mod_next = lambda { int_at[mod, 4] }
|
|
496
|
+
mod_nsym = lambda { int_at[mod, 0x18] } # most portable. yes.
|
|
497
|
+
mod_syms = lambda { int_at[mod, 0x20] }
|
|
498
|
+
|
|
499
|
+
read_strz = lambda { |addr|
|
|
500
|
+
if i = @mem.index(?\0, addr)
|
|
501
|
+
@mem[addr...i]
|
|
502
|
+
end
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
while mod != 0
|
|
506
|
+
symtab = [[]]
|
|
507
|
+
|
|
508
|
+
@mem[mod_syms[], mod_nsym[]*8].to_str.unpack('L*').each { |i|
|
|
509
|
+
# make a list of couples
|
|
510
|
+
if symtab.last.length < 2
|
|
511
|
+
symtab.last << i
|
|
512
|
+
else
|
|
513
|
+
symtab << [i]
|
|
514
|
+
end
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
symtab.each { |v, n|
|
|
518
|
+
n = read_strz[n]
|
|
519
|
+
# ||= to keep symbol precedence order (1st match wins)
|
|
520
|
+
@symbols[v] ||= n
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
mod = mod_next[]
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def loadmap(mapfile)
|
|
529
|
+
# file fmt: addr type name eg 'c01001ba t setup_idt'
|
|
530
|
+
minaddr = maxaddr = nil
|
|
531
|
+
File.read(mapfile).each { |l|
|
|
532
|
+
addr, type, name = l.chomp.split
|
|
533
|
+
addr = addr.to_i(16)
|
|
534
|
+
minaddr = addr if not minaddr or minaddr > addr
|
|
535
|
+
maxaddr = addr if not maxaddr or maxaddr < addr
|
|
536
|
+
@symbols[addr] = name
|
|
537
|
+
}
|
|
538
|
+
if minaddr
|
|
539
|
+
@filemap[minaddr.to_s(16)] = [minaddr, maxaddr+1]
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def backtrace
|
|
544
|
+
s = findsymbol(eip)
|
|
545
|
+
if block_given?
|
|
546
|
+
yield s
|
|
547
|
+
else
|
|
548
|
+
bt = []
|
|
549
|
+
bt << s
|
|
550
|
+
end
|
|
551
|
+
fp = ebp
|
|
552
|
+
while fp >= esp and fp <= esp+0x100000
|
|
553
|
+
s = findsymbol(self[fp+4, 4].unpack('L').first)
|
|
554
|
+
if block_given?
|
|
555
|
+
yield s
|
|
556
|
+
else
|
|
557
|
+
bt << s
|
|
558
|
+
end
|
|
559
|
+
fp = self[fp, 4].unpack('L').first
|
|
560
|
+
end
|
|
561
|
+
bt
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
attr_accessor :logger
|
|
565
|
+
def log(s)
|
|
566
|
+
@logger ||= $stdout
|
|
567
|
+
@logger.puts s
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# set a temporary status info (nil for default value)
|
|
571
|
+
def set_status(s)
|
|
572
|
+
@logger ||= $stdout
|
|
573
|
+
if @logger != $stdout
|
|
574
|
+
@logger.statusline = s
|
|
575
|
+
else
|
|
576
|
+
s ||= ' '*72
|
|
577
|
+
@logger.print s + "\r"
|
|
578
|
+
@logger.flush
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def checkbp ; end
|
|
583
|
+
end
|