metasm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,102 @@
|
|
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
|
+
# this generates a signature file for all function in the given library
|
7
|
+
# usage: gensigs.rb some_lib.a somefile.o somelib.lib > mylib.fsigs
|
8
|
+
#
|
9
|
+
# to be used with the match_libsigs disassembler plugin
|
10
|
+
|
11
|
+
# TODO handle COFF symbols (for .lib)
|
12
|
+
|
13
|
+
require 'metasm'
|
14
|
+
|
15
|
+
include Metasm
|
16
|
+
|
17
|
+
AutoExe.register_signature("!<arch>\n", COFFArchive)
|
18
|
+
AutoExe.register_signature("\x4c\x01", COFF) # no signature, check the machine field = i386
|
19
|
+
|
20
|
+
# minimum number of raw bytes to allow in a signature
|
21
|
+
$min_sigbytes = 8
|
22
|
+
|
23
|
+
# read a binary file, print a signature for all symbols found
|
24
|
+
def create_sig(exe)
|
25
|
+
func = case exe
|
26
|
+
when COFFArchive; :create_sig_arch
|
27
|
+
when PE, COFF; :create_sig_coff
|
28
|
+
when ELF; :create_sig_elf
|
29
|
+
else raise 'unsupported file format'
|
30
|
+
end
|
31
|
+
send(func, exe) { |sym, edata|
|
32
|
+
sig = edata.data.unpack('H*').first
|
33
|
+
edata.reloc.each { |o, r|
|
34
|
+
# TODO if the reloc points to a known func (eg WinMain), keep the info
|
35
|
+
sz = r.length
|
36
|
+
sig[2*o, 2*sz] = '.' * sz * 2
|
37
|
+
}
|
38
|
+
|
39
|
+
next if sig.gsub('.', '').length < 2*$min_sigbytes
|
40
|
+
|
41
|
+
puts sym
|
42
|
+
sig.scan(/.{1,78}/) { |s| puts ' ' + s }
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# handle coff archives
|
47
|
+
def create_sig_arch(exe)
|
48
|
+
exe.members.each { |m|
|
49
|
+
next if m.name == '/' or m.name == '//'
|
50
|
+
obj = m.exe rescue next
|
51
|
+
create_sig(obj)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
# scan an elf file
|
56
|
+
def create_sig_elf(elf)
|
57
|
+
elf.symbols.each { |sym|
|
58
|
+
next if sym.type != 'FUNC' or sym.shndx == 'UNDEF'
|
59
|
+
if elf.header.type == 'REL'
|
60
|
+
next if not data = elf.sections[sym.shndx].encoded
|
61
|
+
off = sym.value
|
62
|
+
else
|
63
|
+
next if not seg = elf.segments.find { |s| s.type == 'LOAD' and sym.value >= s.vaddr and sym.value < s.vaddr+s.memsz }
|
64
|
+
next if not data = seg.encoded
|
65
|
+
off = sym.value - seg.vaddr
|
66
|
+
end
|
67
|
+
|
68
|
+
len = sym.size
|
69
|
+
if len == 0
|
70
|
+
len = data.export.find_all { |k, o| o > off and k !~ /_uuid/ }.transpose[1].to_a.min || data.length
|
71
|
+
len -= off
|
72
|
+
len = 256 if len > 256
|
73
|
+
end
|
74
|
+
|
75
|
+
yield sym.name, data[off, len]
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# scan a pe/coff file
|
80
|
+
def create_sig_coff(coff)
|
81
|
+
if coff.kind_of? PE # dll
|
82
|
+
# dll
|
83
|
+
# TODO
|
84
|
+
else
|
85
|
+
coff.symbols.to_a.compact.each { |sym|
|
86
|
+
next if sym.type != 'FUNCTION'
|
87
|
+
next if not sym.sec_nr.kind_of? Integer
|
88
|
+
data = coff.sections[sym.sec_nr-1].encoded
|
89
|
+
off = sym.value
|
90
|
+
len = data.export.find_all { |k, o| o > off and k !~ /_uuid/ }.transpose[1].to_a.min || data.length
|
91
|
+
|
92
|
+
yield sym.name, data[off, len]
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if __FILE__ == $0
|
98
|
+
$min_sigbytes = ARGV.shift.to_i if ARGV.first =~ /^\d+$/
|
99
|
+
targets = ARGV
|
100
|
+
targets = Dir['*.a'] + Dir['*.lib'] if targets.empty?
|
101
|
+
targets.each { |t| create_sig(AutoExe.decode_file(t)) }
|
102
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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
|
+
#
|
8
|
+
# this sample script fixes a bug in some GTK libs (eg debian) where at some point
|
9
|
+
# when you close a window an invalid memory dereference is done, which crashes the
|
10
|
+
# whole metasm GUI
|
11
|
+
#
|
12
|
+
# bug backtrace:
|
13
|
+
# 0f79e6173h libgobject-2.0.so.0!g_type_check_instance+23
|
14
|
+
# 0f79e3e38h libgobject-2.0.so.0!g_signal_handlers_disconnect_matched+28
|
15
|
+
# 0f70004c3h libgtk-x11-2.0.so.0!gtk_accel_label_set_accel_closure+c3
|
16
|
+
# 0f70006d3h libgtk-x11-2.0.so.0!gtk_accel_label_set_accel_widget+b3
|
17
|
+
# ...
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'metasm'
|
21
|
+
|
22
|
+
include Metasm
|
23
|
+
|
24
|
+
if not pr = OS.current.find_process(ARGV.first)
|
25
|
+
abort "cant find target"
|
26
|
+
end
|
27
|
+
|
28
|
+
dbg = pr.debugger
|
29
|
+
|
30
|
+
dbg.continue
|
31
|
+
puts "monitoring.." if $VERBOSE
|
32
|
+
dbg.wait_target
|
33
|
+
|
34
|
+
while dbg.state == :stopped
|
35
|
+
puts "target #{dbg.state} #{dbg.info}" if $VERBOSE
|
36
|
+
if di = dbg.di_at(dbg.pc) and di.to_s =~ /\[(...)\]/ and reg = $1.downcase.to_sym and regval = dbg.get_reg_value(reg) and regval > 0 and regval < 4096
|
37
|
+
bt = dbg.stacktrace(2)
|
38
|
+
calladdr = bt[1][0]-5
|
39
|
+
dbg.disassembler.disassemble_fast(calladdr)
|
40
|
+
call = dbg.di_at(calladdr)
|
41
|
+
dbg.disassembler.disassemble_fast(call.instruction.args.first.reduce) rescue nil
|
42
|
+
if di = dbg.disassembler.decoded[dbg.pc] and from = dbg.disassembler.decoded[di.block.from_normal.first] and from.block.list[-2].to_s =~ /test #{reg}, #{reg}/
|
43
|
+
puts "fix almost null deref #{di} (#{reg}=#{regval})" if $VERBOSE
|
44
|
+
dbg.set_reg_value(reg, 0)
|
45
|
+
dbg.set_reg_value(:eip, from.block.list[-2].address)
|
46
|
+
else
|
47
|
+
dbg.kill # dont infinite loop ( TODO just dont handle the exception)
|
48
|
+
end
|
49
|
+
elsif dbg.info =~ /SEGV/
|
50
|
+
puts "unhandled segfault #{di}..." if $VERBOSE
|
51
|
+
# yep, this actually works
|
52
|
+
dbg.set_reg_value(:eip, di.next_addr)
|
53
|
+
end
|
54
|
+
dbg.continue
|
55
|
+
puts "target running" if $VERBOSE
|
56
|
+
dbg.wait_target
|
57
|
+
end
|
58
|
+
|
59
|
+
puts "target terminated" if $VERBOSE
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#
|
2
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
3
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
4
|
+
#
|
5
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
6
|
+
|
7
|
+
# this file sets up the RUBYLIB environment variable for windows hosts
|
8
|
+
# the variable is set for the current user only
|
9
|
+
# the user session may need to be restarted so that the changes take effect
|
10
|
+
|
11
|
+
# the path to the framework
|
12
|
+
metasmpath = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
13
|
+
|
14
|
+
$: << metasmpath
|
15
|
+
require 'metasm'
|
16
|
+
|
17
|
+
d = Metasm::DynLdr
|
18
|
+
|
19
|
+
d.new_api_c <<EOS, 'advapi32'
|
20
|
+
__stdcall int RegCreateKeyExA(
|
21
|
+
void *key,
|
22
|
+
char *subkey,
|
23
|
+
int resvd,
|
24
|
+
char *class,
|
25
|
+
int options,
|
26
|
+
int access,
|
27
|
+
void *security,
|
28
|
+
void **keyout,
|
29
|
+
void *dispos);
|
30
|
+
|
31
|
+
__stdcall int RegQueryValueExA(
|
32
|
+
void *key,
|
33
|
+
char *value,
|
34
|
+
int resvd,
|
35
|
+
int type,
|
36
|
+
void *data,
|
37
|
+
int *datalen);
|
38
|
+
|
39
|
+
__stdcall int RegSetValueExA(
|
40
|
+
void *key,
|
41
|
+
char *value,
|
42
|
+
int resvd,
|
43
|
+
int type,
|
44
|
+
void *data,
|
45
|
+
int datalen);
|
46
|
+
|
47
|
+
__stdcall int RegCloseKey(
|
48
|
+
void *key);
|
49
|
+
|
50
|
+
#define KEY_ALL_ACCESS 0xf003f
|
51
|
+
#define KEY_CURRENT_USER 0x80000001
|
52
|
+
#define REG_EXPAND_SZ 2
|
53
|
+
EOS
|
54
|
+
|
55
|
+
key = [0].pack('L')
|
56
|
+
ret = d.regcreatekeyexa(d::KEY_CURRENT_USER, 'Environment', 0, 0, 0, d::KEY_ALL_ACCESS, 0, key, 0)
|
57
|
+
key = key.unpack('L').first
|
58
|
+
abort 'cannot open env key' if ret != 0
|
59
|
+
|
60
|
+
buf = 0.chr*4096
|
61
|
+
buflen = [buf.length].pack('L')
|
62
|
+
ret = d.regqueryvalueexa(key, 'RUBYLIB', 0, 0, buf, buflen)
|
63
|
+
data = ret == 0 ? buf[0, buflen.unpack('L').first-1] : ''
|
64
|
+
|
65
|
+
if data.split(';').include? metasmpath
|
66
|
+
puts 'already registered'
|
67
|
+
else
|
68
|
+
data << ';' if not data.empty?
|
69
|
+
data << metasmpath << 0
|
70
|
+
ret = d.regsetvalueexa(key, 'RUBYLIB', 0, d::REG_EXPAND_SZ, data, data.length)
|
71
|
+
if ret == 0
|
72
|
+
puts "success - restart your session"
|
73
|
+
else
|
74
|
+
puts "failed :( - #{Metasm::WinAPI.last_error_msg(ret)}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
d.regclosekey(key)
|
data/samples/lindebug.rb
ADDED
@@ -0,0 +1,924 @@
|
|
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 linux/x86 debugger with a console interface
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'metasm'
|
11
|
+
|
12
|
+
module Ansi
|
13
|
+
CursHome = "\e[H".freeze
|
14
|
+
ClearLineAfter = "\e[0K"
|
15
|
+
ClearLineBefore = "\e[1K"
|
16
|
+
ClearLine = "\e[2K"
|
17
|
+
ClearScreen = "\e[2J"
|
18
|
+
def self.set_cursor_pos(y=1,x=1) "\e[#{y};#{x}H" end
|
19
|
+
Reset = "\e[m"
|
20
|
+
Colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :aoeu, :reset]
|
21
|
+
def self.color(*args)
|
22
|
+
fg = true
|
23
|
+
"\e[" << args.map { |a|
|
24
|
+
case a
|
25
|
+
when :bold; 2
|
26
|
+
when :negative; 7
|
27
|
+
when :normal; 22
|
28
|
+
when :positive; 27
|
29
|
+
else
|
30
|
+
if col = Colors.index(a)
|
31
|
+
add = (fg ? 30 : 40)
|
32
|
+
fg = false
|
33
|
+
col+add
|
34
|
+
end
|
35
|
+
end
|
36
|
+
}.compact.join(';') << 'm'
|
37
|
+
end
|
38
|
+
def self.hline(len) "\e(0"<<'q'*len<<"\e(B" end
|
39
|
+
|
40
|
+
TIOCGWINSZ = 0x5413
|
41
|
+
TCGETS = 0x5401
|
42
|
+
TCSETS = 0x5402
|
43
|
+
CANON = 2
|
44
|
+
ECHO = 8
|
45
|
+
def self.get_terminal_size
|
46
|
+
s = ''.ljust(8)
|
47
|
+
$stdin.ioctl(TIOCGWINSZ, s) >= 0 ? s.unpack('SS') : [80, 25]
|
48
|
+
end
|
49
|
+
def self.set_term_canon(bool)
|
50
|
+
tty = ''.ljust(256)
|
51
|
+
$stdin.ioctl(TCGETS, tty)
|
52
|
+
if bool
|
53
|
+
tty[12] &= ~(ECHO|CANON)
|
54
|
+
else
|
55
|
+
tty[12] |= ECHO|CANON
|
56
|
+
end
|
57
|
+
$stdin.ioctl(TCSETS, tty)
|
58
|
+
end
|
59
|
+
|
60
|
+
ESC_SEQ = {'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left,
|
61
|
+
'1~' => :home, '2~' => :inser, '3~' => :suppr, '4~' => :end,
|
62
|
+
'5~' => :pgup, '6~' => :pgdown,
|
63
|
+
'P' => :f1, 'Q' => :f2, 'R' => :f3, 'S' => :f4,
|
64
|
+
'15~' => :f5, '17~' => :f6, '18~' => :f7, '19~' => :f8,
|
65
|
+
'20~' => :f9, '21~' => :f10, '23~' => :f11, '24~' => :f12,
|
66
|
+
'[A' => :f1, '[B' => :f2, '[C' => :f3, '[D' => :f4, '[E' => :f5,
|
67
|
+
'H' => :home, 'F' => :end,
|
68
|
+
}
|
69
|
+
def self.getkey
|
70
|
+
c = $stdin.getc
|
71
|
+
raise 'nonblocking $stdin?' if not c
|
72
|
+
return c if c != ?\e
|
73
|
+
c = $stdin.getc
|
74
|
+
if c != ?[ and c != ?O
|
75
|
+
$stdin.ungetc c
|
76
|
+
return ?\e
|
77
|
+
end
|
78
|
+
seq = ''
|
79
|
+
loop do
|
80
|
+
c = $stdin.getc
|
81
|
+
seq << c
|
82
|
+
case c; when ?a..?z, ?A..?Z, ?~; break end
|
83
|
+
end
|
84
|
+
ESC_SEQ[seq] || seq
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Indirect < Metasm::ExpressionType
|
89
|
+
attr_accessor :ptr, :sz
|
90
|
+
UNPACK_STR = {1 => 'C', 2 => 'S', 4 => 'L'}
|
91
|
+
def initialize(ptr, sz) @ptr, @sz = ptr, sz end
|
92
|
+
def bind(bd)
|
93
|
+
raw = bd['tracer_memory'][@ptr.bind(bd).reduce, @sz]
|
94
|
+
Metasm::Expression[raw.unpack(UNPACK_STR[@sz]).first]
|
95
|
+
end
|
96
|
+
def externals ; @ptr.externals end
|
97
|
+
end
|
98
|
+
|
99
|
+
class ExprParser < Metasm::Expression
|
100
|
+
def self.parse_intfloat(lex, tok)
|
101
|
+
case tok.raw
|
102
|
+
when 'byte', 'word', 'dword'
|
103
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
104
|
+
nil while ntok = lex.readtok and ntok.type == :space if ntok and ntok.raw == 'ptr'
|
105
|
+
if ntok and ntok.raw == '['
|
106
|
+
tok.value = Indirect.new(parse(lex), {'byte' => 1, 'word' => 2, 'dword' => 4}[tok.raw])
|
107
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
108
|
+
nil while ntok = lex.readtok and ntok.type == :space if ntok and ntok.raw == ']'
|
109
|
+
lex.unreadtok ntok
|
110
|
+
end
|
111
|
+
else super(lex, tok)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
def self.parse_value(lex)
|
115
|
+
nil while tok = lex.readtok and tok.type == :space
|
116
|
+
lex.unreadtok tok
|
117
|
+
if tok and tok.type == :punct and tok.raw == '['
|
118
|
+
tt = tok.dup
|
119
|
+
tt.type = :string
|
120
|
+
tt.raw = 'dword'
|
121
|
+
lex.unreadtok tt
|
122
|
+
end
|
123
|
+
super(lex)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class LinDebug
|
128
|
+
attr_accessor :win_data_height, :win_code_height, :win_prpt_height
|
129
|
+
def init_screen
|
130
|
+
Ansi.set_term_canon(true)
|
131
|
+
@win_data_height = 20
|
132
|
+
@win_code_height = 20
|
133
|
+
resize
|
134
|
+
end
|
135
|
+
|
136
|
+
def fini_screen
|
137
|
+
Ansi.set_term_canon(false)
|
138
|
+
$stdout.write Ansi.color(:normal, :reset)
|
139
|
+
$stdout.flush
|
140
|
+
end
|
141
|
+
|
142
|
+
def win_data_start; 2 end
|
143
|
+
def win_code_start; win_data_start+win_data_height end
|
144
|
+
def win_prpt_start; win_code_start+win_code_height end
|
145
|
+
|
146
|
+
Color = {:changed => Ansi.color(:cyan, :bold), :border => Ansi.color(:green),
|
147
|
+
:normal => Ansi.color(:white, :black, :normal), :hilight => Ansi.color(:blue, :white, :normal),
|
148
|
+
:status => Ansi.color(:black, :cyan)}
|
149
|
+
|
150
|
+
# yields but keep from reentring (return defretval in this case)
|
151
|
+
def once(name, defretval=nil)
|
152
|
+
@once ||= {}
|
153
|
+
if not @once[name]
|
154
|
+
@once[name] = true
|
155
|
+
begin
|
156
|
+
defretval = yield
|
157
|
+
ensure
|
158
|
+
@once[name] = false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
defretval
|
162
|
+
end
|
163
|
+
|
164
|
+
attr_accessor :dataptr, :codeptr, :rs, :promptlog, :command
|
165
|
+
def initialize(rs)
|
166
|
+
@rs = rs
|
167
|
+
@rs.logger = self
|
168
|
+
@datafmt = 'db'
|
169
|
+
@watch = nil
|
170
|
+
|
171
|
+
@prompthistlen = 20
|
172
|
+
@prompthistory = []
|
173
|
+
@promptloglen = 200
|
174
|
+
@promptlog = []
|
175
|
+
@promptbuf = ''
|
176
|
+
@promptpos = 0
|
177
|
+
@log_off = 0
|
178
|
+
@console_width = 80
|
179
|
+
|
180
|
+
@running = false
|
181
|
+
@focus = :prompt
|
182
|
+
@command = {}
|
183
|
+
load_commands
|
184
|
+
trap('WINCH') { resize }
|
185
|
+
end
|
186
|
+
|
187
|
+
def init_rs
|
188
|
+
@codeptr = @dataptr = @rs.regs_cache['eip'] # avoid initial faults
|
189
|
+
end
|
190
|
+
|
191
|
+
def main_loop
|
192
|
+
begin
|
193
|
+
begin
|
194
|
+
init_screen
|
195
|
+
init_rs
|
196
|
+
main_loop_inner
|
197
|
+
rescue Errno::ESRCH
|
198
|
+
log "target does not exist anymore"
|
199
|
+
ensure
|
200
|
+
fini_screen
|
201
|
+
$stdout.print Ansi.set_cursor_pos(@console_height, 1)
|
202
|
+
end
|
203
|
+
rescue
|
204
|
+
$stdout.puts $!, $!.backtrace
|
205
|
+
end
|
206
|
+
$stdout.puts @promptlog.last
|
207
|
+
end
|
208
|
+
|
209
|
+
# optimize string to display to stdout
|
210
|
+
# currently only skips unchanged lines
|
211
|
+
# could also match end of lines (spaces), but would need to check for color codes etc
|
212
|
+
def optimize_screen(buf)
|
213
|
+
end
|
214
|
+
|
215
|
+
# display the text buffer screenlines to the screen, leaves the cursor at (cursx, cursy), converts cursor pos from 0-base to 1-base
|
216
|
+
# screenlines is a big text buffer with 1 line per tobeshown screen line (e.g. no Ansi cursor pos)
|
217
|
+
# screenlines must be screen wide
|
218
|
+
def display_screen(screenlines, cursx, cursy)
|
219
|
+
|
220
|
+
@oldscreenbuf ||= []
|
221
|
+
lines = screenlines.to_a
|
222
|
+
oldlines = @oldscreenbuf
|
223
|
+
@oldscreenbuf = lines
|
224
|
+
screenlines = lines.zip(oldlines).map { |l, ol| l == ol ? "\n" : l }.join
|
225
|
+
|
226
|
+
while screenlines[-1] == ?\n
|
227
|
+
screenlines.chop!
|
228
|
+
end
|
229
|
+
starty = 1
|
230
|
+
while screenlines[0] == ?\n
|
231
|
+
screenlines = screenlines[1..-1]
|
232
|
+
starty += 1
|
233
|
+
end
|
234
|
+
|
235
|
+
$stdout.write Ansi.set_cursor_pos(starty, 1) + screenlines + Ansi.set_cursor_pos(cursy+1, cursx+1)
|
236
|
+
|
237
|
+
$stdout.flush
|
238
|
+
end
|
239
|
+
|
240
|
+
def update
|
241
|
+
return if not @running
|
242
|
+
display_screen updateregs + updatedata + updatecode + updateprompt, @promptpos+1, @console_height-2
|
243
|
+
end
|
244
|
+
|
245
|
+
def updateregs
|
246
|
+
once(:updateregs, "\n\n") { _updateregs }
|
247
|
+
end
|
248
|
+
|
249
|
+
def _updateregs
|
250
|
+
text = ''
|
251
|
+
text << ' '
|
252
|
+
x = 1
|
253
|
+
%w[eax ebx ecx edx eip].each { |r|
|
254
|
+
text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r]
|
255
|
+
text << r << ?=
|
256
|
+
text << ('%08X' % @rs.regs_cache[r])
|
257
|
+
text << Color[:normal] if @rs.regs_cache[r] != @rs.oldregs[r]
|
258
|
+
text << ' '
|
259
|
+
x += r.length + 11
|
260
|
+
}
|
261
|
+
text << (' '*([@console_width-x, 0].max)) << "\n" << ' '
|
262
|
+
x = 1
|
263
|
+
%w[esi edi ebp esp].each { |r|
|
264
|
+
text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r]
|
265
|
+
text << r << ?=
|
266
|
+
text << ('%08X' % @rs.regs_cache[r])
|
267
|
+
text << Color[:normal] if @rs.regs_cache[r] != @rs.oldregs[r]
|
268
|
+
text << ' '
|
269
|
+
x += r.length + 11
|
270
|
+
}
|
271
|
+
Rubstop::EFLAGS.sort.each { |off, flag|
|
272
|
+
val = @rs.regs_cache['eflags'] & (1<<off)
|
273
|
+
flag = flag.upcase if val != 0
|
274
|
+
if val != @rs.oldregs['eflags'] & (1 << off)
|
275
|
+
text << Color[:changed]
|
276
|
+
text << flag
|
277
|
+
text << Color[:normal]
|
278
|
+
else
|
279
|
+
text << flag
|
280
|
+
end
|
281
|
+
text << ' '
|
282
|
+
x += 2
|
283
|
+
}
|
284
|
+
text << (' '*([@console_width-x, 0].max)) << "\n"
|
285
|
+
end
|
286
|
+
|
287
|
+
def updatecode
|
288
|
+
once(:updatecode, "...\n"*@win_code_height) { _updatecode }
|
289
|
+
end
|
290
|
+
|
291
|
+
def _updatecode
|
292
|
+
if @codeptr
|
293
|
+
addr = @codeptr
|
294
|
+
elsif @rs.oldregs['eip'] and @rs.oldregs['eip'] < @rs.regs_cache['eip'] and @rs.oldregs['eip'] + 8 >= @rs.regs_cache['eip']
|
295
|
+
addr = @rs.oldregs['eip']
|
296
|
+
else
|
297
|
+
addr = @rs.regs_cache['eip']
|
298
|
+
end
|
299
|
+
@codeptr = addr
|
300
|
+
|
301
|
+
if @rs.findfilemap(addr) == '???'
|
302
|
+
base = addr & 0xffff_f000
|
303
|
+
@noelfsig ||= {} # cache elfmagic notfound
|
304
|
+
if not @noelfsig[base] and base < 0xc000_0000
|
305
|
+
self.statusline = " scanning for elf header at #{'%08X' % base}"
|
306
|
+
128.times {
|
307
|
+
@statusline = " scanning for elf header at #{'%08X' % base}"
|
308
|
+
if not @noelfsig[base] and @rs[base, 4] == Metasm::ELF::MAGIC
|
309
|
+
@rs.loadsyms(base, base.to_s(16))
|
310
|
+
break
|
311
|
+
else
|
312
|
+
@noelfsig[base] = true # XXX an elf may be mmaped here later..
|
313
|
+
end
|
314
|
+
base -= 0x1000
|
315
|
+
break if base < 0
|
316
|
+
}
|
317
|
+
self.statusline = nil
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
text = ''
|
322
|
+
text << Color[:border]
|
323
|
+
title = @rs.findsymbol(addr)
|
324
|
+
pre = [@console_width-100, 6].max
|
325
|
+
post = @console_width - (pre + title.length + 2)
|
326
|
+
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
|
327
|
+
|
328
|
+
cnt = @win_code_height
|
329
|
+
while (cnt -= 1) > 0
|
330
|
+
if @rs.symbols[addr]
|
331
|
+
text << (' ' << @rs.symbols[addr] << ?:) << Ansi::ClearLineAfter << "\n"
|
332
|
+
break if (cnt -= 1) <= 0
|
333
|
+
end
|
334
|
+
text << Color[:hilight] if addr == @rs.regs_cache['eip']
|
335
|
+
text << ('%04X' % @rs.regs_cache['cs']) << ':'
|
336
|
+
text << ('%08X' % addr)
|
337
|
+
di = @rs.mnemonic_di(addr)
|
338
|
+
di = nil if di and addr < @rs.regs_cache['eip'] and addr+di.bin_length > @rs.regs_cache['eip']
|
339
|
+
len = (di ? di.bin_length : 1)
|
340
|
+
text << ' '
|
341
|
+
text << @rs[addr, [len, 10].min].unpack('C*').map { |c| '%02X' % c }.join.ljust(22)
|
342
|
+
if di
|
343
|
+
text <<
|
344
|
+
if addr == @rs.regs_cache['eip']
|
345
|
+
"*#{di.instruction}".ljust([@console_width-37, 0].max)
|
346
|
+
else
|
347
|
+
" #{di.instruction}" << Ansi::ClearLineAfter
|
348
|
+
end
|
349
|
+
else
|
350
|
+
text << ' <unk>' << Ansi::ClearLineAfter
|
351
|
+
end
|
352
|
+
text << Color[:normal] if addr == @rs.regs_cache['eip']
|
353
|
+
addr += len
|
354
|
+
text << "\n"
|
355
|
+
end
|
356
|
+
text
|
357
|
+
end
|
358
|
+
|
359
|
+
def updatedata
|
360
|
+
once(:updatedata, "...\n"*@win_data_height) { _updatedata }
|
361
|
+
end
|
362
|
+
|
363
|
+
def _updatedata
|
364
|
+
@dataptr &= 0xffff_ffff
|
365
|
+
addr = @dataptr
|
366
|
+
|
367
|
+
text = ''
|
368
|
+
text << Color[:border]
|
369
|
+
title = @rs.findsymbol(addr)
|
370
|
+
pre = [@console_width-100, 6].max
|
371
|
+
post = [@console_width - (pre + title.length + 2), 0].max
|
372
|
+
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
|
373
|
+
|
374
|
+
cnt = @win_data_height
|
375
|
+
while (cnt -= 1) > 0
|
376
|
+
raw = @rs[addr, 16].to_s
|
377
|
+
text << ('%04X' % @rs.regs_cache['ds']) << ':' << ('%08X' % addr) << ' '
|
378
|
+
case @datafmt
|
379
|
+
when 'db'; text << raw[0,8].unpack('C*').map { |c| '%02x ' % c }.join << ' ' <<
|
380
|
+
raw[8,8].to_s.unpack('C*').map { |c| '%02x ' % c }.join
|
381
|
+
when 'dw'; text << raw.unpack('S*').map { |c| '%04x ' % c }.join
|
382
|
+
when 'dd'; text << raw.unpack('L*').map { |c| '%08x ' % c }.join
|
383
|
+
end
|
384
|
+
text << ' ' << raw.unpack('C*').map { |c| (0x20..0x7e).include?(c) ? c : ?. }.pack('C*')
|
385
|
+
text << Ansi::ClearLineAfter << "\n"
|
386
|
+
addr += 16
|
387
|
+
end
|
388
|
+
text
|
389
|
+
end
|
390
|
+
|
391
|
+
def updateprompt
|
392
|
+
once(:updateprompt, "\n"*@win_prpt_height) { _updateprompt }
|
393
|
+
end
|
394
|
+
|
395
|
+
def _updateprompt
|
396
|
+
text = ''
|
397
|
+
text << Color[:border] << Ansi.hline(@console_width) << Color[:normal] << "\n"
|
398
|
+
|
399
|
+
@log_off = @promptlog.length - 2 if @log_off >= @promptlog.length
|
400
|
+
@log_off = 0 if @log_off < 0
|
401
|
+
len = @win_prpt_height - 2
|
402
|
+
len.times { |i|
|
403
|
+
i += @promptlog.length - @log_off - len
|
404
|
+
text << ((@promptlog[i] if i >= 0) || '')
|
405
|
+
text << Ansi::ClearLineAfter << "\n"
|
406
|
+
}
|
407
|
+
text << ':' << @promptbuf << Ansi::ClearLineAfter << "\n"
|
408
|
+
text << Color[:status] << statusline.chomp.ljust(@console_width) << Color[:normal]
|
409
|
+
end
|
410
|
+
|
411
|
+
def statusline
|
412
|
+
@statusline ||= ' Enter a command (help for help)'
|
413
|
+
end
|
414
|
+
def statusline=(s)
|
415
|
+
@statusline = s
|
416
|
+
update
|
417
|
+
end
|
418
|
+
|
419
|
+
def resize
|
420
|
+
@console_height, @console_width = Ansi.get_terminal_size
|
421
|
+
@win_data_height = 1 if @win_data_height < 1
|
422
|
+
@win_code_height = 1 if @win_code_height < 1
|
423
|
+
if @win_data_height + @win_code_height + 5 > @console_height
|
424
|
+
@win_data_height = @console_height/2 - 4
|
425
|
+
@win_code_height = @console_height/2 - 4
|
426
|
+
end
|
427
|
+
@win_prpt_height = @console_height-(@win_data_height+@win_code_height+2) - 1
|
428
|
+
@oldscreenbuf = []
|
429
|
+
update
|
430
|
+
end
|
431
|
+
|
432
|
+
def log(*strs)
|
433
|
+
strs.each { |str|
|
434
|
+
raise str.inspect if not str.kind_of? ::String
|
435
|
+
str = str.chomp
|
436
|
+
if str.length > @console_width
|
437
|
+
# word wrap
|
438
|
+
str.scan(/.{0,#@console_width}/) { |str_| log str_ }
|
439
|
+
return
|
440
|
+
end
|
441
|
+
@promptlog << str
|
442
|
+
@promptlog.shift if @promptlog.length > @promptloglen
|
443
|
+
}
|
444
|
+
end
|
445
|
+
|
446
|
+
def puts(*s)
|
447
|
+
s.each { |s_| log s_.to_s }
|
448
|
+
super(*s) if not @running
|
449
|
+
update rescue super(*s)
|
450
|
+
end
|
451
|
+
|
452
|
+
def mem_binding(expr)
|
453
|
+
b = @rs.regs_cache.dup
|
454
|
+
ext = expr.externals
|
455
|
+
(ext - @rs.regs_cache.keys).each { |ex|
|
456
|
+
if not s = @rs.symbols.index(ex)
|
457
|
+
near = @rs.symbols.values.grep(/#{ex}/i)
|
458
|
+
if near.length > 1
|
459
|
+
log "#{ex.inspect} is ambiguous: #{near.inspect}"
|
460
|
+
return {}
|
461
|
+
elsif near.empty?
|
462
|
+
log "unknown value #{ex.inspect}"
|
463
|
+
return {}
|
464
|
+
else
|
465
|
+
log "using #{near.first.inspect} for #{ex.inspect}"
|
466
|
+
s = @rs.symbols.index(near.first)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
b[ex] = s
|
470
|
+
}
|
471
|
+
b['tracer_memory'] = @rs
|
472
|
+
b
|
473
|
+
end
|
474
|
+
|
475
|
+
def exec_prompt
|
476
|
+
@log_off = 0
|
477
|
+
log ':'+@promptbuf
|
478
|
+
return if @promptbuf == ''
|
479
|
+
lex = Metasm::Preprocessor.new.feed @promptbuf
|
480
|
+
@prompthistory << @promptbuf
|
481
|
+
@prompthistory.shift if @prompthistory.length > @prompthistlen
|
482
|
+
@promptbuf = ''
|
483
|
+
@promptpos = @promptbuf.length
|
484
|
+
argint = lambda {
|
485
|
+
begin
|
486
|
+
raise if not e = ExprParser.parse(lex)
|
487
|
+
rescue
|
488
|
+
log 'syntax error'
|
489
|
+
return
|
490
|
+
end
|
491
|
+
e = e.bind(mem_binding(e)).reduce
|
492
|
+
if e.kind_of? Integer; e
|
493
|
+
else log "could not resolve #{e.inspect}" ; nil
|
494
|
+
end
|
495
|
+
}
|
496
|
+
|
497
|
+
cmd = lex.readtok
|
498
|
+
cmd = cmd.raw if cmd
|
499
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
500
|
+
lex.unreadtok ntok
|
501
|
+
if @command.has_key? cmd
|
502
|
+
@command[cmd].call(lex, argint)
|
503
|
+
else
|
504
|
+
if cmd and (poss = @command.keys.find_all { |c| c[0, cmd.length] == cmd }).length == 1
|
505
|
+
@command[poss.first].call(lex, argint)
|
506
|
+
else
|
507
|
+
log 'unknown command'
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def updatecodeptr
|
513
|
+
@codeptr ||= @rs.regs_cache['eip']
|
514
|
+
if @codeptr > @rs.regs_cache['eip'] or @codeptr < @rs.regs_cache['eip'] - 6*@win_code_height
|
515
|
+
@codeptr = @rs.regs_cache['eip']
|
516
|
+
elsif @codeptr != @rs.regs_cache['eip']
|
517
|
+
addr = @codeptr
|
518
|
+
addrs = []
|
519
|
+
while addr < @rs.regs_cache['eip']
|
520
|
+
addrs << addr
|
521
|
+
o = ((di = @rs.mnemonic_di(addr)) ? di.bin_length : 0)
|
522
|
+
addr += ((o == 0) ? 1 : o)
|
523
|
+
end
|
524
|
+
if addrs.length > @win_code_height-4
|
525
|
+
@codeptr = addrs[-(@win_code_height-4)]
|
526
|
+
end
|
527
|
+
end
|
528
|
+
updatedataptr
|
529
|
+
end
|
530
|
+
|
531
|
+
def updatedataptr
|
532
|
+
@dataptr = @watch.bind(mem_binding(@watch)).reduce if @watch
|
533
|
+
end
|
534
|
+
|
535
|
+
def singlestep
|
536
|
+
self.statusline = ' target singlestepping...'
|
537
|
+
@rs.singlestep
|
538
|
+
updatecodeptr
|
539
|
+
@statusline = nil
|
540
|
+
end
|
541
|
+
def stepover
|
542
|
+
self.statusline = ' target running...'
|
543
|
+
@rs.stepover
|
544
|
+
updatecodeptr
|
545
|
+
@statusline = nil
|
546
|
+
end
|
547
|
+
def cont(*a)
|
548
|
+
self.statusline = ' target running...'
|
549
|
+
@rs.cont(*a)
|
550
|
+
updatecodeptr
|
551
|
+
@statusline = nil
|
552
|
+
end
|
553
|
+
def stepout
|
554
|
+
self.statusline = ' target running...'
|
555
|
+
@rs.stepout
|
556
|
+
updatecodeptr
|
557
|
+
@statusline = nil
|
558
|
+
end
|
559
|
+
def syscall
|
560
|
+
self.statusline = ' target running to next syscall...'
|
561
|
+
@rs.syscall
|
562
|
+
updatecodeptr
|
563
|
+
@statusline = nil
|
564
|
+
end
|
565
|
+
|
566
|
+
def main_loop_inner
|
567
|
+
@prompthistory = ['']
|
568
|
+
@histptr = nil
|
569
|
+
@running = true
|
570
|
+
update
|
571
|
+
while @running
|
572
|
+
if not IO.select [$stdin], nil, nil, 0
|
573
|
+
begin
|
574
|
+
update
|
575
|
+
rescue Errno::ESRCH
|
576
|
+
break
|
577
|
+
end
|
578
|
+
end
|
579
|
+
break if handle_keypress(Ansi.getkey)
|
580
|
+
end
|
581
|
+
@rs.checkbp
|
582
|
+
end
|
583
|
+
|
584
|
+
def handle_keypress(k)
|
585
|
+
case k
|
586
|
+
when 4; log 'exiting'; return true # eof
|
587
|
+
when ?\e; focus = :prompt
|
588
|
+
when :f5; cont
|
589
|
+
when :f6
|
590
|
+
syscall
|
591
|
+
log @rs.syscallnr.index(@rs.regs_cache['orig_eax']) || @rs.regs_cache['orig_eax'].to_s
|
592
|
+
when :f10; stepover
|
593
|
+
when :f11; singlestep
|
594
|
+
when :f12; stepout
|
595
|
+
when :up
|
596
|
+
case @focus
|
597
|
+
when :prompt
|
598
|
+
if not @histptr
|
599
|
+
@prompthistory << @promptbuf
|
600
|
+
@histptr = 2
|
601
|
+
else
|
602
|
+
@histptr += 1
|
603
|
+
@histptr = 1 if @histptr > @prompthistory.length
|
604
|
+
end
|
605
|
+
@promptbuf = @prompthistory[-@histptr].dup
|
606
|
+
@promptpos = @promptbuf.length
|
607
|
+
when :data
|
608
|
+
@dataptr -= 16
|
609
|
+
when :code
|
610
|
+
@codeptr ||= @rs.regs_cache['eip']
|
611
|
+
@codeptr -= (1..10).find { |off|
|
612
|
+
di = @rs.mnemonic_di(@codeptr-off)
|
613
|
+
di.bin_length == off if di
|
614
|
+
} || 10
|
615
|
+
end
|
616
|
+
when :down
|
617
|
+
case @focus
|
618
|
+
when :prompt
|
619
|
+
if not @histptr
|
620
|
+
@prompthistory << @promptbuf
|
621
|
+
@histptr = @prompthistory.length
|
622
|
+
else
|
623
|
+
@histptr -= 1
|
624
|
+
@histptr = @prompthistory.length if @histptr < 1
|
625
|
+
end
|
626
|
+
@promptbuf = @prompthistory[-@histptr].dup
|
627
|
+
@promptpos = @promptbuf.length
|
628
|
+
when :data
|
629
|
+
@dataptr += 16
|
630
|
+
when :code
|
631
|
+
@codeptr ||= @rs.regs_cache['eip']
|
632
|
+
di = @rs.mnemonic_di(@codeptr)
|
633
|
+
@codeptr += (di ? (di.bin_length || 1) : 1)
|
634
|
+
end
|
635
|
+
when :left; @promptpos -= 1 if @promptpos > 0
|
636
|
+
when :right; @promptpos += 1 if @promptpos < @promptbuf.length
|
637
|
+
when :home; @promptpos = 0
|
638
|
+
when :end; @promptpos = @promptbuf.length
|
639
|
+
when :backspace, 0x7f; @promptbuf[@promptpos-=1, 1] = '' if @promptpos > 0
|
640
|
+
when :suppr; @promptbuf[@promptpos, 1] = '' if @promptpos < @promptbuf.length
|
641
|
+
when :pgup
|
642
|
+
case @focus
|
643
|
+
when :prompt; @log_off += @win_prpt_height-3
|
644
|
+
when :data; @dataptr -= 16*(@win_data_height-1)
|
645
|
+
when :code
|
646
|
+
@codeptr ||= @rs.regs_cache['eip']
|
647
|
+
(@win_code_height-1).times {
|
648
|
+
@codeptr -= (1..10).find { |off|
|
649
|
+
di = @rs.mnemonic_di(@codeptr-off)
|
650
|
+
di.bin_length == off if di
|
651
|
+
} || 10
|
652
|
+
}
|
653
|
+
end
|
654
|
+
when :pgdown
|
655
|
+
case @focus
|
656
|
+
when :prompt; @log_off -= @win_prpt_height-3
|
657
|
+
when :data; @dataptr += 16*(@win_data_height-1)
|
658
|
+
when :code
|
659
|
+
@codeptr ||= @rs.regs_cache['eip']
|
660
|
+
(@win_code_height-1).times { @codeptr += ((o = @rs.mnemonic_di(@codeptr)) ? [o.bin_length, 1].max : 1) }
|
661
|
+
end
|
662
|
+
when ?\t
|
663
|
+
if not @promptbuf[0, @promptpos].include? ' '
|
664
|
+
poss = @command.keys.find_all { |c| c[0, @promptpos] == @promptbuf[0, @promptpos] }
|
665
|
+
if poss.length > 1
|
666
|
+
log poss.sort.join(' ')
|
667
|
+
elsif poss.length == 1
|
668
|
+
@promptbuf[0, @promptpos] = poss.first + ' '
|
669
|
+
@promptpos = poss.first.length+1
|
670
|
+
end
|
671
|
+
end
|
672
|
+
when ?\n
|
673
|
+
@histptr = nil
|
674
|
+
begin
|
675
|
+
exec_prompt
|
676
|
+
rescue Exception
|
677
|
+
log "error: #$!", *$!.backtrace
|
678
|
+
end
|
679
|
+
when 0x20..0x7e
|
680
|
+
@promptbuf[@promptpos, 0] = k.chr
|
681
|
+
@promptpos += 1
|
682
|
+
else log "unknown key pressed #{k.inspect}"
|
683
|
+
end
|
684
|
+
nil
|
685
|
+
end
|
686
|
+
|
687
|
+
def load_commands
|
688
|
+
ntok = nil
|
689
|
+
@command['kill'] = lambda { |lex, int|
|
690
|
+
@rs.kill
|
691
|
+
@running = false
|
692
|
+
log 'killed'
|
693
|
+
}
|
694
|
+
@command['quit'] = @command['detach'] = @command['exit'] = lambda { |lex, int|
|
695
|
+
@rs.detach
|
696
|
+
@running = false
|
697
|
+
}
|
698
|
+
@command['closeui'] = lambda { |lex, int|
|
699
|
+
@rs.logger = nil
|
700
|
+
@running = false
|
701
|
+
}
|
702
|
+
@command['bpx'] = lambda { |lex, int|
|
703
|
+
addr = int[]
|
704
|
+
@rs.bpx addr
|
705
|
+
}
|
706
|
+
@command['bphw'] = lambda { |lex, int|
|
707
|
+
type = lex.readtok.raw
|
708
|
+
addr = int[]
|
709
|
+
@rs.set_hwbp type, addr
|
710
|
+
}
|
711
|
+
@command['bl'] = lambda { |lex, int|
|
712
|
+
log "bpx at #{@rs.findsymbol(@rs.wantbp)}" if @rs.wantbp.kind_of? ::Integer
|
713
|
+
@rs.breakpoints.sort.each { |addr, oct|
|
714
|
+
log "bpx at #{@rs.findsymbol(addr)}"
|
715
|
+
}
|
716
|
+
(0..3).each { |dr|
|
717
|
+
if @rs.regs_cache['dr7'] & (1 << (2*dr)) != 0
|
718
|
+
log "bphw #{{0=>'x', 1=>'w', 2=>'?', 3=>'r'}[(@rs.regs_cache['dr7'] >> (16+4*dr)) & 3]} at #{@rs.findsymbol(@rs.regs_cache["dr#{dr}"])}"
|
719
|
+
end
|
720
|
+
}
|
721
|
+
}
|
722
|
+
@command['bc'] = lambda { |lex, int|
|
723
|
+
@rs.clearbreaks
|
724
|
+
}
|
725
|
+
@command['bt'] = lambda { |lex, int| @rs.backtrace { |t| puts t } }
|
726
|
+
@command['d'] = lambda { |lex, int| @dataptr = int[] || return }
|
727
|
+
@command['db'] = lambda { |lex, int| @datafmt = 'db' ; @dataptr = int[] || return }
|
728
|
+
@command['dw'] = lambda { |lex, int| @datafmt = 'dw' ; @dataptr = int[] || return }
|
729
|
+
@command['dd'] = lambda { |lex, int| @datafmt = 'dd' ; @dataptr = int[] || return }
|
730
|
+
@command['r'] = lambda { |lex, int|
|
731
|
+
r = lex.readtok.raw
|
732
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
733
|
+
if r == 'fl'
|
734
|
+
flag = ntok.raw
|
735
|
+
if i = Rubstop::EFLAGS.index(flag)
|
736
|
+
@rs.eflags ^= 1 << i
|
737
|
+
@rs.readregs
|
738
|
+
else
|
739
|
+
log "bad flag #{flag}"
|
740
|
+
end
|
741
|
+
elsif not @rs.regs_cache[r]
|
742
|
+
log "bad reg #{r}"
|
743
|
+
elsif ntok
|
744
|
+
lex.unreadtok ntok
|
745
|
+
newval = int[]
|
746
|
+
if newval and newval.kind_of? ::Integer
|
747
|
+
@rs.send r+'=', newval
|
748
|
+
@rs.readregs
|
749
|
+
end
|
750
|
+
else
|
751
|
+
log "#{r} = #{@rs.regs_cache[r]}"
|
752
|
+
end
|
753
|
+
}
|
754
|
+
@command['run'] = @command['cont'] = lambda { |lex, int|
|
755
|
+
if tok = lex.readtok
|
756
|
+
lex.unreadtok tok
|
757
|
+
cont int[]
|
758
|
+
else cont
|
759
|
+
end
|
760
|
+
}
|
761
|
+
@command['syscall'] = lambda { |lex, int| syscall }
|
762
|
+
@command['singlestep'] = lambda { |lex, int| singlestep }
|
763
|
+
@command['stepover'] = lambda { |lex, int| stepover }
|
764
|
+
@command['stepout'] = lambda { |lex, int| stepout }
|
765
|
+
@command['g'] = lambda { |lex, int|
|
766
|
+
target = int[]
|
767
|
+
@rs.singlestep if @rs.regs_cache['eip'] == target
|
768
|
+
@rs.bpx target, true
|
769
|
+
cont
|
770
|
+
}
|
771
|
+
@command['u'] = lambda { |lex, int| @codeptr = int[] || break }
|
772
|
+
@command['has_pax'] = lambda { |lex, int|
|
773
|
+
if tok = lex.readtok
|
774
|
+
lex.unreadtok tok
|
775
|
+
if (int[] == 0)
|
776
|
+
@rs.set_pax false
|
777
|
+
else
|
778
|
+
@rs.set_pax true
|
779
|
+
end
|
780
|
+
else @rs.set_pax !@rs.has_pax
|
781
|
+
end
|
782
|
+
log "has_pax now #{@rs.has_pax}"
|
783
|
+
}
|
784
|
+
@command['loadsyms'] = lambda { |lex, int|
|
785
|
+
mapfile = ''
|
786
|
+
mapfile << ntok.raw while ntok = lex.readtok
|
787
|
+
if mapfile != ''
|
788
|
+
@rs.loadmap mapfile
|
789
|
+
else
|
790
|
+
@rs.loadallsyms
|
791
|
+
end
|
792
|
+
}
|
793
|
+
@command['scansyms'] = lambda { |lex, int| @rs.scansyms }
|
794
|
+
@command['sym'] = lambda { |lex, int|
|
795
|
+
sym = ''
|
796
|
+
sym << ntok.raw while ntok = lex.readtok
|
797
|
+
s = []
|
798
|
+
@rs.symbols.each { |k, v|
|
799
|
+
s << k if v =~ /#{sym}/
|
800
|
+
}
|
801
|
+
if s.empty?
|
802
|
+
log "unknown symbol #{sym}"
|
803
|
+
else
|
804
|
+
s.sort.each { |s_| log "#{'%08x' % s_} #{@rs.symbols_len[s_].to_s.ljust 6} #{@rs.findsymbol(s_)}" }
|
805
|
+
end
|
806
|
+
}
|
807
|
+
@command['delsym'] = lambda { |lex, int|
|
808
|
+
addr = int[]
|
809
|
+
log "deleted #{@rs.symbols.delete addr}"
|
810
|
+
@rs.symbols_len.delete addr
|
811
|
+
}
|
812
|
+
@command['addsym'] = lambda { |lex, int|
|
813
|
+
name = lex.readtok.raw
|
814
|
+
addr = int[]
|
815
|
+
if t = lex.readtok
|
816
|
+
lex.unreadtok t
|
817
|
+
@rs.symbols_len[addr] = int[]
|
818
|
+
else
|
819
|
+
@rs.symbols_len[addr] = 1
|
820
|
+
end
|
821
|
+
@rs.symbols[addr] = name
|
822
|
+
}
|
823
|
+
@command['help'] = lambda { |lex, int|
|
824
|
+
log 'commands: (addr/values are things like dword ptr [ebp+(4*byte [eax])] ), type <tab> to see all commands'
|
825
|
+
log ' bpx <addr>'
|
826
|
+
log ' bphw [r|w|x] <addr>: debug register breakpoint'
|
827
|
+
log ' bl: list breakpoints'
|
828
|
+
log ' bc: clear breakpoints'
|
829
|
+
log ' cont [<signr>]: continue the target sending a signal'
|
830
|
+
log ' d/db/dw/dd [<addr>]: change data type/address'
|
831
|
+
log ' g <addr>: set a bp at <addr> and run'
|
832
|
+
log ' has_pax [0|1]: set has_pax flag'
|
833
|
+
log ' loadsyms: load symbol information from mapped files (from /proc and disk)'
|
834
|
+
log ' ma <addr> <ascii>: write memory'
|
835
|
+
log ' mx <addr> <hex>: write memory'
|
836
|
+
log ' maps: list maps'
|
837
|
+
log ' r <reg> [<value>]: show/change register'
|
838
|
+
log ' r fl <flag>: toggle eflags bit'
|
839
|
+
log ' scansyms: scan memory for ELF headers'
|
840
|
+
log ' sym <symbol regex>: show symbol information'
|
841
|
+
log ' addsym <name> <addr> [<size>]'
|
842
|
+
log ' delsym <addr>'
|
843
|
+
log ' u <addr>: disassemble addr'
|
844
|
+
log ' reload: reload lindebug source'
|
845
|
+
log ' ruby <ruby code>: instance_evals ruby code in current instance'
|
846
|
+
log ' closeui: detach from the underlying RubStop'
|
847
|
+
log 'keys:'
|
848
|
+
log ' F5: continue'
|
849
|
+
log ' F6: syscall'
|
850
|
+
log ' F10: step over'
|
851
|
+
log ' F11: single step'
|
852
|
+
log ' F12: step out (til next ret)'
|
853
|
+
log ' pgup/pgdown: move command history'
|
854
|
+
}
|
855
|
+
@command['reload'] = lambda { |lex, int| load $0 ; load_commands }
|
856
|
+
@command['ruby'] = lambda { |lex, int|
|
857
|
+
str = ''
|
858
|
+
str << ntok.raw while ntok = lex.readtok
|
859
|
+
instance_eval str
|
860
|
+
}
|
861
|
+
@command['maps'] = lambda { |lex, int|
|
862
|
+
@rs.filemap.sort_by { |f, (b, e)| b }.each { |f, (b, e)|
|
863
|
+
log "#{f.ljust 20} #{'%08x' % b} - #{'%08x' % e}"
|
864
|
+
}
|
865
|
+
}
|
866
|
+
@command['ma'] = lambda { |lex, int|
|
867
|
+
addr = int[]
|
868
|
+
str = ''
|
869
|
+
str << ntok.raw while ntok = lex.readtok
|
870
|
+
@rs[addr, str.length] = str
|
871
|
+
}
|
872
|
+
@command['mx'] = lambda { |lex, int|
|
873
|
+
addr = int[]
|
874
|
+
data = [lex.readtok.raw].pack('H*')
|
875
|
+
@rs[addr, data.length] = data
|
876
|
+
}
|
877
|
+
@command['resize'] = lambda { |lex, int| resize }
|
878
|
+
@command['watch'] = lambda { |lex, int| @watch = ExprParser.parse(lex) ; updatedataptr }
|
879
|
+
@command['wd'] = lambda { |lex, int|
|
880
|
+
@focus = :data
|
881
|
+
if tok = lex.readtok
|
882
|
+
lex.unreadtok tok
|
883
|
+
@win_data_height = int[] || return
|
884
|
+
resize
|
885
|
+
end
|
886
|
+
}
|
887
|
+
@command['wc'] = lambda { |lex, int|
|
888
|
+
@focus = :code
|
889
|
+
if tok = lex.readtok
|
890
|
+
lex.unreadtok tok
|
891
|
+
@win_code_height = int[] || return
|
892
|
+
resize
|
893
|
+
end
|
894
|
+
}
|
895
|
+
@command['wp'] = lambda { |lex, int| @focus = :prompt }
|
896
|
+
@command['?'] = lambda { |lex, int|
|
897
|
+
val = int[]
|
898
|
+
log "#{val} 0x#{val.to_s(16)} #{[val].pack('L').inspect}"
|
899
|
+
}
|
900
|
+
@command['.'] = lambda { |lex, int| @codeptr = nil }
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
|
905
|
+
if $0 == __FILE__
|
906
|
+
require 'optparse'
|
907
|
+
filemap = nil
|
908
|
+
OptionParser.new { |opt|
|
909
|
+
opt.on('-m map', '--map filemap') { |f| filemap = f }
|
910
|
+
}.parse!(ARGV)
|
911
|
+
|
912
|
+
if not defined? Rubstop
|
913
|
+
if ARGV.first =~ /:/
|
914
|
+
stub = 'gdbclient'
|
915
|
+
else
|
916
|
+
stub = 'rubstop'
|
917
|
+
end
|
918
|
+
require File.join(File.dirname(__FILE__), stub)
|
919
|
+
end
|
920
|
+
|
921
|
+
rs = Rubstop.new(ARGV.join(' '))
|
922
|
+
rs.loadmap(filemap) if filemap
|
923
|
+
LinDebug.new(rs).main_loop
|
924
|
+
end
|