metasm 1.0.1 → 1.0.2

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 (235) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.hgtags +3 -0
  4. data/Gemfile +1 -0
  5. data/INSTALL +61 -0
  6. data/LICENCE +458 -0
  7. data/README +29 -21
  8. data/Rakefile +10 -0
  9. data/TODO +10 -12
  10. data/doc/code_organisation.txt +2 -0
  11. data/doc/core/DynLdr.txt +247 -0
  12. data/doc/core/ExeFormat.txt +43 -0
  13. data/doc/core/Expression.txt +220 -0
  14. data/doc/core/GNUExports.txt +27 -0
  15. data/doc/core/Ia32.txt +236 -0
  16. data/doc/core/SerialStruct.txt +108 -0
  17. data/doc/core/VirtualString.txt +145 -0
  18. data/doc/core/WindowsExports.txt +61 -0
  19. data/doc/core/index.txt +1 -0
  20. data/doc/style.css +6 -3
  21. data/doc/usage/debugger.txt +327 -0
  22. data/doc/usage/index.txt +1 -0
  23. data/doc/use_cases.txt +2 -2
  24. data/metasm.gemspec +22 -0
  25. data/{lib/metasm.rb → metasm.rb} +11 -3
  26. data/{lib/metasm → metasm}/compile_c.rb +13 -7
  27. data/metasm/cpu/arc.rb +8 -0
  28. data/metasm/cpu/arc/decode.rb +425 -0
  29. data/metasm/cpu/arc/main.rb +191 -0
  30. data/metasm/cpu/arc/opcodes.rb +588 -0
  31. data/{lib/metasm → metasm/cpu}/arm.rb +7 -5
  32. data/{lib/metasm → metasm/cpu}/arm/debug.rb +2 -2
  33. data/{lib/metasm → metasm/cpu}/arm/decode.rb +13 -12
  34. data/{lib/metasm → metasm/cpu}/arm/encode.rb +23 -8
  35. data/{lib/metasm → metasm/cpu}/arm/main.rb +0 -3
  36. data/metasm/cpu/arm/opcodes.rb +324 -0
  37. data/{lib/metasm → metasm/cpu}/arm/parse.rb +25 -13
  38. data/{lib/metasm → metasm/cpu}/arm/render.rb +2 -2
  39. data/metasm/cpu/arm64.rb +15 -0
  40. data/metasm/cpu/arm64/debug.rb +38 -0
  41. data/metasm/cpu/arm64/decode.rb +289 -0
  42. data/metasm/cpu/arm64/encode.rb +41 -0
  43. data/metasm/cpu/arm64/main.rb +105 -0
  44. data/metasm/cpu/arm64/opcodes.rb +232 -0
  45. data/metasm/cpu/arm64/parse.rb +20 -0
  46. data/metasm/cpu/arm64/render.rb +95 -0
  47. data/{lib/metasm/ppc.rb → metasm/cpu/bpf.rb} +2 -4
  48. data/metasm/cpu/bpf/decode.rb +142 -0
  49. data/metasm/cpu/bpf/main.rb +60 -0
  50. data/metasm/cpu/bpf/opcodes.rb +81 -0
  51. data/metasm/cpu/bpf/render.rb +41 -0
  52. data/metasm/cpu/cy16.rb +9 -0
  53. data/metasm/cpu/cy16/decode.rb +253 -0
  54. data/metasm/cpu/cy16/main.rb +63 -0
  55. data/metasm/cpu/cy16/opcodes.rb +78 -0
  56. data/metasm/cpu/cy16/render.rb +41 -0
  57. data/metasm/cpu/dalvik.rb +11 -0
  58. data/{lib/metasm → metasm/cpu}/dalvik/decode.rb +35 -13
  59. data/{lib/metasm → metasm/cpu}/dalvik/main.rb +51 -2
  60. data/{lib/metasm → metasm/cpu}/dalvik/opcodes.rb +19 -11
  61. data/metasm/cpu/ia32.rb +17 -0
  62. data/{lib/metasm → metasm/cpu}/ia32/compile_c.rb +5 -7
  63. data/{lib/metasm → metasm/cpu}/ia32/debug.rb +5 -5
  64. data/{lib/metasm → metasm/cpu}/ia32/decode.rb +246 -59
  65. data/{lib/metasm → metasm/cpu}/ia32/decompile.rb +7 -7
  66. data/{lib/metasm → metasm/cpu}/ia32/encode.rb +19 -13
  67. data/{lib/metasm → metasm/cpu}/ia32/main.rb +51 -8
  68. data/metasm/cpu/ia32/opcodes.rb +1424 -0
  69. data/{lib/metasm → metasm/cpu}/ia32/parse.rb +47 -16
  70. data/{lib/metasm → metasm/cpu}/ia32/render.rb +31 -4
  71. data/metasm/cpu/mips.rb +14 -0
  72. data/{lib/metasm → metasm/cpu}/mips/compile_c.rb +1 -1
  73. data/metasm/cpu/mips/debug.rb +42 -0
  74. data/{lib/metasm → metasm/cpu}/mips/decode.rb +46 -16
  75. data/{lib/metasm → metasm/cpu}/mips/encode.rb +4 -3
  76. data/{lib/metasm → metasm/cpu}/mips/main.rb +11 -4
  77. data/{lib/metasm → metasm/cpu}/mips/opcodes.rb +86 -17
  78. data/{lib/metasm → metasm/cpu}/mips/parse.rb +1 -1
  79. data/{lib/metasm → metasm/cpu}/mips/render.rb +1 -1
  80. data/{lib/metasm/dalvik.rb → metasm/cpu/msp430.rb} +1 -1
  81. data/metasm/cpu/msp430/decode.rb +247 -0
  82. data/metasm/cpu/msp430/main.rb +62 -0
  83. data/metasm/cpu/msp430/opcodes.rb +101 -0
  84. data/{lib/metasm → metasm/cpu}/pic16c/decode.rb +6 -7
  85. data/{lib/metasm → metasm/cpu}/pic16c/main.rb +0 -0
  86. data/{lib/metasm → metasm/cpu}/pic16c/opcodes.rb +1 -1
  87. data/{lib/metasm/mips.rb → metasm/cpu/ppc.rb} +4 -4
  88. data/{lib/metasm → metasm/cpu}/ppc/decode.rb +18 -12
  89. data/{lib/metasm → metasm/cpu}/ppc/decompile.rb +3 -3
  90. data/{lib/metasm → metasm/cpu}/ppc/encode.rb +2 -2
  91. data/{lib/metasm → metasm/cpu}/ppc/main.rb +17 -12
  92. data/{lib/metasm → metasm/cpu}/ppc/opcodes.rb +11 -5
  93. data/metasm/cpu/ppc/parse.rb +55 -0
  94. data/metasm/cpu/python.rb +8 -0
  95. data/metasm/cpu/python/decode.rb +136 -0
  96. data/metasm/cpu/python/main.rb +36 -0
  97. data/metasm/cpu/python/opcodes.rb +180 -0
  98. data/{lib/metasm → metasm/cpu}/sh4.rb +1 -1
  99. data/{lib/metasm → metasm/cpu}/sh4/decode.rb +48 -17
  100. data/{lib/metasm → metasm/cpu}/sh4/main.rb +13 -4
  101. data/{lib/metasm → metasm/cpu}/sh4/opcodes.rb +7 -8
  102. data/metasm/cpu/x86_64.rb +15 -0
  103. data/{lib/metasm → metasm/cpu}/x86_64/compile_c.rb +28 -17
  104. data/{lib/metasm → metasm/cpu}/x86_64/debug.rb +4 -4
  105. data/{lib/metasm → metasm/cpu}/x86_64/decode.rb +57 -15
  106. data/{lib/metasm → metasm/cpu}/x86_64/encode.rb +55 -26
  107. data/{lib/metasm → metasm/cpu}/x86_64/main.rb +14 -6
  108. data/metasm/cpu/x86_64/opcodes.rb +136 -0
  109. data/{lib/metasm → metasm/cpu}/x86_64/parse.rb +10 -2
  110. data/metasm/cpu/x86_64/render.rb +35 -0
  111. data/metasm/cpu/z80.rb +9 -0
  112. data/metasm/cpu/z80/decode.rb +313 -0
  113. data/metasm/cpu/z80/main.rb +67 -0
  114. data/metasm/cpu/z80/opcodes.rb +224 -0
  115. data/metasm/cpu/z80/render.rb +59 -0
  116. data/{lib/metasm/os/main.rb → metasm/debug.rb} +160 -401
  117. data/{lib/metasm → metasm}/decode.rb +35 -4
  118. data/{lib/metasm → metasm}/decompile.rb +15 -16
  119. data/{lib/metasm → metasm}/disassemble.rb +201 -45
  120. data/{lib/metasm → metasm}/disassemble_api.rb +651 -87
  121. data/{lib/metasm → metasm}/dynldr.rb +220 -133
  122. data/{lib/metasm → metasm}/encode.rb +10 -1
  123. data/{lib/metasm → metasm}/exe_format/a_out.rb +9 -6
  124. data/{lib/metasm → metasm}/exe_format/autoexe.rb +1 -0
  125. data/{lib/metasm → metasm}/exe_format/bflt.rb +57 -27
  126. data/{lib/metasm → metasm}/exe_format/coff.rb +11 -3
  127. data/{lib/metasm → metasm}/exe_format/coff_decode.rb +53 -20
  128. data/{lib/metasm → metasm}/exe_format/coff_encode.rb +11 -13
  129. data/{lib/metasm → metasm}/exe_format/dex.rb +13 -5
  130. data/{lib/metasm → metasm}/exe_format/dol.rb +1 -0
  131. data/{lib/metasm → metasm}/exe_format/elf.rb +93 -57
  132. data/{lib/metasm → metasm}/exe_format/elf_decode.rb +143 -34
  133. data/{lib/metasm → metasm}/exe_format/elf_encode.rb +122 -31
  134. data/metasm/exe_format/gb.rb +65 -0
  135. data/metasm/exe_format/javaclass.rb +424 -0
  136. data/{lib/metasm → metasm}/exe_format/macho.rb +204 -16
  137. data/{lib/metasm → metasm}/exe_format/main.rb +26 -3
  138. data/{lib/metasm → metasm}/exe_format/mz.rb +1 -0
  139. data/{lib/metasm → metasm}/exe_format/nds.rb +7 -4
  140. data/{lib/metasm → metasm}/exe_format/pe.rb +71 -8
  141. data/metasm/exe_format/pyc.rb +167 -0
  142. data/{lib/metasm → metasm}/exe_format/serialstruct.rb +67 -14
  143. data/{lib/metasm → metasm}/exe_format/shellcode.rb +7 -3
  144. data/metasm/exe_format/shellcode_rwx.rb +114 -0
  145. data/metasm/exe_format/swf.rb +205 -0
  146. data/{lib/metasm → metasm}/exe_format/xcoff.rb +7 -7
  147. data/metasm/exe_format/zip.rb +335 -0
  148. data/metasm/gui.rb +13 -0
  149. data/{lib/metasm → metasm}/gui/cstruct.rb +35 -41
  150. data/{lib/metasm → metasm}/gui/dasm_coverage.rb +11 -11
  151. data/{lib/metasm → metasm}/gui/dasm_decomp.rb +7 -20
  152. data/{lib/metasm → metasm}/gui/dasm_funcgraph.rb +0 -0
  153. data/metasm/gui/dasm_graph.rb +1695 -0
  154. data/{lib/metasm → metasm}/gui/dasm_hex.rb +12 -8
  155. data/{lib/metasm → metasm}/gui/dasm_listing.rb +43 -28
  156. data/{lib/metasm → metasm}/gui/dasm_main.rb +310 -53
  157. data/{lib/metasm → metasm}/gui/dasm_opcodes.rb +5 -19
  158. data/{lib/metasm → metasm}/gui/debug.rb +93 -27
  159. data/{lib/metasm → metasm}/gui/gtk.rb +162 -40
  160. data/{lib/metasm → metasm}/gui/qt.rb +12 -2
  161. data/{lib/metasm → metasm}/gui/win32.rb +179 -42
  162. data/{lib/metasm → metasm}/gui/x11.rb +59 -59
  163. data/{lib/metasm → metasm}/main.rb +389 -264
  164. data/{lib/metasm/os/remote.rb → metasm/os/gdbremote.rb} +146 -54
  165. data/{lib/metasm → metasm}/os/gnu_exports.rb +1 -1
  166. data/{lib/metasm → metasm}/os/linux.rb +628 -151
  167. data/metasm/os/main.rb +330 -0
  168. data/{lib/metasm → metasm}/os/windows.rb +132 -42
  169. data/{lib/metasm → metasm}/os/windows_exports.rb +141 -0
  170. data/{lib/metasm → metasm}/parse.rb +26 -24
  171. data/{lib/metasm → metasm}/parse_c.rb +221 -116
  172. data/{lib/metasm → metasm}/preprocessor.rb +55 -40
  173. data/{lib/metasm → metasm}/render.rb +14 -38
  174. data/misc/hexdump.rb +2 -1
  175. data/misc/lint.rb +58 -0
  176. data/misc/txt2html.rb +9 -7
  177. data/samples/bindiff.rb +3 -4
  178. data/samples/dasm-plugins/bindiff.rb +15 -0
  179. data/samples/dasm-plugins/bookmark.rb +133 -0
  180. data/samples/dasm-plugins/c_constants.rb +57 -0
  181. data/samples/dasm-plugins/colortheme_solarized.rb +125 -0
  182. data/samples/dasm-plugins/cppobj_funcall.rb +60 -0
  183. data/samples/dasm-plugins/dasm_all.rb +70 -0
  184. data/samples/dasm-plugins/demangle_cpp.rb +31 -0
  185. data/samples/dasm-plugins/deobfuscate.rb +251 -0
  186. data/samples/dasm-plugins/dump_text.rb +35 -0
  187. data/samples/dasm-plugins/export_graph_svg.rb +86 -0
  188. data/samples/dasm-plugins/findgadget.rb +75 -0
  189. data/samples/dasm-plugins/hl_opcode.rb +32 -0
  190. data/samples/dasm-plugins/hotfix_gtk_dbg.rb +19 -0
  191. data/samples/dasm-plugins/imm2off.rb +34 -0
  192. data/samples/dasm-plugins/match_libsigs.rb +93 -0
  193. data/samples/dasm-plugins/patch_file.rb +95 -0
  194. data/samples/dasm-plugins/scanfuncstart.rb +36 -0
  195. data/samples/dasm-plugins/scanxrefs.rb +26 -0
  196. data/samples/dasm-plugins/selfmodify.rb +197 -0
  197. data/samples/dasm-plugins/stringsxrefs.rb +28 -0
  198. data/samples/dasmnavig.rb +1 -1
  199. data/samples/dbg-apihook.rb +24 -9
  200. data/samples/dbg-plugins/heapscan.rb +283 -0
  201. data/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c +155 -0
  202. data/samples/dbg-plugins/heapscan/compiled_heapscan_win.c +128 -0
  203. data/samples/dbg-plugins/heapscan/graphheap.rb +616 -0
  204. data/samples/dbg-plugins/heapscan/heapscan.rb +709 -0
  205. data/samples/dbg-plugins/heapscan/winheap.h +174 -0
  206. data/samples/dbg-plugins/heapscan/winheap7.h +307 -0
  207. data/samples/dbg-plugins/trace_func.rb +214 -0
  208. data/samples/disassemble-gui.rb +35 -5
  209. data/samples/disassemble.rb +31 -6
  210. data/samples/dump_upx.rb +24 -12
  211. data/samples/dynamic_ruby.rb +12 -3
  212. data/samples/exeencode.rb +6 -5
  213. data/samples/factorize-headers-peimports.rb +1 -1
  214. data/samples/lindebug.rb +175 -381
  215. data/samples/metasm-shell.rb +1 -2
  216. data/samples/peldr.rb +2 -2
  217. data/tests/all.rb +1 -1
  218. data/tests/arc.rb +26 -0
  219. data/tests/dynldr.rb +22 -4
  220. data/tests/expression.rb +55 -0
  221. data/tests/graph_layout.rb +285 -0
  222. data/tests/ia32.rb +79 -26
  223. data/tests/mips.rb +9 -2
  224. data/tests/x86_64.rb +66 -18
  225. metadata +330 -218
  226. data/lib/metasm/arm/opcodes.rb +0 -177
  227. data/lib/metasm/gui.rb +0 -23
  228. data/lib/metasm/gui/dasm_graph.rb +0 -1354
  229. data/lib/metasm/ia32.rb +0 -14
  230. data/lib/metasm/ia32/opcodes.rb +0 -873
  231. data/lib/metasm/ppc/parse.rb +0 -52
  232. data/lib/metasm/x86_64.rb +0 -12
  233. data/lib/metasm/x86_64/opcodes.rb +0 -118
  234. data/samples/gdbclient.rb +0 -583
  235. data/samples/rubstop.rb +0 -399
