metasm 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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