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