@@ -0,0 +1,330 @@
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
+ require 'metasm/main'
7
+
8
+ module Metasm
9
+ # this module regroups OS-related functions
10
+ # (eg. find_process, inject_shellcode)
11
+ # a 'class' just to be able to inherit from it...
12
+ class OS
13
+ # represents a running process with a few information, and defines methods to get more interaction (#memory, #debugger)
14
+ class Process
15
+ attr_accessor :pid, :path, :modules
16
+ class Module
17
+ attr_accessor :path, :addr, :size
18
+ end
19
+
20
+ def initialize(pid=nil)
21
+ @pid = pid
22
+ end
23
+
24
+ def to_s
25
+ mod = File.basename(path) rescue nil
26
+ "#{pid}: ".ljust(6) << (mod || '<unknown>')
27
+ end
28
+ def inspect
29
+ '<Process:' + ["pid: #@pid", modules.to_a.map { |m| " #{'%X' % m.addr} #{m.path}" }].join("\n") + '>'
30
+ end
31
+ end
32
+
33
+ # returns the Process whose pid is name (if name is an Integer) or first module path includes name (string)
34
+ def self.find_process(name)
35
+ case name
36
+ when nil
37
+ when Integer
38
+ list_processes.find { |pr| pr.pid == name }
39
+ else
40
+ list_processes.find { |pr| pr.path.to_s.include? name.to_s } or
41
+ (find_process(Integer(name)) if name =~ /^(0x[0-9a-f]+|[0-9]+)$/i)
42
+ end
43
+ end
44
+
45
+ # create a new debuggee process stopped at start
46
+ def self.create_process(path)
47
+ dbg = create_debugger(path)
48
+ pr = open_process(dbg.pid)
49
+ pr.debugger = dbg
50
+ pr.memory = dbg.memory
51
+ pr
52
+ end
53
+
54
+ # return 'winos' or 'linos' depending on the underlying OS
55
+ def self.shortname
56
+ case RUBY_PLATFORM
57
+ when /mswin|mingw|cygwin/i; 'winos'
58
+ when /linux/i; 'linos'
59
+ end
60
+ end
61
+
62
+ # return the platform-specific version
63
+ def self.current
64
+ case shortname
65
+ when 'winos'; WinOS
66
+ when 'linos'; LinOS
67
+ end
68
+ end
69
+ end
70
+
71
+ # This class implements an objects that behaves like a regular string, but
72
+ # whose real data is dynamically fetched or generated on demand
73
+ # its size is immutable
74
+ # implements a page cache
75
+ # substrings are Strings (small substring) or another VirtualString
76
+ # (a kind of 'window' on the original VString, when the substring length is > 4096)
77
+ class VirtualString
78
+ # formats parameters for reading
79
+ def [](from, len=nil)
80
+ if not len and from.kind_of? Range
81
+ b = from.begin
82
+ e = from.end
83
+ b = b + length if b < 0
84
+ e = e + length if e < 0
85
+ len = e - b
86
+ len += 1 if not from.exclude_end?
87
+ from = b
88
+ end
89
+ from = from + length if from < 0
90
+
91
+ return nil if from > length or (from == length and not len)
92
+ len = length - from if len and from + len > length
93
+ return '' if len == 0
94
+
95
+ read_range(from, len)
96
+ end
97
+
98
+ # formats parameters for overwriting portion of the string
99
+ def []=(from, len, val=nil)
100
+ raise TypeError, 'cannot modify frozen virtualstring' if frozen?
101
+
102
+ if not val
103
+ val = len
104
+ len = nil
105
+ end
106
+ if not len and from.kind_of? Range
107
+ b = from.begin
108
+ e = from.end
109
+ b = b + length if b < 0
110
+ e = e + length if e < 0
111
+ len = e - b
112
+ len += 1 if not from.exclude_end?
113
+ from = b
114
+ elsif not len
115
+ len = 1
116
+ val = val.chr
117
+ end
118
+ from = from + length if from < 0
119
+
120
+ raise IndexError, 'Index out of string' if from > length
121
+ raise IndexError, 'Cannot modify virtualstring length' if val.length != len or from + len > length
122
+
123
+ write_range(from, val)
124
+ end
125
+
126
+ # returns the full raw data
127
+ def realstring
128
+ ret = ''
129
+ addr = 0
130
+ len = length
131
+ while len > @pagelength
132
+ ret << self[addr, @pagelength]
133
+ addr += @pagelength
134
+ len -= @pagelength
135
+ end
136
+ ret << self[addr, len]
137
+ end
138
+
139
+ # alias to realstring
140
+ # for bad people checking respond_to? :to_str (like String#<<)
141
+ # XXX alias does not work (not virtual (a la C++))
142
+ def to_str
143
+ realstring
144
+ end
145
+
146
+ # forwards unhandled messages to a frozen realstring
147
+ def method_missing(m, *args, &b)
148
+ if ''.respond_to? m
149
+ puts "Using VirtualString.realstring for #{m} from:", caller if $DEBUG
150
+ realstring.freeze.send(m, *args, &b)
151
+ else
152
+ super(m, *args, &b)
153
+ end
154
+ end
155
+
156
+ # avoid triggering realstring from method_missing if possible
157
+ def empty?
158
+ length == 0
159
+ end
160
+
161
+ # avoid triggering realstring from method_missing if possible
162
+ # heavily used in to find 0-terminated strings in ExeFormats
163
+ def index(chr, base=0)
164
+ return if base >= length or base <= -length
165
+ if i = self[base, 64].index(chr) or i = self[base, @pagelength].index(chr)
166
+ base + i
167
+ else
168
+ realstring.index(chr, base)
169
+ end
170
+ end
171
+
172
+ def rindex(chr, max=length)
173
+ return if max > length
174
+ if max > 64 and i = self[max-64, 64].rindex(chr)
175
+ max - 64 + i
176
+ elsif max > @pagelength and i = self[max-@pagelength, @pagelength].rindex(chr)
177
+ max - @pagelength + i
178
+ else
179
+ realstring.rindex(chr, max)
180
+ end
181
+ end
182
+
183
+ # '=~' does not go through method_missing
184
+ def =~(o)
185
+ realstring =~ o
186
+ end
187
+
188
+ # implements a read page cache
189
+
190
+ # the real address of our first byte
191
+ attr_accessor :addr_start
192
+ # our length
193
+ attr_accessor :length
194
+ # array of [addr, raw data], sorted by first == last accessed
195
+ attr_accessor :pagecache
196
+ # maximum length of self.pagecache (number of cached pages)
197
+ attr_accessor :pagecache_len
198
+ def initialize(addr_start, length)
199
+ @addr_start = addr_start
200
+ @length = length
201
+ @pagecache = []
202
+ @pagecache_len = 4
203
+ @pagelength ||= 4096 # must be (1 << x)
204
+ end
205
+
206
+ # returns wether a page is valid or not
207
+ def page_invalid?(addr)
208
+ cache_get_page(@addr_start+addr)[2]
209
+ end
210
+
211
+ # invalidates the page cache
212
+ def invalidate
213
+ @pagecache.clear
214
+ end
215
+
216
+ # returns the @pagelength-bytes page starting at addr
217
+ # return nil if the page is invalid/inaccessible
218
+ # addr is page-aligned by the caller
219
+ # addr is absolute
220
+ #def get_page(addr, len=@pagelength)
221
+ #end
222
+
223
+ # searches the cache for a page containing addr, updates if not found
224
+ def cache_get_page(addr)
225
+ addr &= ~(@pagelength-1)
226
+ i = 0
227
+ @pagecache.each { |c|
228
+ if addr == c[0]
229
+ # most recently used first
230
+ @pagecache.unshift @pagecache.delete_at(i) if i != 0
231
+ return c
232
+ end
233
+ i += 1
234
+ }
235
+ @pagecache.pop if @pagecache.length >= @pagecache_len
236
+ c = [addr]
237
+ p = get_page(addr)
238
+ c << p.to_s.ljust(@pagelength, "\0")
239
+ c << true if not p
240
+ @pagecache.unshift c
241
+ c
242
+ end
243
+
244
+ # reads a range from the page cache
245
+ # returns a new VirtualString (using dup) if the request is bigger than @pagelength bytes
246
+ def read_range(from, len)
247
+ from += @addr_start
248
+ if not len
249
+ base, page = cache_get_page(from)
250
+ page[from - base]
251
+ elsif len <= @pagelength
252
+ base, page = cache_get_page(from)
253
+ s = page[from - base, len]
254
+ if from+len-base > @pagelength # request crosses a page boundary
255
+ base, page = cache_get_page(from+len)
256
+ s << page[0, from+len-base]
257
+ end
258
+ s
259
+ else
260
+ # big request: return a new virtual page
261
+ dup(from, len)
262
+ end
263
+ end
264
+
265
+ # rewrites a segment of data
266
+ # the length written is the length of the content (a VirtualString cannot grow/shrink)
267
+ def write_range(from, content)
268
+ invalidate
269
+ rewrite_at(from + @addr_start, content)
270
+ end
271
+
272
+ # overwrites a section of the original data
273
+ #def rewrite_at(addr, content)
274
+ #end
275
+ end
276
+
277
+ # on-demand reading of a file
278
+ class VirtualFile < VirtualString
279
+ # returns a new VirtualFile of the whole file content (defaults readonly)
280
+ # returns a String if the file is small (<4096o) and readonly access
281
+ def self.read(path, mode='rb')
282
+ raise 'no filename specified' if not path
283
+ if sz = File.size(path) <= 4096 and (mode == 'rb' or mode == 'r')
284
+ File.open(path, mode) { |fd| fd.read }
285
+ else
286
+ File.open(path, mode) { |fd| new fd.dup, 0, sz }
287
+ end
288
+ end
289
+
290
+ # the underlying file descriptor
291
+ attr_accessor :fd
292
+
293
+ # creates a new virtual mapping of a section of the file
294
+ # the file descriptor must be seekable
295
+ def initialize(fd, addr_start = 0, length = nil)
296
+ @fd = fd
297
+ if not length
298
+ @fd.seek(0, File::SEEK_END)
299
+ length = @fd.tell - addr_start
300
+ end
301
+ super(addr_start, length)
302
+ end
303
+
304
+ def dup(addr = @addr_start, len = @length)
305
+ self.class.new(@fd, addr, len)
306
+ end
307
+
308
+ # reads an aligned page from the file, at file offset addr
309
+ def get_page(addr, len=@pagelength)
310
+ @fd.pos = addr
311
+ @fd.read len
312
+ end
313
+
314
+ def page_invalid?(addr)
315
+ false
316
+ end
317
+
318
+ # overwrite a section of the file
319
+ def rewrite_at(addr, data)
320
+ @fd.pos = addr
321
+ @fd.write data
322
+ end
323
+
324
+ # returns the full content of the file
325
+ def realstring
326
+ @fd.pos = @addr_start
327
+ @fd.read(@length)
328
+ end
329
+ end
330
+ end
@@ -4,6 +4,7 @@
4
4
  # Licence is LGPL, see LICENCE in the top-level directory
