metasm 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (276) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +3 -0
  4. data/.gitignore +3 -0
  5. data/.hgtags +3 -0
  6. data/Gemfile +3 -0
  7. data/INSTALL +61 -0
  8. data/LICENCE +458 -0
  9. data/README +29 -21
  10. data/Rakefile +10 -0
  11. data/TODO +10 -12
  12. data/doc/code_organisation.txt +3 -1
  13. data/doc/core/DynLdr.txt +247 -0
  14. data/doc/core/ExeFormat.txt +43 -0
  15. data/doc/core/Expression.txt +220 -0
  16. data/doc/core/GNUExports.txt +27 -0
  17. data/doc/core/Ia32.txt +236 -0
  18. data/doc/core/SerialStruct.txt +108 -0
  19. data/doc/core/VirtualString.txt +145 -0
  20. data/doc/core/WindowsExports.txt +61 -0
  21. data/doc/core/index.txt +1 -0
  22. data/doc/style.css +6 -3
  23. data/doc/usage/debugger.txt +327 -0
  24. data/doc/usage/index.txt +1 -0
  25. data/doc/use_cases.txt +2 -2
  26. data/metasm.gemspec +23 -0
  27. data/{lib/metasm.rb → metasm.rb} +15 -3
  28. data/{lib/metasm → metasm}/compile_c.rb +15 -9
  29. data/metasm/cpu/arc.rb +8 -0
  30. data/metasm/cpu/arc/decode.rb +404 -0
  31. data/metasm/cpu/arc/main.rb +191 -0
  32. data/metasm/cpu/arc/opcodes.rb +588 -0
  33. data/metasm/cpu/arm.rb +14 -0
  34. data/{lib/metasm → metasm/cpu}/arm/debug.rb +2 -2
  35. data/{lib/metasm → metasm/cpu}/arm/decode.rb +15 -18
  36. data/{lib/metasm → metasm/cpu}/arm/encode.rb +23 -8
  37. data/{lib/metasm → metasm/cpu}/arm/main.rb +3 -6
  38. data/metasm/cpu/arm/opcodes.rb +324 -0
  39. data/{lib/metasm → metasm/cpu}/arm/parse.rb +25 -13
  40. data/{lib/metasm → metasm/cpu}/arm/render.rb +2 -2
  41. data/metasm/cpu/arm64.rb +15 -0
  42. data/metasm/cpu/arm64/debug.rb +38 -0
  43. data/metasm/cpu/arm64/decode.rb +285 -0
  44. data/metasm/cpu/arm64/encode.rb +41 -0
  45. data/metasm/cpu/arm64/main.rb +105 -0
  46. data/metasm/cpu/arm64/opcodes.rb +232 -0
  47. data/metasm/cpu/arm64/parse.rb +20 -0
  48. data/metasm/cpu/arm64/render.rb +95 -0
  49. data/{lib/metasm/mips/compile_c.rb → metasm/cpu/bpf.rb} +4 -2
  50. data/metasm/cpu/bpf/decode.rb +110 -0
  51. data/metasm/cpu/bpf/main.rb +60 -0
  52. data/metasm/cpu/bpf/opcodes.rb +81 -0
  53. data/metasm/cpu/bpf/render.rb +30 -0
  54. data/{lib/metasm/ppc.rb → metasm/cpu/cy16.rb} +2 -4
  55. data/metasm/cpu/cy16/decode.rb +247 -0
  56. data/metasm/cpu/cy16/main.rb +63 -0
  57. data/metasm/cpu/cy16/opcodes.rb +78 -0
  58. data/metasm/cpu/cy16/render.rb +30 -0
  59. data/metasm/cpu/dalvik.rb +11 -0
  60. data/{lib/metasm → metasm/cpu}/dalvik/decode.rb +34 -34
  61. data/{lib/metasm → metasm/cpu}/dalvik/main.rb +71 -4
  62. data/{lib/metasm → metasm/cpu}/dalvik/opcodes.rb +21 -12
  63. data/{lib/metasm/mips.rb → metasm/cpu/ebpf.rb} +3 -4
  64. data/metasm/cpu/ebpf/debug.rb +61 -0
  65. data/metasm/cpu/ebpf/decode.rb +142 -0
  66. data/metasm/cpu/ebpf/main.rb +58 -0
  67. data/metasm/cpu/ebpf/opcodes.rb +97 -0
  68. data/metasm/cpu/ebpf/render.rb +36 -0
  69. data/metasm/cpu/ia32.rb +17 -0
  70. data/{lib/metasm → metasm/cpu}/ia32/compile_c.rb +23 -9
  71. data/{lib/metasm → metasm/cpu}/ia32/debug.rb +44 -6
  72. data/{lib/metasm → metasm/cpu}/ia32/decode.rb +342 -128
  73. data/{lib/metasm → metasm/cpu}/ia32/decompile.rb +75 -53
  74. data/{lib/metasm → metasm/cpu}/ia32/encode.rb +19 -13
  75. data/{lib/metasm → metasm/cpu}/ia32/main.rb +66 -8
  76. data/metasm/cpu/ia32/opcodes.rb +1424 -0
  77. data/{lib/metasm → metasm/cpu}/ia32/parse.rb +55 -17
  78. data/{lib/metasm → metasm/cpu}/ia32/render.rb +32 -5
  79. data/metasm/cpu/mcs51.rb +8 -0
  80. data/metasm/cpu/mcs51/decode.rb +99 -0
  81. data/metasm/cpu/mcs51/main.rb +87 -0
  82. data/metasm/cpu/mcs51/opcodes.rb +120 -0
  83. data/metasm/cpu/mips.rb +14 -0
  84. data/metasm/cpu/mips/debug.rb +42 -0
  85. data/{lib/metasm → metasm/cpu}/mips/decode.rb +59 -38
  86. data/{lib/metasm → metasm/cpu}/mips/encode.rb +4 -3
  87. data/{lib/metasm → metasm/cpu}/mips/main.rb +13 -6
  88. data/{lib/metasm → metasm/cpu}/mips/opcodes.rb +87 -18
  89. data/{lib/metasm → metasm/cpu}/mips/parse.rb +1 -1
  90. data/{lib/metasm → metasm/cpu}/mips/render.rb +1 -1
  91. data/{lib/metasm/dalvik.rb → metasm/cpu/msp430.rb} +1 -1
  92. data/metasm/cpu/msp430/decode.rb +243 -0
  93. data/metasm/cpu/msp430/main.rb +62 -0
  94. data/metasm/cpu/msp430/opcodes.rb +101 -0
  95. data/metasm/cpu/openrisc.rb +11 -0
  96. data/metasm/cpu/openrisc/debug.rb +106 -0
  97. data/metasm/cpu/openrisc/decode.rb +182 -0
  98. data/metasm/cpu/openrisc/decompile.rb +350 -0
  99. data/metasm/cpu/openrisc/main.rb +70 -0
  100. data/metasm/cpu/openrisc/opcodes.rb +109 -0
  101. data/metasm/cpu/openrisc/render.rb +37 -0
  102. data/{lib/metasm → metasm/cpu}/pic16c/decode.rb +6 -7
  103. data/{lib/metasm → metasm/cpu}/pic16c/main.rb +0 -0
  104. data/{lib/metasm → metasm/cpu}/pic16c/opcodes.rb +1 -1
  105. data/metasm/cpu/ppc.rb +11 -0
  106. data/{lib/metasm → metasm/cpu}/ppc/decode.rb +18 -37
  107. data/{lib/metasm → metasm/cpu}/ppc/decompile.rb +3 -3
  108. data/{lib/metasm → metasm/cpu}/ppc/encode.rb +2 -2
  109. data/{lib/metasm → metasm/cpu}/ppc/main.rb +23 -18
  110. data/{lib/metasm → metasm/cpu}/ppc/opcodes.rb +11 -6
  111. data/metasm/cpu/ppc/parse.rb +55 -0
  112. data/metasm/cpu/python.rb +8 -0
  113. data/metasm/cpu/python/decode.rb +116 -0
  114. data/metasm/cpu/python/main.rb +36 -0
  115. data/metasm/cpu/python/opcodes.rb +180 -0
  116. data/{lib/metasm → metasm/cpu}/sh4.rb +1 -1
  117. data/{lib/metasm → metasm/cpu}/sh4/decode.rb +50 -23
  118. data/{lib/metasm → metasm/cpu}/sh4/main.rb +38 -27
  119. data/{lib/metasm → metasm/cpu}/sh4/opcodes.rb +7 -8
  120. data/metasm/cpu/st20.rb +9 -0
  121. data/metasm/cpu/st20/decode.rb +173 -0
  122. data/metasm/cpu/st20/decompile.rb +283 -0
  123. data/metasm/cpu/st20/main.rb +37 -0
  124. data/metasm/cpu/st20/opcodes.rb +140 -0
  125. data/{lib/metasm/arm.rb → metasm/cpu/webasm.rb} +4 -5
  126. data/metasm/cpu/webasm/debug.rb +31 -0
  127. data/metasm/cpu/webasm/decode.rb +321 -0
  128. data/metasm/cpu/webasm/decompile.rb +386 -0
  129. data/metasm/cpu/webasm/encode.rb +104 -0
  130. data/metasm/cpu/webasm/main.rb +81 -0
  131. data/metasm/cpu/webasm/opcodes.rb +214 -0
  132. data/metasm/cpu/x86_64.rb +15 -0
  133. data/{lib/metasm → metasm/cpu}/x86_64/compile_c.rb +40 -25
  134. data/{lib/metasm → metasm/cpu}/x86_64/debug.rb +4 -4
  135. data/{lib/metasm → metasm/cpu}/x86_64/decode.rb +58 -15
  136. data/{lib/metasm → metasm/cpu}/x86_64/encode.rb +59 -28
  137. data/{lib/metasm → metasm/cpu}/x86_64/main.rb +18 -6
  138. data/metasm/cpu/x86_64/opcodes.rb +138 -0
  139. data/{lib/metasm → metasm/cpu}/x86_64/parse.rb +12 -4
  140. data/metasm/cpu/x86_64/render.rb +35 -0
  141. data/metasm/cpu/z80.rb +9 -0
  142. data/metasm/cpu/z80/decode.rb +286 -0
  143. data/metasm/cpu/z80/main.rb +67 -0
  144. data/metasm/cpu/z80/opcodes.rb +224 -0
  145. data/metasm/cpu/z80/render.rb +48 -0
  146. data/{lib/metasm/os/main.rb → metasm/debug.rb} +201 -407
  147. data/{lib/metasm → metasm}/decode.rb +104 -24
  148. data/{lib/metasm → metasm}/decompile.rb +804 -478
  149. data/{lib/metasm → metasm}/disassemble.rb +385 -170
  150. data/{lib/metasm → metasm}/disassemble_api.rb +684 -105
  151. data/{lib/metasm → metasm}/dynldr.rb +231 -138
  152. data/{lib/metasm → metasm}/encode.rb +20 -5
  153. data/{lib/metasm → metasm}/exe_format/a_out.rb +9 -6
  154. data/{lib/metasm → metasm}/exe_format/autoexe.rb +3 -0
  155. data/{lib/metasm → metasm}/exe_format/bflt.rb +57 -27
  156. data/{lib/metasm → metasm}/exe_format/coff.rb +35 -7
  157. data/{lib/metasm → metasm}/exe_format/coff_decode.rb +70 -23
  158. data/{lib/metasm → metasm}/exe_format/coff_encode.rb +24 -22
  159. data/{lib/metasm → metasm}/exe_format/dex.rb +26 -8
  160. data/{lib/metasm → metasm}/exe_format/dol.rb +1 -0
  161. data/{lib/metasm → metasm}/exe_format/elf.rb +108 -58
  162. data/{lib/metasm → metasm}/exe_format/elf_decode.rb +202 -36
  163. data/{lib/metasm → metasm}/exe_format/elf_encode.rb +126 -32
  164. data/metasm/exe_format/gb.rb +65 -0
  165. data/metasm/exe_format/javaclass.rb +424 -0
  166. data/{lib/metasm → metasm}/exe_format/macho.rb +218 -16
  167. data/{lib/metasm → metasm}/exe_format/main.rb +28 -3
  168. data/{lib/metasm → metasm}/exe_format/mz.rb +2 -0
  169. data/{lib/metasm → metasm}/exe_format/nds.rb +7 -4
  170. data/{lib/metasm → metasm}/exe_format/pe.rb +96 -11
  171. data/metasm/exe_format/pyc.rb +167 -0
  172. data/{lib/metasm → metasm}/exe_format/serialstruct.rb +67 -14
  173. data/{lib/metasm → metasm}/exe_format/shellcode.rb +7 -3
  174. data/metasm/exe_format/shellcode_rwx.rb +114 -0
  175. data/metasm/exe_format/swf.rb +205 -0
  176. data/metasm/exe_format/wasm.rb +402 -0
  177. data/{lib/metasm → metasm}/exe_format/xcoff.rb +7 -7
  178. data/metasm/exe_format/zip.rb +335 -0
  179. data/metasm/gui.rb +13 -0
  180. data/{lib/metasm → metasm}/gui/cstruct.rb +35 -41
  181. data/{lib/metasm → metasm}/gui/dasm_coverage.rb +11 -11
  182. data/{lib/metasm → metasm}/gui/dasm_decomp.rb +177 -114
  183. data/{lib/metasm → metasm}/gui/dasm_funcgraph.rb +0 -0
  184. data/metasm/gui/dasm_graph.rb +1754 -0
  185. data/{lib/metasm → metasm}/gui/dasm_hex.rb +16 -12
  186. data/{lib/metasm → metasm}/gui/dasm_listing.rb +43 -28
  187. data/{lib/metasm → metasm}/gui/dasm_main.rb +360 -77
  188. data/{lib/metasm → metasm}/gui/dasm_opcodes.rb +5 -19
  189. data/{lib/metasm → metasm}/gui/debug.rb +109 -34
  190. data/{lib/metasm → metasm}/gui/gtk.rb +174 -44
  191. data/{lib/metasm → metasm}/gui/qt.rb +14 -4
  192. data/{lib/metasm → metasm}/gui/win32.rb +180 -43
  193. data/{lib/metasm → metasm}/gui/x11.rb +59 -59
  194. data/{lib/metasm → metasm}/main.rb +421 -286
  195. data/metasm/os/emulator.rb +175 -0
  196. data/{lib/metasm/os/remote.rb → metasm/os/gdbremote.rb} +146 -54
  197. data/{lib/metasm → metasm}/os/gnu_exports.rb +1 -1
  198. data/{lib/metasm → metasm}/os/linux.rb +628 -151
  199. data/metasm/os/main.rb +335 -0
  200. data/{lib/metasm → metasm}/os/windows.rb +151 -58
  201. data/{lib/metasm → metasm}/os/windows_exports.rb +141 -0
  202. data/{lib/metasm → metasm}/parse.rb +49 -36
  203. data/{lib/metasm → metasm}/parse_c.rb +405 -246
  204. data/{lib/metasm → metasm}/preprocessor.rb +71 -41
  205. data/{lib/metasm → metasm}/render.rb +14 -38
  206. data/misc/hexdump.rb +4 -3
  207. data/misc/lint.rb +58 -0
  208. data/misc/objdiff.rb +4 -1
  209. data/misc/objscan.rb +1 -1
  210. data/misc/openrisc-parser.rb +79 -0
  211. data/misc/txt2html.rb +9 -7
  212. data/samples/bindiff.rb +3 -4
  213. data/samples/dasm-plugins/bindiff.rb +15 -0
  214. data/samples/dasm-plugins/bookmark.rb +133 -0
  215. data/samples/dasm-plugins/c_constants.rb +57 -0
  216. data/samples/dasm-plugins/colortheme_solarized.rb +125 -0
  217. data/samples/dasm-plugins/cppobj_funcall.rb +60 -0
  218. data/samples/dasm-plugins/dasm_all.rb +70 -0
  219. data/samples/dasm-plugins/demangle_cpp.rb +31 -0
  220. data/samples/dasm-plugins/deobfuscate.rb +251 -0
  221. data/samples/dasm-plugins/dump_text.rb +35 -0
  222. data/samples/dasm-plugins/export_graph_svg.rb +86 -0
  223. data/samples/dasm-plugins/findgadget.rb +75 -0
  224. data/samples/dasm-plugins/hl_opcode.rb +32 -0
  225. data/samples/dasm-plugins/hotfix_gtk_dbg.rb +19 -0
  226. data/samples/dasm-plugins/imm2off.rb +34 -0
  227. data/samples/dasm-plugins/match_libsigs.rb +93 -0
  228. data/samples/dasm-plugins/patch_file.rb +95 -0
  229. data/samples/dasm-plugins/scanfuncstart.rb +36 -0
  230. data/samples/dasm-plugins/scanxrefs.rb +29 -0
  231. data/samples/dasm-plugins/selfmodify.rb +197 -0
  232. data/samples/dasm-plugins/stringsxrefs.rb +28 -0
  233. data/samples/dasmnavig.rb +1 -1
  234. data/samples/dbg-apihook.rb +24 -9
  235. data/samples/dbg-plugins/heapscan.rb +283 -0
  236. data/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c +155 -0
  237. data/samples/dbg-plugins/heapscan/compiled_heapscan_win.c +128 -0
  238. data/samples/dbg-plugins/heapscan/graphheap.rb +616 -0
  239. data/samples/dbg-plugins/heapscan/heapscan.rb +709 -0
  240. data/samples/dbg-plugins/heapscan/winheap.h +174 -0
  241. data/samples/dbg-plugins/heapscan/winheap7.h +307 -0
  242. data/samples/dbg-plugins/trace_func.rb +214 -0
  243. data/samples/disassemble-gui.rb +48 -7
  244. data/samples/disassemble.rb +31 -6
  245. data/samples/dump_upx.rb +24 -12
  246. data/samples/dynamic_ruby.rb +35 -27
  247. data/samples/elfencode.rb +15 -0
  248. data/samples/emubios.rb +251 -0
  249. data/samples/emudbg.rb +127 -0
  250. data/samples/exeencode.rb +6 -5
  251. data/samples/factorize-headers-peimports.rb +1 -1
  252. data/samples/lindebug.rb +186 -391
  253. data/samples/metasm-shell.rb +68 -57
  254. data/samples/peldr.rb +2 -2
  255. data/tests/all.rb +1 -1
  256. data/tests/arc.rb +26 -0
  257. data/tests/dynldr.rb +22 -4
  258. data/tests/expression.rb +57 -0
  259. data/tests/graph_layout.rb +285 -0
  260. data/tests/ia32.rb +80 -26
  261. data/tests/mcs51.rb +27 -0
  262. data/tests/mips.rb +10 -3
  263. data/tests/preprocessor.rb +18 -0
  264. data/tests/x86_64.rb +66 -18
  265. metadata +465 -219
  266. metadata.gz.sig +2 -0
  267. data/lib/metasm/arm/opcodes.rb +0 -177
  268. data/lib/metasm/gui.rb +0 -23
  269. data/lib/metasm/gui/dasm_graph.rb +0 -1354
  270. data/lib/metasm/ia32.rb +0 -14
  271. data/lib/metasm/ia32/opcodes.rb +0 -872
  272. data/lib/metasm/ppc/parse.rb +0 -52
  273. data/lib/metasm/x86_64.rb +0 -12
  274. data/lib/metasm/x86_64/opcodes.rb +0 -118
  275. data/samples/gdbclient.rb +0 -583
  276. data/samples/rubstop.rb +0 -399
