metasm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. data/BUGS +11 -0
  2. data/CREDITS +17 -0
  3. data/README +270 -0
  4. data/TODO +114 -0
  5. data/doc/code_organisation.txt +146 -0
  6. data/doc/const_missing.txt +16 -0
  7. data/doc/core_classes.txt +75 -0
  8. data/doc/feature_list.txt +53 -0
  9. data/doc/index.txt +59 -0
  10. data/doc/install_notes.txt +170 -0
  11. data/doc/style.css +3 -0
  12. data/doc/use_cases.txt +18 -0
  13. data/lib/metasm.rb +80 -0
  14. data/lib/metasm/arm.rb +12 -0
  15. data/lib/metasm/arm/debug.rb +39 -0
  16. data/lib/metasm/arm/decode.rb +167 -0
  17. data/lib/metasm/arm/encode.rb +77 -0
  18. data/lib/metasm/arm/main.rb +75 -0
  19. data/lib/metasm/arm/opcodes.rb +177 -0
  20. data/lib/metasm/arm/parse.rb +130 -0
  21. data/lib/metasm/arm/render.rb +55 -0
  22. data/lib/metasm/compile_c.rb +1457 -0
  23. data/lib/metasm/dalvik.rb +8 -0
  24. data/lib/metasm/dalvik/decode.rb +196 -0
  25. data/lib/metasm/dalvik/main.rb +60 -0
  26. data/lib/metasm/dalvik/opcodes.rb +366 -0
  27. data/lib/metasm/decode.rb +213 -0
  28. data/lib/metasm/decompile.rb +2659 -0
  29. data/lib/metasm/disassemble.rb +2068 -0
  30. data/lib/metasm/disassemble_api.rb +1280 -0
  31. data/lib/metasm/dynldr.rb +1329 -0
  32. data/lib/metasm/encode.rb +333 -0
  33. data/lib/metasm/exe_format/a_out.rb +194 -0
  34. data/lib/metasm/exe_format/autoexe.rb +82 -0
  35. data/lib/metasm/exe_format/bflt.rb +189 -0
  36. data/lib/metasm/exe_format/coff.rb +455 -0
  37. data/lib/metasm/exe_format/coff_decode.rb +901 -0
  38. data/lib/metasm/exe_format/coff_encode.rb +1078 -0
  39. data/lib/metasm/exe_format/dex.rb +457 -0
  40. data/lib/metasm/exe_format/dol.rb +145 -0
  41. data/lib/metasm/exe_format/elf.rb +923 -0
  42. data/lib/metasm/exe_format/elf_decode.rb +979 -0
  43. data/lib/metasm/exe_format/elf_encode.rb +1375 -0
  44. data/lib/metasm/exe_format/macho.rb +827 -0
  45. data/lib/metasm/exe_format/main.rb +228 -0
  46. data/lib/metasm/exe_format/mz.rb +164 -0
  47. data/lib/metasm/exe_format/nds.rb +172 -0
  48. data/lib/metasm/exe_format/pe.rb +437 -0
  49. data/lib/metasm/exe_format/serialstruct.rb +246 -0
  50. data/lib/metasm/exe_format/shellcode.rb +114 -0
  51. data/lib/metasm/exe_format/xcoff.rb +167 -0
  52. data/lib/metasm/gui.rb +23 -0
  53. data/lib/metasm/gui/cstruct.rb +373 -0
  54. data/lib/metasm/gui/dasm_coverage.rb +199 -0
  55. data/lib/metasm/gui/dasm_decomp.rb +369 -0
  56. data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
  57. data/lib/metasm/gui/dasm_graph.rb +1354 -0
  58. data/lib/metasm/gui/dasm_hex.rb +543 -0
  59. data/lib/metasm/gui/dasm_listing.rb +599 -0
  60. data/lib/metasm/gui/dasm_main.rb +906 -0
  61. data/lib/metasm/gui/dasm_opcodes.rb +291 -0
  62. data/lib/metasm/gui/debug.rb +1228 -0
  63. data/lib/metasm/gui/gtk.rb +884 -0
  64. data/lib/metasm/gui/qt.rb +495 -0
  65. data/lib/metasm/gui/win32.rb +3004 -0
  66. data/lib/metasm/gui/x11.rb +621 -0
  67. data/lib/metasm/ia32.rb +14 -0
  68. data/lib/metasm/ia32/compile_c.rb +1523 -0
  69. data/lib/metasm/ia32/debug.rb +193 -0
  70. data/lib/metasm/ia32/decode.rb +1167 -0
  71. data/lib/metasm/ia32/decompile.rb +564 -0
  72. data/lib/metasm/ia32/encode.rb +314 -0
  73. data/lib/metasm/ia32/main.rb +233 -0
  74. data/lib/metasm/ia32/opcodes.rb +872 -0
  75. data/lib/metasm/ia32/parse.rb +327 -0
  76. data/lib/metasm/ia32/render.rb +91 -0
  77. data/lib/metasm/main.rb +1193 -0
  78. data/lib/metasm/mips.rb +11 -0
  79. data/lib/metasm/mips/compile_c.rb +7 -0
  80. data/lib/metasm/mips/decode.rb +253 -0
  81. data/lib/metasm/mips/encode.rb +51 -0
  82. data/lib/metasm/mips/main.rb +72 -0
  83. data/lib/metasm/mips/opcodes.rb +443 -0
  84. data/lib/metasm/mips/parse.rb +51 -0
  85. data/lib/metasm/mips/render.rb +43 -0
  86. data/lib/metasm/os/gnu_exports.rb +270 -0
  87. data/lib/metasm/os/linux.rb +1112 -0
  88. data/lib/metasm/os/main.rb +1686 -0
  89. data/lib/metasm/os/remote.rb +527 -0
  90. data/lib/metasm/os/windows.rb +2027 -0
  91. data/lib/metasm/os/windows_exports.rb +745 -0
  92. data/lib/metasm/parse.rb +876 -0
  93. data/lib/metasm/parse_c.rb +3938 -0
  94. data/lib/metasm/pic16c/decode.rb +42 -0
  95. data/lib/metasm/pic16c/main.rb +17 -0
  96. data/lib/metasm/pic16c/opcodes.rb +68 -0
  97. data/lib/metasm/ppc.rb +11 -0
  98. data/lib/metasm/ppc/decode.rb +264 -0
  99. data/lib/metasm/ppc/decompile.rb +251 -0
  100. data/lib/metasm/ppc/encode.rb +51 -0
  101. data/lib/metasm/ppc/main.rb +129 -0
  102. data/lib/metasm/ppc/opcodes.rb +410 -0
  103. data/lib/metasm/ppc/parse.rb +52 -0
  104. data/lib/metasm/preprocessor.rb +1277 -0
  105. data/lib/metasm/render.rb +130 -0
  106. data/lib/metasm/sh4.rb +8 -0
  107. data/lib/metasm/sh4/decode.rb +336 -0
  108. data/lib/metasm/sh4/main.rb +292 -0
  109. data/lib/metasm/sh4/opcodes.rb +381 -0
  110. data/lib/metasm/x86_64.rb +12 -0
  111. data/lib/metasm/x86_64/compile_c.rb +1025 -0
  112. data/lib/metasm/x86_64/debug.rb +59 -0
  113. data/lib/metasm/x86_64/decode.rb +268 -0
  114. data/lib/metasm/x86_64/encode.rb +264 -0
  115. data/lib/metasm/x86_64/main.rb +135 -0
  116. data/lib/metasm/x86_64/opcodes.rb +118 -0
  117. data/lib/metasm/x86_64/parse.rb +68 -0
  118. data/misc/bottleneck.rb +61 -0
  119. data/misc/cheader-findpppath.rb +58 -0
  120. data/misc/hexdiff.rb +74 -0
  121. data/misc/hexdump.rb +55 -0
  122. data/misc/metasm-all.rb +13 -0
  123. data/misc/objdiff.rb +47 -0
  124. data/misc/objscan.rb +40 -0
  125. data/misc/pdfparse.rb +661 -0
  126. data/misc/ppc_pdf2oplist.rb +192 -0
  127. data/misc/tcp_proxy_hex.rb +84 -0
  128. data/misc/txt2html.rb +440 -0
  129. data/samples/a.out.rb +31 -0
  130. data/samples/asmsyntax.rb +77 -0
  131. data/samples/bindiff.rb +555 -0
  132. data/samples/compilation-steps.rb +49 -0
  133. data/samples/cparser_makestackoffset.rb +55 -0
  134. data/samples/dasm-backtrack.rb +38 -0
  135. data/samples/dasmnavig.rb +318 -0
  136. data/samples/dbg-apihook.rb +228 -0
  137. data/samples/dbghelp.rb +143 -0
  138. data/samples/disassemble-gui.rb +102 -0
  139. data/samples/disassemble.rb +133 -0
  140. data/samples/dump_upx.rb +95 -0
  141. data/samples/dynamic_ruby.rb +1929 -0
  142. data/samples/elf_list_needed.rb +46 -0
  143. data/samples/elf_listexports.rb +33 -0
  144. data/samples/elfencode.rb +25 -0
  145. data/samples/exeencode.rb +128 -0
  146. data/samples/factorize-headers-elfimports.rb +77 -0
  147. data/samples/factorize-headers-peimports.rb +109 -0
  148. data/samples/factorize-headers.rb +43 -0
  149. data/samples/gdbclient.rb +583 -0
  150. data/samples/generate_libsigs.rb +102 -0
  151. data/samples/hotfix_gtk_dbg.rb +59 -0
  152. data/samples/install_win_env.rb +78 -0
  153. data/samples/lindebug.rb +924 -0
  154. data/samples/linux_injectsyscall.rb +95 -0
  155. data/samples/machoencode.rb +31 -0
  156. data/samples/metasm-shell.rb +91 -0
  157. data/samples/pe-hook.rb +69 -0
  158. data/samples/pe-ia32-cpuid.rb +203 -0
  159. data/samples/pe-mips.rb +35 -0
  160. data/samples/pe-shutdown.rb +78 -0
  161. data/samples/pe-testrelocs.rb +51 -0
  162. data/samples/pe-testrsrc.rb +24 -0
  163. data/samples/pe_listexports.rb +31 -0
  164. data/samples/peencode.rb +19 -0
  165. data/samples/peldr.rb +494 -0
  166. data/samples/preprocess-flatten.rb +19 -0
  167. data/samples/r0trace.rb +308 -0
  168. data/samples/rubstop.rb +399 -0
  169. data/samples/scan_pt_gnu_stack.rb +54 -0
  170. data/samples/scanpeexports.rb +62 -0
  171. data/samples/shellcode-c.rb +40 -0
  172. data/samples/shellcode-dynlink.rb +146 -0
  173. data/samples/source.asm +34 -0
  174. data/samples/struct_offset.rb +47 -0
  175. data/samples/testpe.rb +32 -0
  176. data/samples/testraw.rb +45 -0
  177. data/samples/win32genloader.rb +132 -0
  178. data/samples/win32hooker-advanced.rb +169 -0
  179. data/samples/win32hooker.rb +96 -0
  180. data/samples/win32livedasm.rb +33 -0
  181. data/samples/win32remotescan.rb +133 -0
  182. data/samples/wintrace.rb +92 -0
  183. data/tests/all.rb +8 -0
  184. data/tests/dasm.rb +39 -0
  185. data/tests/dynldr.rb +35 -0
  186. data/tests/encodeddata.rb +132 -0
  187. data/tests/ia32.rb +82 -0
  188. data/tests/mips.rb +116 -0
  189. data/tests/parse_c.rb +239 -0
  190. data/tests/preprocessor.rb +269 -0
  191. data/tests/x86_64.rb +62 -0
  192. 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)
@@ -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