5
5
 
6
6
  require 'metasm/os/main'
7
+ require 'metasm/debug'
7
8
  require 'metasm/dynldr'
8
9
 
9
10
  module Metasm
@@ -125,6 +126,12 @@ typedef void *HMODULE;
125
126
  #define DBG_CONTROL_C ((DWORD )0x40010005L)
126
127
  #define DBG_CONTROL_BREAK ((DWORD )0x40010008L)
127
128
  #define DBG_COMMAND_EXCEPTION ((DWORD )0x40010009L)
129
+ #define STATUS_WX86_CONTINUE ((DWORD )0x4000001DL)
130
+ #define STATUS_WX86_SINGLE_STEP ((DWORD )0x4000001EL)
131
+ #define STATUS_WX86_BREAKPOINT ((DWORD )0x4000001FL)
132
+ #define STATUS_WX86_EXCEPTION_CONTINUE ((DWORD )0x40000020L)
133
+ #define STATUS_WX86_EXCEPTION_LASTCHANCE ((DWORD )0x40000021L)
134
+ #define STATUS_WX86_EXCEPTION_CHAIN ((DWORD )0x40000022L)
128
135
  #define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L)
129
136
  #define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L)
130
137
  #define STATUS_BREAKPOINT ((DWORD )0x80000003L)