@@ -27,7 +27,7 @@ class HexWidget < DrawableWidget
27
27
  @oldcaret_x_data = 42
28
28
  @focus_zone = @oldfocus_zone = :hex
29
29
  @addr_min = @dasm.sections.keys.grep(Integer).min rescue nil
30
- @addr_max = @dasm.sections.map { |s, e| s + e.length }.max rescue nil
30
+ @addr_max = @dasm.sections.select { |s, _| s.kind_of?(Integer) }.map { |s, e| s + e.length }.max rescue nil
31
31
  @view_addr = @dasm.prog_binding['entrypoint'] || @addr_min || 0
32
32
  @show_address = @show_data = @show_ascii = true
33
33
  @data_size = 1
@@ -42,12 +42,11 @@ class HexWidget < DrawableWidget
42
42
  @relative_addr = nil # show '+42h' in the addr column if not nil
43
43
  @hl_curbyte = true # draw grey bg for current byte
44
44
 
45
- @default_color_association = { :ascii => :black, :data => :black,
46
- :address => :blue, :caret => :black, :background => :white,
47
- :write_pending => :darkred, :caret_mirror => :palegrey }
45
+ @default_color_association = ColorTheme.merge :ascii => :black, :data => :black,
46
+ :write_pending => :darkred, :caret_mirror => :palegrey
48
47
  end