@@ -416,7 +423,7 @@ typedef struct _CONTEXT_AMD64 {
416
423
 
417
424
  XMMREG Vector[26];
418
425
  DWORD64 VectorControl;
419
-
426
+
420
427
  DWORD64 DebugControl;
421
428
  DWORD64 LastBranchToRip;
422
429
  DWORD64 LastBranchFromRip;
@@ -490,6 +497,10 @@ typedef struct _PROCESS_INFORMATION {
490
497
  } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
491
498
 
492
499
 
500
+ WINAPI
501
+ DWORD
502
+ GetVersion(VOID);
503
+
493
504
  WINBASEAPI
494
505
  HANDLE
495
506
  WINAPI
@@ -1033,7 +1044,7 @@ OpenThreadToken (
1033
1044
  __out HANDLE *TokenHandle);
1034
1045
  EOS
1035
1046
  SE_DEBUG_NAME = 'SeDebugPrivilege'
1036
-
1047
+
1037
1048
  new_api_c <<EOS, 'ntdll'
1038
1049
  #line #{__LINE__}
1039
1050
 
@@ -1170,11 +1181,12 @@ EOS
1170
1181
  # convert a native function return value
1171
1182
  # if the native does not have the zero_not_fail attribute, convert 0
1172
1183
  # to nil, and print a message on stdout
1173
- def self.convert_ret_c2rb(fproto, ret)
1184
+ def self.convert_ret_c2rb(fproto, ret)
1174
1185
  @last_err_msg = nil
1175
1186
  if ret == 0 and not fproto.has_attribute 'zero_not_fail'
1176
1187
  # save error msg so that last_error_msg returns the same thing if called again
1177
1188
  puts "WinAPI: error in #{fproto.name}: #{@last_err_msg = last_error_msg}" if $VERBOSE
1189
+ puts caller if $DEBUG
1178
1190
  nil
1179
1191
  else super(fproto, ret)
1180
1192
  end
@@ -1254,12 +1266,12 @@ class WinOS < OS
1254
1266
  def mappings
1255
1267
  addr = 0
1256
1268
  list = []
1257
- info = WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{addrsz}")
1269
+ info = WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}")
1258
1270
  path = [0xff].pack('C') * 512
1259
1271
 
1260
1272
  hcache = heaps
1261
1273
 
1262
- while WinAPI.virtualqueryex(handle, addr, info, info.length) != 0
1274
+ while WinAPI.virtualqueryex(handle, addr, info, info.sizeof) != 0
1263
1275
  addr += info.regionsize
1264
1276
  next unless info.state & WinAPI::MEM_COMMIT > 0
1265
1277
 
@@ -1273,7 +1285,7 @@ class WinOS < OS
1273
1285
  WinAPI::PAGE_EXECUTE_READWRITE => 'rwx',
1274
1286
  WinAPI::PAGE_EXECUTE_WRITECOPY => 'rwx'
1275
1287
  }[info[:protect] & 0xff]
1276
- prot << 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0
1288
+ prot = prot.sub('r', '-') + 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0
1277
1289
  prot << 'p' if info[:type] & WinAPI::MEM_PRIVATE > 0
1278
1290
 
1279
1291
  if h = hcache[info.baseaddress]
@@ -1301,7 +1313,7 @@ class WinOS < OS
1301
1313
  @peb_base ||=
1302
1314
  if WinAPI.respond_to?(:ntqueryinformationprocess)
1303
1315
  pinfo = WinAPI.alloc_c_struct('PROCESS_BASIC_INFORMATION')