49
48
 
50
- def resized(w, h)
49
+ def resized(w=width, h=height)
51
50
  wc = w/@font_width
52
51
  hc = h/@font_height
53
52
  ca = current_address
@@ -103,6 +102,7 @@ class HexWidget < DrawableWidget
103
102
  end
104
103
  else
105
104
  @data_size = {1 => 2, 2 => 4, 4 => 8, 8 => 1}[@data_size]
105
+ resized
106
106
  end
107
107
  redraw
108
108
  end
@@ -350,8 +350,8 @@ class HexWidget < DrawableWidget
350
350
  else
351
351
  case v = key
352
352
  when ?0..?9; v -= ?0
353
- when ?a..?f; v -= ?a-10
354
- when ?A..?F; v -= ?A-10
353
+ when ?a..?f; v -= ?a - 10
354
+ when ?A..?F; v -= ?A - 10
355
355
  else return false
356
356
  end
357
357
  end
@@ -392,11 +392,15 @@ class HexWidget < DrawableWidget
392
392
 
393
393
  # pop a dialog, scans the sections for a hex pattern
394
394
  def prompt_search_hex
395
- inputbox('hex pattern to search (hex regexp, use .. for wildcard)') { |pat|
395
+ text = ''
396
+ if current_address.kind_of?(::Integer)
397
+ text = Expression.encode_imm(current_address, "u#{@dasm.cpu.size}".to_sym, @dasm.cpu).unpack('H*').first
398
+ end
399
+ inputbox('hex pattern to search (hex regexp, use .. for wildcard)', :text => text) { |pat|
396
400
  pat = pat.gsub(' ', '').gsub('..', '.').gsub(/[0-9a-f][0-9a-f]/i) { |o| "\\x#{o}" }
397
401
  pat = Regexp.new(pat, Regexp::MULTILINE, 'n') # 'n' = force ascii-8bit
398
402
  list = [['addr']] + @dasm.pattern_scan(pat).map { |a| [Expression[a]] }
399
- listwindow("hex search #{pat}", list) { |i| focus_addr i[0] }
403
+ listwindow("hex search #{pat}", list) { |i| @parent_widget.focus_addr i[0] }
400
404
  }
401
405
  end
402
406
 
@@ -404,7 +408,7 @@ class HexWidget < DrawableWidget
404
408
  def prompt_search_ascii
405
409
  inputbox('data pattern to search (regexp)') { |pat|
406
410
  list = [['addr']] + @dasm.pattern_scan(/#{pat}/).map { |a| [Expression[a]] }
407
- listwindow("data search #{pat}", list) { |i| focus_addr i[0] }
411
+ listwindow("data search #{pat}", list) { |i| @parent_widget.focus_addr i[0] }
408
412
  }
409
413
  end
410
414
 
@@ -483,7 +487,7 @@ class HexWidget < DrawableWidget
483
487
  }
484
488
  @write_pending.clear
485
489
  rescue
486
- @parent_widget.messagebox($!, $!.class.to_s)
490
+ @parent_widget.messagebox($!.message.to_s, $!.class.to_s)
487
491
  end
488
492
 
489
493
  def get_cursor_pos
@@ -534,7 +538,7 @@ class HexWidget < DrawableWidget
534
538
 
535
539
  def gui_update
536
540
  @addr_min = @dasm.sections.keys.grep(Integer).min rescue nil
537
- @addr_max = @dasm.sections.map { |s, e| s + e.length }.max rescue nil
541
+ @addr_max = @dasm.sections.select { |s, _| s.kind_of?(Integer) }.map { |s, e| s + e.length }.max rescue nil
538
542
  @raw_data_cache.clear
539
543
  redraw
540
544
  end
@@ -28,10 +28,8 @@ class AsmListingWidget < DrawableWidget
28
28
  @maxaddr = (addrs.max + @dasm.sections[addrs.max].length rescue (1 << @dasm.cpu.size))
29
29
  @startaddr = @dasm.prog_binding['entrypoint'] || @minaddr
30
30
 
31
- @default_color_association = { :comment => :darkblue, :label => :darkgreen, :text => :black,
32
- :instruction => :black, :address => :blue, :caret => :black, :raw_data => :black,
33
- :background => :white, :cursorline_bg => :paleyellow, :hl_word => :palered,
34
- :arrows_bg => :palegrey, :arrow_up => :darkblue, :arrow_dn => :darkyellow, :arrow_hl => :red }
31
+ @default_color_association = ColorTheme.merge :raw_data => :black, :arrows_bg => :palegrey,
32
+ :arrow_up => :darkblue, :arrow_dn => :darkyellow, :arrow_hl => :red
35
33
  end
36
34
 
37
35
  def resized(w, h)
@@ -55,11 +53,26 @@ class AsmListingWidget < DrawableWidget
55
53
 
56
54
  def click(x, y)
57
55
  set_caret_from_click(x - @arrow_zone_w, y)
56
+ @caret_x = 0 if @caret_x < 0
58
57
  end
59
58
 
60
59
  def rightclick(x, y)
61
60
  click(x, y)
62
- @parent_widget.clone_window(@hl_word, :listing)
61
+ cx = (x - @arrow_zone_w) / @font_width
62
+ cy = y / @font_height
63
+ if cx > 0
64
+ m = new_menu
65
+ cm = new_menu
66
+ addsubmenu(cm, 'copy _word') { clipboard_copy(@hl_word) if @hl_word }
67
+ addsubmenu(cm, 'copy _line') { clipboard_copy(@line_text[cy]) if @line_text[cy] }
68
+ addsubmenu(cm, 'copy _all') { clipboard_copy(@line_text.join("\r\n")) } # XXX auto \r\n vs \n
69
+ addsubmenu(m, '_clipboard', cm)
70
+ addsubmenu(m, 'clone _window') { @parent_widget.clone_window(@hl_word, :listing) }
71
+ if @parent_widget.respond_to?(:extend_contextmenu)
72
+ @parent_widget.extend_contextmenu(self, m, @line_address[@caret_y])
73
+ end
74
+ popupmenu(m, x, y)
75
+ end
63
76
  end
64
77
 
65
78
  def doubleclick(x, y)
@@ -118,19 +131,7 @@ class AsmListingWidget < DrawableWidget
118
131
  render = lambda { |str, color|
119
132
  # function ends when we write under the bottom of the listing
120
133
  next if not str or y >= w_h or x >= w_w
121
- if @hl_word and @hl_word != ''
122
- stmp = str
123
- pre_x = 0
124
- while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/
125
- s1, s2 = $1, $2
126
- pre_x += s1.length * @font_width
127
- hl_x = s2.length * @font_width
128
- draw_rectangle_color(:hl_word, x+pre_x, y, hl_x, @font_height)
129
- pre_x += hl_x
130
- stmp = stmp[s1.length+s2.length..-1]
131
- end
132
- end
133
- draw_string_color(color, x, y, str)
134
+ draw_string_hl(color, x, y, str)
134
135
  x += str.length * @font_width
135
136
  }
136
137
 
@@ -275,6 +276,12 @@ class AsmListingWidget < DrawableWidget
275
276
 
276
277
  def keypress(key)
277
278
  case key
279
+ when ?u # undef data formatting with ?d
280
+ addr = current_address
281
+ if not @dasm.decoded[addr] and @dasm.xrefs[addr].kind_of?(Xref)
282
+ @dasm.xrefs.delete addr
283
+ gui_update
284
+ end
278
285
  when :left
279
286
  if @caret_x >= 1
280
287
  @caret_x -= 1
@@ -315,6 +322,8 @@ class AsmListingWidget < DrawableWidget
315
322
  when :end
316
323
  @caret_x = @line_text[@caret_y].to_s.length
317
324
  update_caret
325
+ when :popupmenu
326
+ rightclick(@caret_x*@font_width + @arrow_zone_w+1, @caret_y*@font_height)
318
327
  else return false
319
328
  end
320
329
  true
@@ -422,16 +431,18 @@ class AsmListingWidget < DrawableWidget
422
431
  # ary
423
432
  di.block.each_from_samefunc(@dasm) { |addr|
424
433
  addr = @dasm.normalize addr
425
- next if not addr.kind_of? ::Integer or (ndi = @dasm.di_at(addr) and ndi.next_addr == curaddr)
434
+ # block.list.last for delayslot
435
+ next if ndi = @dasm.di_at(addr) and ndi.block.list.last.next_addr == curaddr
426
436
  arrows_addr << [addr, curaddr]
427
437
  }
428
438
  end
429
439
  if di.block.list.last == di
440
+ # kikoo delayslot
441
+ rdi = di.block.list[-[4, di.block.list.length].min, 4].reverse.find { |_di| _di.opcode.props[:setip] } || di
430
442
  di.block.each_to_samefunc(@dasm) { |addr|
431
443
  addr = @dasm.normalize addr
432
- next if not addr.kind_of? ::Integer or (di.next_addr == addr and
433
- (not di.opcode.props[:saveip] or di.block.to_subfuncret))
434
- arrows_addr << [curaddr, addr]
444
+ next if di.next_addr == addr and (not rdi.opcode.props[:saveip] or rdi.block.to_subfuncret)
445
+ arrows_addr << [rdi.address, addr]
435
446
  }
436
447
  end
437
448
  str_c << ["#{Expression[di.address]} ", :address]
@@ -485,11 +496,11 @@ class AsmListingWidget < DrawableWidget
485
496
  xlen ||= xref.len || 1 if xref.len
486
497
  comment << " #{xref.type}#{xref.len}:#{Expression[xref.origin]}" if xref.origin
487
498
  } if @dasm.xrefs[curaddr]