1304
- if WinAPI.ntqueryinformationprocess(handle, WinAPI::PROCESSBASICINFORMATION, pinfo, pinfo.length, 0) == 0
1316
+ if WinAPI.ntqueryinformationprocess(handle, WinAPI::PROCESSBASICINFORMATION, pinfo, pinfo.sizeof, 0) == 0
1305
1317
  pinfo.pebbaseaddress
1306
1318
  end
1307
1319
  else
@@ -1336,7 +1348,7 @@ class WinOS < OS
1336
1348
  @teb_base ||=
1337
1349
  if WinAPI.respond_to?(:ntqueryinformationthread)
1338
1350
  tinfo = WinAPI.alloc_c_struct('THREAD_BASIC_INFORMATION')
1339
- if WinAPI.ntqueryinformationthread(handle, WinAPI::THREADBASICINFORMATION, tinfo, tinfo.length, 0) == 0
1351
+ if WinAPI.ntqueryinformationthread(handle, WinAPI::THREADBASICINFORMATION, tinfo, tinfo.sizeof, 0) == 0
1340
1352
  tinfo.tebbaseaddress
1341
1353
  end
1342
1354
  else
@@ -1373,10 +1385,12 @@ class WinOS < OS
1373
1385
  @context ||= Context.new(self, :all)
1374
1386
  if block_given?
1375
1387
  suspend
1376
- @context.update
1377
- ret = yield @context
1378
- resume
1379
- ret
1388
+ begin
1389
+ @context.update
1390
+ yield @context
1391
+ ensure
1392
+ resume
1393
+ end
1380
1394
  else
1381
1395
  @context
1382
1396
  end
@@ -1386,28 +1400,34 @@ class WinOS < OS
1386
1400
  class Context
1387
1401
  def initialize(thread, kind=:all)
1388
1402
  @handle = thread.handle
1389
- tg = thread.process ? thread.process.addrsz : 32
1390
- case WinAPI.host_cpu.shortname
1391
- when 'ia32', 'x64'; tg = ((tg == 32) ? 'ia32' : 'x64')
1403
+ tg = (thread.process ? thread.process.addrsz : 32)
1404
+ hcpu = WinAPI.host_cpu.shortname
1405
+ case hcpu
1406
+ when 'ia32', 'x64'
1392
1407
  else raise "unsupported architecture #{tg}"
1393
1408
  end
1394
1409
 
1395
1410
  @getcontext = :getthreadcontext
1396
1411
  @setcontext = :setthreadcontext
1397
1412
  case tg
1398
- when 'ia32'
1413
+ when 32
1399
1414
  @context = WinAPI.alloc_c_struct('_CONTEXT_I386')
1400
1415
  @context.contextflags = WinAPI::CONTEXT_I386_ALL
1401
- if WinAPI.host_cpu.shortname == 'x64'
1416
+ if hcpu == 'x64'
1402
1417
  @getcontext = :wow64getthreadcontext
1403
1418
  @setcontext = :wow64setthreadcontext
1404
1419
  end
1405
- when 'x64'
1420
+ when 64
1406
1421
  @context = WinAPI.alloc_c_struct('_CONTEXT_AMD64')
1407
1422
  @context.contextflags = WinAPI::CONTEXT_AMD64_ALL
1408
1423
  end
1409
1424
  end
1410
1425
 
1426
+ # retrieve the actual context structure (so we can pass to API's like StackWalk64)
1427
+ def c_struct
1428
+ @context
1429
+ end
1430
+
1411
1431
  # update the context to reflect the current thread reg values
1412
1432
  # call only when the thread is suspended
1413
1433
  def update
@@ -1418,15 +1438,17 @@ class WinOS < OS
1418
1438
  case k.to_s
1419
1439
  when /^[cdefgs]s$/i
1420
1440
  @context["seg#{k}"]
1421
- when /^st(\d*)/i
1441
+ when /^st(\d?)$/i
1422
1442
  v = @context['st'][$1.to_i]
1423
- buf = v.str[v.str_off, 10]
1443
+ buf = v.str[v.stroff, 10]
1424
1444
  # TODO check this, 'D' is 8byte wide
1425
1445
  buf.unpack('D')[0]
1426
- when /^xmm(\d+)/i
1446
+ # TODO when /^ymm(\d+)$/i
1447
+ when /^xmm(\d+)$/i
1427
1448
  v = @context['xmm'][$1.to_i]
1428
1449
  (v.hi << 64) | v.lo
1429
- when /^mmx?(\d+)/i
1450
+ when /^mmx?(\d)$/i
1451
+ # XXX probably in st(0/7)
1430
1452
  @context['xmm'][$1.to_i].lo
1431
1453
  else
1432
1454
  @context[k]
@@ -1437,15 +1459,17 @@ class WinOS < OS
1437
1459
  case k.to_s
1438
1460
  when /^[cdefgs]s$/i
1439
1461
  @context["seg#{k}"] = v
1440
- when /^st(\d*)/i
1462
+ when /^st(\d?)$/i
1441
1463
  # TODO check this, 'D' is 8byte wide
1442
1464
  buf = [v, 0, 0].pack('DCC')
1443
1465
  @context['st'][$1.to_i][0, 10] = buf
1444
- when /^xmm(\d+)/i
1466
+ # TODO when /^ymm(\d+)$/i
1467
+ when /^xmm(\d+)$/i
1445
1468
  kk = @context['xmm'][$1.to_i]
1446
1469
  kk.lo = v & ((1<<64)-1)
1447
1470
  kk.hi = (v>>64) & ((1<<64)-1)
1448
- when /^mmx?(\d+)/i
1471
+ when /^mmx?(\d)$/i
1472
+ # XXX st(7-$1) ?
1449
1473
  @context['xmm'][$1.to_i].lo = v
1450
1474
  else
1451
1475
  @context[k] = v
@@ -1455,10 +1479,10 @@ class WinOS < OS
1455
1479
 
1456
1480
  def method_missing(m, *a)
1457
1481
  if m.to_s[-1] == ?=
1458
- super(m, *a) if a.length != 1
1482
+ return super(m, *a) if a.length != 1
1459
1483
  send '[]=', m.to_s[0...-1], a[0]
1460
1484
  else
1461
- super(m, *a) if a.length != 0
1485
+ return super(m, *a) if a.length != 0
1462
1486
  send '[]', m
1463
1487
  end
1464
1488
  end
@@ -1649,6 +1673,11 @@ class << self
1649
1673
  end
1650
1674
  end
1651
1675
 
1676
+ # returns the [major, minor] version of the windows os
1677
+ def version
1678
+ v = WinAPI.getversion
1679
+ [v & 0xff, (v>>8) & 0xff]
1680
+ end
1652
1681
  end # class << self
1653
1682
  end
1654
1683
 
@@ -1699,6 +1728,8 @@ class WinDebugger < Debugger
1699
1728
  attr_accessor :os_process, :os_thread,
1700
1729
  :auto_fix_fs_bug,
1701
1730
  # is current exception handled? (arg to pass to continuedbgevt)
1731
+ # if it has the special value :suspended, it means that the thread
1732
+ # is to be restarted through resume and not continuedbgevt
1702
1733
  :continuecode
1703
1734
 
1704
1735
  attr_accessor :callback_unloadlibrary, :callback_debugstring, :callback_ripevent
@@ -1747,6 +1778,7 @@ class WinDebugger < Debugger
1747
1778
  @os_process = WinOS::Process.new(processinfo.dwprocessid, processinfo.hprocess)
1748
1779
  @os_thread = WinOS::Thread.new(processinfo.dwthreadid, processinfo.hthread, @os_process)
1749
1780
  initialize_osprocess
1781
+ check_target
1750
1782
  end
1751
1783
 
1752
1784
  # called whenever we receive a handle to a new process being debugged, after initialisation of @os_process
@@ -1830,17 +1862,46 @@ class WinDebugger < Debugger
1830
1862
  end
1831
1863
 
1832
1864
  def set_reg_value(r, v)
1833
- ctx[r] = v
1865
+ if @state == :running
1866
+ suspend
1867
+ ctx[r] = v
1868
+ resume
1869
+ else
1870
+ ctx[r] = v
1871
+ end
1834
1872
  end
1835
1873
 
1836
1874
  def do_continue(*a)
1837
1875
  @cpu.dbg_disable_singlestep(self)
1838
- WinAPI.continuedebugevent(@pid, @tid, @continuecode)
1876
+ if @continuecode == :suspended
1877
+ resume
1878
+ else
1879
+ @state = :running
1880
+ WinAPI.continuedebugevent(@pid, @tid, @continuecode)
1881
+ end
1839
1882
  end
1840
1883
 
1841
1884
  def do_singlestep(*a)
1842
1885
  @cpu.dbg_enable_singlestep(self)