488
- len = xlen if xlen and xlen > 2 # db xref may point a string
499
+ len = xlen if xlen and xlen >= 2 # db xref may point a string
489
500
  comment = nil if comment.empty?
490
501
  len = (1..len).find { |l| @dasm.xrefs[curaddr+l] or s.inv_export[s.ptr+l] or s.reloc[s.ptr+l] } || len
491
502
  str = str[0, len] if len < str.length
492
- str = str.pack('C*').unpack(@dasm.cpu.endianness == :big ? 'n*' : 'v*') if len == 2
503
+ str = str.pack('C*').unpack(@dasm.cpu.endianness == :big ? 'n*' : 'v*') if xlen == 2
493
504
  if (xlen == 1 or xlen == 2) and asc = str.inject('') { |asc_, c|
494
505
  case c
495
506
  when 0x20..0x7e, 9, 10, 13; asc_ << c
@@ -534,7 +545,7 @@ class AsmListingWidget < DrawableWidget
534
545
  comment = []
535
546
  @dasm.each_xref(curaddr) { |xref|
536
547
  len = xref.len if xref.len
537
- comment << " #{xref.type}#{xref.len}:#{Expression[xref.origin]} "
548
+ comment << " #{xref.type}#{xref.len}:#{Expression[xref.origin] if xref.origin} "
538
549
  }
539
550
  len = 1 if (len != 2 and len != 4 and len != 8) or len < 1
540
551
  dat = "#{%w[x db dw x dd x x x dq][len]} ? "
@@ -578,9 +589,13 @@ class AsmListingWidget < DrawableWidget
578
589
  prev_arrows = @arrows
579
590
  addr_line = {} # addr => last line (di)
580
591
  @line_address.each_with_index { |a, l| addr_line[a] = l }
581
- @arrows = arrows_addr.uniq.sort.map { |from, to|
582
- [(addr_line[from] || (from < curaddr ? :up : :down) rescue :up),
583
- (addr_line[ to ] || ( to < curaddr ? :up : :down) rescue :up)]
592
+ @arrows = arrows_addr.uniq.find_all { |from, to|
593
+ ((from-curaddr)+(to-curaddr)).kind_of?(::Integer) rescue nil
594
+ }.sort_by { |from, to|
595
+ [from-curaddr, to-curaddr]
596
+ }.map { |from, to|
597
+ [(addr_line[from] || (from-curaddr < 0 ? :up : :down)),
598
+ (addr_line[ to ] || (to - curaddr < 0 ? :up : :down))]
584
599
  }
585
600
  invalidate(0, 0, @arrow_zone_w, 100000) if prev_arrows != @arrows
586
601
  end
@@ -15,6 +15,16 @@ require 'metasm/gui/cstruct'
15
15
 
16
16
  module Metasm
17
17
  module Gui
18
+ class DrawableWidget
19
+ ColorTheme = { :comment => :darkblue, :label => :darkgreen, :text => :black,
20
+ :instruction => :black, :address => :blue, :caret => :black, :background => :white,
21
+ :cursorline_bg => :paleyellow, :hl_word_bg => :palered, :hl_word => :black,
22
+ :red_bg => 'f88', :green_bg => '8f8', :blue_bg => '88f',
23
+ :cyan_bg => '8ff', :magenta_bg => 'f8f', :yellow_bg => 'ff8',
24
+ :orange_bg => 'fc8'
25
+ }
26
+ end
27
+
18
28
  # the main disassembler widget: this is a container for all the lower-level widgets that actually render the dasm state
19
29
  class DisasmWidget < ContainerChoiceWidget
20
30
  attr_accessor :entrypoints, :gui_update_counter_max
@@ -50,6 +60,10 @@ class DisasmWidget < ContainerChoiceWidget
50
60
  addview :cstruct, CStructWidget.new(@dasm, self)
51
61
 
52
62
  view(:listing).grab_focus
63
+
64
+ if ENV['METASM_DASM_PLUGINS']
65
+ ENV['METASM_DASM_PLUGINS'].split(',').each { |p| @dasm.load_plugin p }
66
+ end
53
67
  end
54
68
 
55
69
  attr_reader :dasm
@@ -64,6 +78,7 @@ class DisasmWidget < ContainerChoiceWidget
64
78
 
65
79
  # start an idle callback that will run one round of @dasm.disassemble_mainiter
66
80
  def start_disassemble_bg
81
+ return if @dasm.addrs_todo.empty? and @entrypoints.all? { |ep| @dasm.decoded[ep] }
67
82
  gui_update_counter = 0
68
83
  run = false
69
84
  Gui.idle_add {
@@ -84,6 +99,10 @@ class DisasmWidget < ContainerChoiceWidget
84
99
  }
85
100
  end
86
101
 
102
+ def wait_disassemble_bg
103
+ Gui.main_iter until @entrypoints.empty? and @dasm.addrs_todo.empty?
104
+ end
105
+
87
106
  def terminate
88
107
  @clones.delete self
89
108
  end
@@ -95,7 +114,7 @@ class DisasmWidget < ContainerChoiceWidget
95
114
 
96
115
  # returns the object under the cursor in current view (@dasm.decoded[curaddr])
97
116
  def curobj
98
- @dasm.decoded[curaddr]
117
+ curview.respond_to?(:curobj) ? curview.curobj : @dasm.decoded[curaddr]
99
118
  end
100
119
 
101
120
  # returns the address of the label under the cursor or the address of the line of the cursor
@@ -107,6 +126,17 @@ class DisasmWidget < ContainerChoiceWidget
107
126
  @dasm.prog_binding[hl] || curview.current_address
108
127
  end
109
128
 
129
+ # returns the ExpressionString if the currently hilighted word is a :stackvar
130
+ def pointed_localvar(obj=curobj, hl=curview.hl_word)
131
+ return if not obj.kind_of?(Renderable)
132
+ localvar = nil
133
+ obj.each_expr { |e|
134
+ next unless e.kind_of?(ExpressionString)
135
+ localvar = e if e.type == :stackvar and e.str == hl
136
+ }
137
+ localvar
138
+ end
139
+
110
140
  # parse an address and change it to a canonical address form
111
141
  # supported formats: label names, or string with numerical value, incl hex (0x42 and 42h)
112
142
  # if the string is full decimal, a check against mapped space is done to find if it is
@@ -138,17 +168,17 @@ class DisasmWidget < ContainerChoiceWidget
138
168
  end
139
169
 
140
170
  # display the specified address
141
- # the display first searches in the current view (graph, listing, etc),
142
- # if it cannot display the address all other views are tried in order
171
+ # the display first searches in the current view
172
+ # if it cannot display the address, the listing, graph and decompile views are tried (in that order)
143
173
  # the current focus address is saved in @pos_history (see focus_addr_back/redo)
144
- # a messagebox is popped if no view can display the address unless quiet is true
174
+ # if quiet is false, a messagebox is popped if no view can display the address
145
175
  def focus_addr(addr, viewidx=nil, quiet=false, *a)
146
176
  viewidx ||= curview_index || :listing
147
177
  return if not addr
148
- return if viewidx == curview_index and addr == curaddr
178
+ return if viewidx == curview_index and addr == curaddr and a.empty?
149
179
  oldpos = [curview_index, (curview.get_cursor_pos if curview)]
150
180
  views = [viewidx, oldpos[0]]
151
- views += [:listing, :graph] & view_indexes
181
+ views += [:listing, :graph, :decompile] & view_indexes
152
182
  if views.compact.uniq.find { |i|
153
183
  o_p = view(i).get_cursor_pos
154
184
  if (view(i).focus_addr(addr, *a) rescue nil)
@@ -157,11 +187,13 @@ class DisasmWidget < ContainerChoiceWidget
157
187
  true
158
188
  else
159
189
  view(i).set_cursor_pos o_p
190
+ a.clear
160
191
  false
161
192
  end
162
193
  }
163
194
  @pos_history << oldpos if oldpos[0] # ignore start focus_addr
164
195
  @pos_history_redo.clear
196
+ session_append "@session_focus_addr = #{addr.inspect} ; @pos_history = #{@pos_history.inspect}"
165
197
  true
166
198
  else
167
199
  messagebox "Invalid address #{addr}" if not quiet
@@ -198,7 +230,7 @@ class DisasmWidget < ContainerChoiceWidget
198
230
 
199
231
  # ask the current view to update itself
200
232
  def do_gui_update
201
- curview.gui_update # invalidate all views ?
233
+ curview.gui_update if curview # invalidate all views ?
202
234
  end
203
235
 
204
236
  # redraw the window
@@ -212,13 +244,14 @@ class DisasmWidget < ContainerChoiceWidget
212
244
  yield
213
245
  focus_addr curaddr if addr
214
246
  end
215
-
247
+
216
248
  # calls listwindow with the same argument, but also creates a new bg_color_callback
217
249
  # that will color lines whose address is to be found in list[0] in green
218
250
  # the callback is put only for the duration of the listwindow, and is not reentrant.
219
251
  def list_bghilight(title, list, a={}, &b)
220
252
  prev_colorcb = bg_color_callback
221
- hash = list[1..-1].inject({}) { |h, l| h.update Expression[l[0] || :unknown].reduce => true }
253
+ addr_idx = a.delete(:bghilight_index) || 0
254
+ hash = list[1..-1].inject({}) { |h, l| h.update Expression[l[addr_idx] || :unknown].reduce => true }
222
255
  @bg_color_callback = lambda { |addr| hash[addr] ? '0f0' : prev_colorcb ? prev_colorcb[addr] : nil }
223
256
  redraw
224
257
  popupend = lambda { @bg_color_callback = prev_colorcb ; redraw }
@@ -234,18 +267,24 @@ class DisasmWidget < ContainerChoiceWidget
234
267
  inputbox("new comment for #{Expression[addr]}", :text => cmt) { |c|
235
268
  c = c.split("\n")
236
269
  c = nil if c == []
237
- if di = @dasm.di_at(addr)
238
- di.comment = c
239
- else
240
- @dasm.comment[addr] = c
241
- end
270
+ do_add_comment(addr, c)
271
+ session_append "do_add_comment(#{addr.inspect}, #{c.inspect})"
242
272
  gui_update
243
273
  }
244
274
  end
245
275
 
276
+ def do_add_comment(addr, c)
277
+ if di = @dasm.di_at(addr)
278
+ di.comment = c
279
+ else
280
+ @dasm.comment[addr] = c
281
+ end
282
+ end
283
+
246
284
  # disassemble from this point
247
285
  # if points to a call, make it return
248
286
  def disassemble(addr)
287
+ session_append "disassemble(#{addr.inspect}) ; wait_disassemble_bg"
249
288
  if di = @dasm.di_at(addr) and di.opcode.props[:saveip]
250
289
  di.block.each_to_normal { |t|
251
290
  t = @dasm.normalize t
@@ -253,9 +292,9 @@ class DisasmWidget < ContainerChoiceWidget
253
292
  @dasm.function[t] ||= @dasm.function[:default] ? @dasm.function[:default].dup : DecodedFunction.new
254
293
  }
255
294
  di.block.add_to_subfuncret(di.next_addr)
256
- @dasm.addrs_todo << [di.next_addr, addr, true]
295
+ @dasm.addrs_todo << { :addr => di.next_addr, :from => addr, :from_subfuncret => true }
257
296
  elsif addr
258
- @dasm.addrs_todo << [addr]
297
+ @entrypoints << addr
259
298
  end
260
299
  start_disassemble_bg
261
300
  end
@@ -263,20 +302,23 @@ class DisasmWidget < ContainerChoiceWidget
263
302
  # disassemble fast from this point (don't dasm subfunctions, don't backtrace)
264
303
  def disassemble_fast(addr)
265
304
  @dasm.disassemble_fast(addr)
305
+ session_append "dasm.disassemble_fast(#{addr.inspect})"
266
306
  gui_update
267
307
  end
268
308
 
269
309
  # disassemble fast & deep from this point (don't backtrace, but still dasm subfuncs)
270
310
  def disassemble_fast_deep(addr)
271
311
  @dasm.disassemble_fast_deep(addr)
312
+ session_append "dasm.disassemble_fast_deep(#{addr.inspect})"
272
313
  gui_update
273
314
  end
274
315
 
275
316
  # (re)decompile
276
317
  def decompile(addr)
318
+ session_append "decompile(#{addr.inspect})"
277
319
  if @dasm.c_parser and var = @dasm.c_parser.toplevel.symbol[addr] and (var.type.kind_of? C::Function or @dasm.di_at(addr))
278
320
  @dasm.decompiler.redecompile(addr)
279
- view(:decompile).curaddr = nil
321
+ view(:decompile).curfuncaddr = nil
280
322
  end
281
323
  focus_addr(addr, :decompile)
282
324
  end
@@ -284,6 +326,7 @@ class DisasmWidget < ContainerChoiceWidget
284
326
  # change the format of displayed data under addr (byte, word, dword, qword)
285
327
  # currently this is done using a fake empty xref
286
328
  def toggle_data(addr)
329
+ session_append "toggle_data(#{addr.inspect})"
287
330
  return if @dasm.decoded[addr] or not @dasm.get_section_at(addr)
288
331
  @dasm.add_xref(addr, Xref.new(nil, nil, 1)) if not @dasm.xrefs[addr]
289
332
  @dasm.each_xref(addr) { |x|
@@ -328,15 +371,31 @@ class DisasmWidget < ContainerChoiceWidget
328
371
  listwindow("list of strings", list) { |i| focus_addr i[0] }
329
372
  end
330
373
 
331
- def list_xrefs(addr)
374
+ def list_xrefs(addr=nil)
332
375
  list = [['address', 'type', 'instr']]
333
- @dasm.each_xref(addr) { |xr|
334
- next if not xr.origin
335
- list << [Expression[xr.origin], "#{xr.type}#{xr.len}"]
336
- if di = @dasm.di_at(xr.origin)
337
- list.last << di.instruction
376
+ if not addr and pointed_localvar
377
+ addr = curview.hl_word
378
+ faddr = @dasm.find_function_start(curaddr)
379
+ func = @dasm.function[faddr]
380
+ if func and func.localvars_xrefs
381
+ stoff = func.localvars.index(addr)
382
+ func.localvars_xrefs[stoff].to_a.each { |a|
383
+ list << [Expression[a], '?']
384
+ if di = @dasm.di_at(a)
385
+ list.last << di.instruction
386
+ end
387
+ }
338
388
  end
339
- }
389
+ else
390
+ addr ||= pointed_addr
391
+ @dasm.each_xref(addr) { |xr|
392
+ next if not xr.origin
393
+ list << [Expression[xr.origin], "#{xr.type}#{xr.len}"]
394
+ if di = @dasm.di_at(xr.origin)
395
+ list.last << di.instruction
396
+ end
397
+ }
398
+ end
340
399
  if list.length == 1
341
400
  messagebox "no xref to #{Expression[addr]}" if addr
342
401
  else
@@ -351,8 +410,7 @@ class DisasmWidget < ContainerChoiceWidget
351
410
  }
352
411
  end
353
412
 
354
- def prompt_backtrace
355
- addr = curaddr
413
+ def prompt_backtrace(addr=curaddr)
356
414
  inputbox('expression to backtrace', :text => curview.hl_word) { |e|
357
415
  expr = IndExpression.parse_string(e)
358
416
  bd = {}
@@ -366,29 +424,132 @@ class DisasmWidget < ContainerChoiceWidget
366
424
 
367
425
  log = []
368
426
  dasm.backtrace(expr, addr, :log => log)
369
- list = [['address', 'type', 'old value', 'value']]
427
+ list = [['order', 'address', 'type', 'old value', 'value']]
428
+ order = 0
370
429
  log.each { |t, *a|
371
- list << [Expression[a[-1]], t]
430
+ order += 1
431
+ list << [('%03d' % order), Expression[a[-1]], t] rescue next
372
432
  case t
373
433
  when :start
374
434
  list.last << a[0]
375
435
  when :up
436
+ order -= 1
376
437
  list.pop
377
438
  when :di
439
+ list.last[-1] = "di #{@dasm.di_at(a[-1]).instruction rescue nil}"
378
440
  list.last << a[1] << a[0]
379
441
  when :func
380
442
  list.last << a[1] << a[0]
381
443
  when :found
382
444
  list.pop
383
- a[0].each { |e_| list << [nil, :found, Expression[e_]] }
445
+ a[0].each { |e_| list << [('%03d' % (order += 1)), nil, :found, Expression[e_]] }
384
446
  else
385
447
  list.last << a[0] << a[1..-1].inspect
386
448
  end
387
449
  }
388
- list_bghilight("backtrace #{expr} from #{Expression[addr]}", list) { |i|
389
- a = i[0].empty? ? i[2] : i[0]
450
+ list_bghilight("backtrace #{expr} from #{Expression[addr]}", list, :bghilight_index => 1) { |i|
451
+ a = i[1].empty? ? i[3] : i[1]
390
452
  focus_addr(a, nil, true)
391
- }
453
+ }
454
+ }
455
+ end
456
+
457
+ # prompt the contant to use in place of some numeric value
458
+ def prompt_constant(di=curobj)
459
+ return if not di.kind_of?(DecodedInstruction)
460
+ di.each_expr { |e|
461
+ next unless e.kind_of?(Expression)
462
+ if (e.lexpr.kind_of?(Integer) or e.lexpr.kind_of?(ExpressionString)) and
463
+ (!curview.hl_word or curview.hl_word == Expression[e.lexpr].to_s)
464
+ v = Expression[e.lexpr].reduce
465
+ lst = []
466
+ dasm.c_constants.each { |cn, cv, fm| lst << [cn, fm] if v == cv }
467
+ if not lst.empty?
468
+ default = Expression[v].to_s
469
+ lst << [default]
470
+ listwindow("constant for #{Expression[v]}", [['name', 'enum']] + lst) { |a|
471
+ if a[0] == default
472
+ e.lexpr = v
473
+ else
474
+ e.lexpr = ExpressionString.new(v, a[0], :constant)
475
+ end
476
+ session_append "if di = dasm.di_at(#{di.address.inspect}) ; di.each_expr { |e| e.lexpr = #{e.lexpr.inspect} if e.kind_of?(Expression) and e.lexpr and Expression[e.lexpr].reduce == #{v.inspect} } ; end"
477
+ gui_update
478
+ }
479
+ end
480
+ end
481
+ if (e.rexpr.kind_of? Integer or e.rexpr.kind_of?(ExpressionString)) and
482
+ (!curview.hl_word or curview.hl_word == Expression[e.rexpr].to_s)
483
+ v = Expression[e.rexpr].reduce
484
+ lst = []
485
+ dasm.c_constants.each { |cn, cv, fm| lst << [cn, fm] if v == cv }
486
+ if not lst.empty?
487
+ default = Expression[v].to_s
488
+ lst << [default]
489
+ listwindow("constant for #{Expression[v]}", [['name', 'enum']] + lst) { |a|
490
+ if a[0] == default
491
+ e.rexpr = v
492
+ else
493
+ e.rexpr = ExpressionString.new(v, a[0], :constant)
494
+ end
495
+ session_append "if di = dasm.di_at(#{di.address.inspect}) ; di.each_expr { |e| e.rexpr = #{e.rexpr.inspect} if e.kind_of?(Expression) and e.rexpr and Expression[e.rexpr].reduce == #{v.inspect} } ; end"
496
+ gui_update
497
+ }
498
+ end
499
+ end
500
+ }
501
+ end
502
+
503
+ # prompts for a structure name, autocompletes to known structures, and/or display a listwindow with
504
+ # possible completions, yields the target structure name
505
+ def prompt_c_struct(prompt, opts={})
506
+ inputbox(prompt, opts) { |st_name|
507
+ stars = ''
508
+ if opts[:allow_stars]
509
+ stars = st_name[/\**$/]
510
+ st_name[stars] = ''
511
+ end
512
+
513
+ # TODO propose typedef struct {} moo; too
514
+ sh = @dasm.c_parser.toplevel.struct
515
+ if sh[st_name].kind_of?(C::Union)
516
+ stn_list = [st_name]
517
+ else
518
+ stn_list = sh.keys.grep(String).find_all { |k| sh[k].kind_of?(C::Union) }
519
+ end
520
+
521
+ if name = stn_list.find { |n| n == st_name } || stn_list.find { |n| n.downcase == st_name.downcase }
522
+ # single match
523
+ yield(name+stars)
524
+ else
525
+ # try autocomplete
526
+ list = [['name']]
527
+ list += stn_list.sort.grep(/#{st_name}/i).map { |stn| [stn+stars] }
528
+ if list.length == 2
529
+ # single autocompletion
530
+ yield(list[1][0])
531
+ else
532
+ listwindow(prompt, list) { |ans|
533
+ yield(ans[0])
534
+ }
535
+ end
536
+ end
537
+ }
538
+ end
539
+
540
+ # prompt the struct to use for offset in a given instr
541
+ def prompt_struct_ptr(reg=curview.hl_word, addr=curaddr)
542
+ return if not reg or not @dasm.cpu.register_symbols.find { |rs| rs.to_s == reg.to_s }
543
+ reg = reg.to_sym
544
+
545
+ di = @dasm.di_at(addr)
546
+ return if not di.kind_of?(DecodedInstruction)
547
+
548
+ prompt_c_struct("struct pointed by #{reg}", :allow_stars => true) { |st|
549
+ # TODO store that info for the decompiler ?
550
+ @dasm.trace_update_reg_structptr(addr, reg, st)
551
+ session_append "dasm.trace_update_reg_structptr(#{addr.inspect}, #{reg.inspect}, #{st.inspect})"
552
+ gui_update
392
553
  }
393
554
  end
394
555
 
@@ -397,11 +558,14 @@ class DisasmWidget < ContainerChoiceWidget
397
558
  def focus_addr_autocomplete(v, show_alt=true)
398
559
  if not focus_addr(v, nil, true)
399
560
  labels = @dasm.prog_binding.map { |k, vv|
400
- [k, Expression[@dasm.normalize(vv)]] if k.downcase.include? v.downcase
561
+ [k, Expression[@dasm.normalize(vv)]] if k.downcase.include? v.downcase
401
562
  }.compact
402
563
  case labels.length
403
- when 0; focus_addr(v)
404
- when 1; focus_addr(labels[0][0])
564
+ when 0
565
+ ve = @dasm.normalize(Expression.parse(v))
566
+ focus_addr(v) if not focus_addr(ve, nil, true)
567
+ when 1
568
+ focus_addr(labels[0][0])
405
569
  else
406
570
  if labels.all? { |k, vv| vv == labels[0][1] }
407
571
  focus_addr(labels[0][0])
@@ -422,7 +586,10 @@ class DisasmWidget < ContainerChoiceWidget
422
586
 
423
587
  # run arbitrary ruby
424
588
  def prompt_run_ruby
425
- inputbox('ruby code to eval()') { |c| messagebox eval(c).inspect[0, 512], 'eval' }
589
+ inputbox('ruby code to eval()') { |c|
590
+ messagebox eval(c).inspect[0, 512], 'eval'
591
+ session_append "#eval #{c.inspect}"
592
+ }
426
593
  end
427
594
 
428
595
  # run ruby plugin
@@ -454,25 +621,41 @@ class DisasmWidget < ContainerChoiceWidget
454
621
  end
455
622
  end
456
623
 
457
- # prompts for a new name for addr
458
- def rename_label(addr)
459
- old = addr
460
- if @dasm.prog_binding[old] or old = @dasm.get_label_at(addr)
624
+ # prompts for a new name for what is under the cursor (or the current address)
625
+ def rename(what=nil)
626
+ if not what and localvar = pointed_localvar
627
+ addr = curaddr
628
+ str = localvar.str.dup
629
+ inputbox("new name for #{localvar}", :text => localvar.to_s) { |v|
630
+ if v =~ /^[a-z_][a-z0-9_]*$/i
631
+ localvar.str.replace v
632
+ session_append "pointed_localvar(dasm.decoded[#{addr.inspect}], #{str.inspect}).str.replace(#{v.inspect})"
633
+ gui_update
634
+ else messagebox("invalid local var name #{v.inspect}")
635
+ end
636
+ }
637
+ return
638
+ end
639
+
640
+ what ||= pointed_addr
641
+ if @dasm.prog_binding[what] or old = @dasm.get_label_at(what)
642
+ old ||= what
461
643
  inputbox("new name for #{old}", :text => old) { |v|
462
644
  if v == ''
463
- @dasm.del_label_at(addr)
645
+ @dasm.del_label_at(what)
646
+ session_append "dasm.del_label_at(#{what.inspect})"
464
647
  else
465
648
  @dasm.rename_label(old, v)
649
+ session_append "dasm.rename_label(#{old.inspect}, #{v.inspect})"
466
650
  end
467
651
  gui_update
468
652
  }
469
653
  else
470
- inputbox("label name for #{Expression[addr]}", :text => Expression[addr]) { |v|
654
+ inputbox("label name for #{Expression[what]}", :text => Expression[what]) { |v|
471
655
  next if v == ''
472
- @dasm.set_label_at(addr, v)
473
- if di = @dasm.di_at(addr)
474
- @dasm.split_block(di.block, di.address)
475
- end
656
+ @dasm.set_label_at(what, v)
657
+ @dasm.split_block(what)
658
+ session_append "dasm.set_label_at(#{what.inspect}, #{v.inspect}) ; dasm.split_block(#{what.inspect})"
476
659
  gui_update
477
660
  }
478
661
  end
@@ -489,7 +672,7 @@ class DisasmWidget < ContainerChoiceWidget
489
672
  true
490
673
  elsif @dasm_pause.empty?
491
674
  @dasm_pause = @dasm.addrs_todo.dup
492
- @dasm.addrs_todo.replace @dasm_pause.find_all { |a, *b| @dasm.decoded[@dasm.normalize(a)] }
675
+ @dasm.addrs_todo.replace @dasm_pause.find_all { |a| @dasm.decoded[@dasm.normalize(a[:addr])] }
493
676
  @dasm_pause -= @dasm.addrs_todo
494
677
  puts "dasm paused (#{@dasm_pause.length})"
495
678
  else
@@ -504,12 +687,34 @@ class DisasmWidget < ContainerChoiceWidget
504
687
  # toggles <41h> vs <'A'> display
505
688
  def toggle_expr_char(o)
506
689
  @dasm.toggle_expr_char(o)
690
+ session_append "dasm.toggle_expr_char(dasm.decoded[#{curaddr.inspect}])"
691
+ gui_update
692
+ end
693
+
694
+ # toggle <10h> vs <16> display
695
+ def toggle_expr_dec(o)
696
+ @dasm.toggle_expr_dec(o)
697
+ session_append "dasm.toggle_expr_dec(dasm.decoded[#{curaddr.inspect}])"
507
698
  gui_update
508
699
  end
509
700
 
510
701
  # toggle <401000h> vs <'sub_fancyname'> in the current instr display
511
702
  def toggle_expr_offset(o)
512
703
  @dasm.toggle_expr_offset(o)
704
+ session_append "dasm.toggle_expr_offset(dasm.decoded[#{curaddr.inspect}])"
705
+ gui_update
706
+ end
707
+
708
+ # toggle constant/localvar names with raw value
709
+ def toggle_expr_str(o)
710
+ @dasm.toggle_expr_str(o)
711
+ session_append "dasm.toggle_expr_str(dasm.decoded[#{curaddr.inspect}])"
712
+ gui_update
713
+ end
714
+
715
+ def name_local_vars(a)
716
+ @dasm.name_local_vars(a)
717
+ session_append "dasm.name_local_vars(#{a.inspect})"
513
718
  gui_update
514
719
  end
515
720
 
@@ -524,6 +729,7 @@ class DisasmWidget < ContainerChoiceWidget
524
729
  list = []
525
730
  @dasm.each_function_block(addr, incl_subfuncs) { |b| list << b }
526
731
  list.each { |b| @dasm.undefine_from(b) }
732
+ session_append "undefine_function(#{addr.inspect}, #{incl_subfuncs.inspect})"
527
733
  gui_update
528
734
  end
529
735
 
@@ -549,28 +755,33 @@ class DisasmWidget < ContainerChoiceWidget
549
755
  when ?/; inputbox('search word') { |w|
550
756
  next unless curview.respond_to? :hl_word
551
757
  next if w == ''
552
- curview.hl_word = w
758
+ curview.hl_word = w
759
+ curview.hl_word_re = /(.*)(#{w})/
553
760
  curview.redraw
554
761
  }
555
- when ?b; prompt_backtrace
556
- when ?c; disassemble(curview.current_address)
557
- when ?C; disassemble_fast(curview.current_address)
558
- when ?d; toggle_data(curview.current_address)
762
+ when ?b; prompt_backtrace(curaddr)
763
+ when ?c; disassemble(curaddr)
764
+ when ?C; disassemble_fast(curaddr)
765
+ when ?d; curobj.kind_of?(DecodedInstruction) ? toggle_expr_dec(curobj) : toggle_data(curaddr)
559
766
  when ?f; list_functions
560
767
  when ?g; prompt_goto
768
+ when ?k; toggle_expr_str(curobj)
769
+ when ?K; name_local_vars(curaddr)
561
770
  when ?l; list_labels
562
- when ?n; rename_label(pointed_addr)
771
+ when ?m; prompt_constant(curobj)
772
+ when ?n; rename
563
773
  when ?o; toggle_expr_offset(curobj)
564
774
  when ?p; playpause_dasm
565
775
  when ?r; toggle_expr_char(curobj)
776
+ when ?t; prompt_struct_ptr
566
777
  when ?v; $VERBOSE = ! $VERBOSE ; puts "#{'not ' if not $VERBOSE}verbose" # toggle verbose flag
567
- when ?x; list_xrefs(pointed_addr)
568
- when ?;; add_comment(curview.current_address)
778
+ when ?x; list_xrefs
779
+ when ?;; add_comment(curaddr)
569
780
 
570
781
  when ?\ ; toggle_view(:listing)
571
782
  when :tab; toggle_view(:decompile)
572
783
  when ?j; curview.keypress(:down)
573
- when ?k; curview.keypress(:up)
784
+ #when ?k; curview.keypress(:up)
574
785
  else
575
786
  p key if $DEBUG
576
787
  return @parent_widget ? @parent_widget.keypress(key) : false
@@ -578,6 +789,48 @@ class DisasmWidget < ContainerChoiceWidget
578
789
  true
579
790
  end
580
791
 
792
+ attr_accessor :session_file
793
+ def save_session(filename)
794
+ @session_file = filename
795
+ end
796
+
797
+ def replay_session(filename)
798
+ i = 0
799
+ File.readlines(filename).each { |l|
800
+ instance_eval l
801
+ i += 1
802
+ }
803
+ focus_addr(@session_focus_addr) if @session_focus_addr
804
+ puts "Session replay finished"
805
+ rescue ::Exception
806
+ puts "Session replay: error on line #{i}: #{$!.class} #{$!}"
807
+ end
808
+
809
+ # append one line to the session file
810
+ # converts addresses to hex, deletes consecutive set_focus lines
811
+ def session_append(str)
812
+ return if not session_file
813
+
814
+ # convert decimal addrs to hex
815
+ str = str.sub(/(\(|\[|= )(\d\d\d\d\d\d+)/) { $1 + ('0x%x' % $2.to_i) }
816
+
817
+ @session_lastsz_setfocus ||= nil # prevent warning
818
+ if str =~ /^@session_focus_addr = / and @session_lastsz_setfocus
819
+ # overwrite previous set_focus
820
+ File.truncate(session_file, @session_lastsz_setfocus) if File.size(session_file) == @session_lastsz
821
+ is_setfocus = true
822
+ end
823
+
824
+ File.open(session_file, 'a') { |fd| fd.puts str }
825
+
826
+ @session_lastsz = File.size(session_file)
827
+ @session_lastsz_setfocus = @session_lastsz if not is_setfocus
828
+
829
+ rescue
830
+ @session_file = nil
831
+ puts "Failed to save session, disabling (#{$!.class} #{$!})"
832
+ end
833
+
581
834
  # creates a new dasm window with the same disassembler object, focus it on addr#win
582
835
  def clone_window(*focus)
583
836
  return if not popup = DasmWindow.new
@@ -594,11 +847,27 @@ class DisasmWidget < ContainerChoiceWidget
594
847
  def dragdropfile(f)
595
848
  case f
596
849
  when /\.(c|h|cpp)$/; @dasm.parse_c_file(f)
597
- when /\.map$/; @dasm.load_map(f)
850
+ when /\.map$/; @dasm.load_map(f) ; gui_update
598
851
  when /\.rb$/; @dasm.load_plugin(f)
599
852
  else messagebox("unsupported file extension #{f}")
600
853
  end
601
854
  end
855
+
856
+ def spawn_emudbg
857
+ edbg = EmuDebugger.new(@dasm)
858
+ edbg.pc = curaddr
859
+ DbgWindow.new(edbg)
860
+ end
861
+
862
+ def extend_contextmenu(tg, menu, addr=nil)
863
+ if @parent_widget.respond_to?(:extend_contextmenu)
864
+ @parent_widget.extend_contextmenu(tg, menu, addr)
865
+ end
866
+ end
867
+
868
+ def inspect
869
+ "<DisasmWidget @%x @dasm=#{dasm.inspect}>" % object_id
870
+ end
602
871
  end
603
872
 
604
873
  # this widget is loaded in an empty DasmWindow to handle shortcuts (open file, etc)
@@ -634,17 +903,20 @@ end
634
903
 
635
904
  class DasmWindow < Window
636
905
  attr_accessor :dasm_widget, :menu
637
- def initialize_window(title = 'metasm disassembler', dasm=nil, *ep)
906
+ def initialize_window(*args)
907
+ dasm = args.grep(Disassembler).first
908
+ args -= [dasm]
909
+ title = args.find { |a| dasm ? !dasm.get_section_at(a) : a.kind_of?(::String) } || 'metasm disassembler'
910
+ ep = args - [title]
638
911
  self.title = title
639
912
  @dasm_widget = nil
640
913
  if dasm
641
- ep = ep.first if ep.length == 1 and ep.first.kind_of? Array
642
914
  display(dasm, ep)
643
915
  else
644
916
  self.widget = NoDasmWidget.new(self)
645
917
  end
646
918
  end
647
-
919
+
648
920
  def widget=(w)
649
921
  super(w || NoDasmWidget.new(self))
650
922
  end
@@ -658,10 +930,11 @@ class DasmWindow < Window
658
930
  # returns the widget
659
931
  def display(dasm, ep=[])
660
932
  @dasm_widget.terminate if @dasm_widget
661
- ep = [ep] if not ep.kind_of? Array
933
+ ep = [ep] if not ep.kind_of?(Array)
934
+ ep0 = ep.first
662
935
  @dasm_widget = DisasmWidget.new(dasm, ep)
663
936
  self.widget = @dasm_widget
664
- @dasm_widget.focus_addr(ep.first) if ep.first
937
+ @dasm_widget.focus_addr(ep0) if ep0
665
938
  @dasm_widget
666
939
  end
667
940
 
@@ -673,8 +946,11 @@ class DasmWindow < Window
673
946
  def loadfile(path, cpu='Ia32', exefmt=nil)
674
947
  if exefmt
675
948
  exefmt = Metasm.const_get(exefmt) if exefmt.kind_of? String
949
+ if exefmt.kind_of?(::Class) and exefmt.name.split('::').last == 'Shellcode'
950
+ exefmt = Shellcode.withcpu(cpu)
951
+ end
676
952
  else
677
- exefmt = AutoExe.orshellcode { cpu = Metasm.const_get(cpu) if cpu.kind_of? String ; cpu.new }
953
+ exefmt = AutoExe.orshellcode { cpu = Metasm.const_get(cpu) if cpu.kind_of? String ; cpu = cpu.new if cpu.kind_of?(::Class) ; cpu }
678
954
  end
679
955
 
680
956
  exe = exefmt.decode_file(path) { |type, str|
@@ -687,15 +963,18 @@ class DasmWindow < Window
687
963
  ret
688
964
  end
689
965
  }
690
- (@dasm_widget ? DasmWindow.new : self).display(exe.disassembler)
966
+ tg_win = self
967
+ tg_win = DasmWindow.new if @dasm_widget
968
+ tg_win.display(exe.disassembler)
969
+ tg_win.title = "#{File.basename(path)} - metasm disassembler"
691
970
  exe
692
971
  end
693
972
 
694
- def promptopen(caption='chose target binary')
695
- openfile(caption) { |exename| loadfile(exename) ; yield self if block_given? }
973
+ def promptopen(caption='choose target binary', &b)
974
+ openfile(caption) { |exename| loadfile(exename) ; b.call(self) if b }
696
975
  end
697
976
 
698
- def promptdebug(caption='chose target')
977
+ def promptdebug(caption='choose target', &b)
699
978
  l = nil
700
979
  i = inputbox(caption) { |name|
701
980
  i = nil ; l.destroy if l and not l.destroyed?
@@ -711,7 +990,7 @@ class DasmWindow < Window
711
990
  end
712
991
  DbgWindow.new(target)
713
992
  destroy if not @dasm_widget
714
- yield self if block_given?
993
+ b.call(self) if b
715
994
  }
716
995
 
717
996
  # build process list in bg (exe name resolution takes a few seconds)
@@ -741,7 +1020,7 @@ class DasmWindow < Window
741
1020
  @dasm_widget.dasm.save_file @savefile
742
1021
  return
743
1022
  end
744
- openfile('chose save file') { |file|
1023
+ savefile('choose save file') { |file|
745
1024
  @savefile = file
746
1025
  @dasm_widget.dasm.save_file(file)
747
1026
  }
@@ -784,12 +1063,13 @@ class DasmWindow < Window
784
1063
 
785
1064
  importmenu = new_menu
786
1065
  addsubmenu(importmenu, 'Load _map') {
787
- openfile('chose map file') { |file|
1066
+ openfile('choose map file') { |file|
788
1067
  @dasm_widget.dasm.load_map(File.read(file)) if @dasm_widget
1068
+ @dasm_widget.gui_update if @dasm_widget
789
1069
  } if @dasm_widget
790
1070
  }
791
1071
  addsubmenu(importmenu, 'Load _C') {
792
- openfile('chose C file') { |file|
1072
+ openfile('choose C file') { |file|
793
1073
  @dasm_widget.dasm.parse_c(File.read(file)) if @dasm_widget
794
1074
  } if @dasm_widget
795
1075
  }
@@ -797,21 +1077,21 @@ class DasmWindow < Window
797
1077
 
798
1078
  exportmenu = new_menu
799
1079
  addsubmenu(exportmenu, 'Save _map') {
800
- savefile('chose map file') { |file|
1080
+ savefile('choose map file') { |file|
801
1081
  File.open(file, 'w') { |fd|
802
1082
  fd.puts @dasm_widget.dasm.save_map
803
1083
  } if @dasm_widget
804
1084
  } if @dasm_widget
805
1085
  }
806
1086
  addsubmenu(exportmenu, 'Save _asm') {
807
- savefile('chose asm file') { |file|
1087
+ savefile('choose asm file') { |file|
808
1088
  File.open(file, 'w') { |fd|
809
1089
  fd.puts @dasm_widget.dasm
810
1090
  } if @dasm_widget
811
1091
  } if @dasm_widget
812
1092
  }
813
1093
  addsubmenu(exportmenu, 'Save _C') {
814
- savefile('chose C file') { |file|
1094
+ savefile('choose C file') { |file|
815
1095
  File.open(file, 'w') { |fd|
816
1096
  fd.puts @dasm_widget.dasm.c_parser
817
1097
  } if @dasm_widget
@@ -838,17 +1118,19 @@ class DasmWindow < Window
838
1118
  addsubmenu(actions, '_Backtrace', 'b') { @dasm_widget.prompt_backtrace }
839
1119
  addsubmenu(actions, 'List functions', 'f') { @dasm_widget.list_functions }
840
1120
  addsubmenu(actions, 'List labels', 'l') { @dasm_widget.list_labels }
841
- addsubmenu(actions, 'List xrefs', 'x') { @dasm_widget.list_xrefs(@dasm_widget.pointed_addr) }
1121
+ addsubmenu(actions, 'List xrefs', 'x') { @dasm_widget.list_xrefs }
1122
+ addsubmenu(actions, 'Find local vars', 'K') { @dasm_widget.name_local_vars(@dasm_widget.curview.current_address) }
842
1123
  addsubmenu(actions, 'Rebase') { @dasm_widget.rebase }
843
- addsubmenu(actions, 'Rename label', 'n') { @dasm_widget.rename_label(@dasm_widget.pointed_addr) }
1124
+ addsubmenu(actions, 'Rename label', 'n') { @dasm_widget.rename }
844
1125
  addsubmenu(actions, 'Decompile', '<tab>') { @dasm_widget.decompile(@dasm_widget.curview.current_address) }
845
1126
  addsubmenu(actions, 'Decompile finali_ze') { @dasm_widget.dasm.decompiler.finalize ; @dasm_widget.gui_update }
846
- addsubmenu(actions, 'Comment', ';') { @dasm_widget.decompile(@dasm_widget.curview.current_address) }
1127
+ addsubmenu(actions, 'Comment', ';') { @dasm_widget.add_comment(@dasm_widget.curview.current_address) }
847
1128
  addsubmenu(actions, '_Undefine') { @dasm_widget.dasm.undefine_from(@dasm_widget.curview.current_address) ; @dasm_widget.gui_update }
848
1129
  addsubmenu(actions, 'Unde_fine function') { @dasm_widget.undefine_function(@dasm_widget.curview.current_address) }
849
1130
  addsubmenu(actions, 'Undefine function & _subfuncs') { @dasm_widget.undefine_function(@dasm_widget.curview.current_address, true) }
850
1131
  addsubmenu(actions, 'Data', 'd') { @dasm_widget.toggle_data(@dasm_widget.curview.current_address) }
851
1132
  addsubmenu(actions, 'Pause dasm', 'p', :check) { |ck| !@dasm_widget.playpause_dasm }
1133
+ addsubmenu(actions, 'Spawn EmuDb_g', 'G') { @dasm_widget.spawn_emudbg }
852
1134
  addsubmenu(actions, 'Run ruby snippet', '^r') { promptruby }
853
1135
  addsubmenu(actions, 'Run _ruby plugin') { @dasm_widget.prompt_run_ruby_plugin }
854
1136
 
@@ -870,6 +1152,7 @@ class DasmWindow < Window
870
1152
  } if @dasm_widget
871
1153
  }
872
1154
  addsubmenu(options)
1155
+ addsubmenu(options, 'Forbid a_ll optimizations', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_all_optimizations = ck }
873
1156
  addsubmenu(options, 'Forbid decompile _types', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_decompile_types = ck }
874
1157
  addsubmenu(options, 'Forbid decompile _if/while', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_decompile_ifwhile = ck }
875
1158
  addsubmenu(options, 'Forbid decomp _optimize', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_optimize_code = ck }