1843
- WinAPI.continuedebugevent(@pid, @tid, @continuecode)
1886
+ if @continuecode == :suspended
1887
+ resume
1888
+ else
1889
+ @state = :running
1890
+ WinAPI.continuedebugevent(@pid, @tid, @continuecode)
1891
+ end
1892
+ end
1893
+
1894
+ def do_enable_bpm(bp)
1895
+ @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}")
1896
+ WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof)
1897
+ # TODO save original page perms, check bpm type (:w -> vprotect(PAGE_READONLY)), handle multiple bpm on same page
1898
+ WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] | WinAPI::PAGE_GUARD, @bpm_info)
1899
+ end
1900
+
1901
+ def do_disable_bpm(bp)
1902
+ @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}")
1903
+ WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof)
1904
+ WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] & ~WinAPI::PAGE_GUARD, @bpm_info)
1844
1905
  end
1845
1906
 
1846
1907
  def update_dbgev(ev)
@@ -1855,7 +1916,7 @@ class WinDebugger < Debugger
1855
1916
  st = ev.exception
1856
1917
  str = st.exceptionrecord
1857
1918
  stf = st.dwfirstchance # non-zero = first chance
1858
-
1919
+
1859
1920
  @state = :stopped
1860
1921
  @info = "exception"
1861
1922
 
@@ -1866,7 +1927,7 @@ class WinDebugger < Debugger
1866
1927
  # DWORD NumberParameters;
1867
1928
  # ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
1868
1929
  case str.exceptioncode
1869
- when WinAPI::STATUS_ACCESS_VIOLATION
1930
+ when WinAPI::STATUS_ACCESS_VIOLATION, WinAPI::STATUS_GUARD_PAGE_VIOLATION
1870
1931
  if @auto_fix_fs_bug and ctx.fs != 0x3b
1871
1932
  # fix bug in xpsp1 where fs would get a random value in a debugee
1872
1933
  log "wdbg: #{pid}:#{tid} fix fs bug" if $DEBUG
@@ -1882,11 +1943,11 @@ class WinDebugger < Debugger
1882
1943
  addr = str.exceptioninformation[1]
1883
1944
  evt_exception(:type => 'access violation', :st => str, :firstchance => stf,
1884
1945
  :fault_addr => addr, :fault_access => mode)
1885
- when WinAPI::STATUS_BREAKPOINT
1946
+ when WinAPI::STATUS_BREAKPOINT, WinAPI::STATUS_WX86_BREAKPOINT
1886
1947
  # we must ack ntdll interrupts on process start
1887
1948
  # but we should not mask process-generated exceptions by default..
1888
1949
  evt_bpx
1889
- when WinAPI::STATUS_SINGLE_STEP
1950
+ when WinAPI::STATUS_SINGLE_STEP, WinAPI::STATUS_WX86_SINGLE_STEP
1890
1951
  evt_hwbp_singlestep
1891
1952
  else
1892
1953
  @status_name ||= WinAPI.cp.lexer.definition.keys.grep(/^STATUS_/).
@@ -1988,31 +2049,59 @@ class WinDebugger < Debugger
1988
2049
  @dbg_eventstruct ||= WinAPI.alloc_c_struct('_DEBUG_EVENT')
1989
2050
  if WinAPI.waitfordebugevent(@dbg_eventstruct, timeout) != 0
1990
2051
  update_dbgev(@dbg_eventstruct)
2052
+ true
1991
2053
  end
1992
2054
  end
1993
2055
 
2056
+ def del_tid
2057
+ # tell Windows to release the THREAD object
2058
+ WinAPI.continuedebugevent(@pid, @tid, @continuecode)
2059
+ super()
2060
+ end
2061
+
2062
+ # do nothing, windows will send us a EXIT_PROCESS event
2063
+ def del_tid_notid
2064
+ nil while do_waitfordebug(10) and !@tid
2065
+ end
2066
+
2067
+ def del_pid
2068
+ # tell Windows to release the PROCESS object
2069
+ WinAPI.debugactiveprocessstop(@pid) if WinAPI.respond_to?(:debugactiveprocessstop)
2070
+ super()
2071
+ end
2072
+
1994
2073
  def break
1995
2074
  return if @state != :running
1996
- if WinAPI.respond_to? :debugbreakprocess
1997
- WinAPI.debugbreakprocess(os_process.handle)
1998
- else
1999
- suspend
2000
- end
2075
+ # debugbreak() will create a new thread to 0xcc, but wont touch existing threads
2076
+ suspend
2001
2077
  end
2002
2078
 
2003
2079
  def suspend
2004
2080
  os_thread.suspend
2081
+ invalidate
2005
2082
  @state = :stopped
2006
2083
  @info = 'thread suspended'
2084
+ @continuecode = :suspended
2085
+ end
2086
+
2087
+ def resume
2088
+ @state = :running
2089
+ @info = nil
2090
+ os_thread.resume
2007
2091
  end
2008
2092
 
2009
2093
  def detach
2010
2094
  del_all_breakpoints
2011
- if WinAPI.respond_to? :debugactiveprocessstop
2012
- WinAPI.debugactiveprocessstop(@pid)
2013
- else
2095
+ if not WinAPI.respond_to? :debugactiveprocessstop
2014
2096
  raise 'detach not supported'
2015
2097
  end
2098
+ # handle pending bp events
2099
+ # TODO check_target needs the Breakpoint objects...
2100
+ #pid = @pid ; 50.times { check_target } ; self.pid = pid
2101
+
2102
+ # if we detach after a dbgevt and before calling continuedbgevent, the thread
2103
+ # may receive unhandled exceptions (eg BPX) and crash the process right after detach
2104
+ each_tid { do_continue if @state == :stopped }
2016
2105
  del_pid
2017
2106
  end
2018
2107
 
@@ -2021,6 +2110,7 @@ class WinDebugger < Debugger
2021
2110
  end
2022
2111
 
2023
2112
  def pass_current_exception(doit = true)
2113
+ return if @continuecode == :suspended
2024
2114
  @continuecode = (doit ? WinAPI::DBG_EXCEPTION_NOT_HANDLED : WinAPI::DBG_CONTINUE)
2025
2115
  end
2026
2116
  end