metasm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/BUGS +11 -0
  2. data/CREDITS +17 -0
  3. data/README +270 -0
  4. data/TODO +114 -0
  5. data/doc/code_organisation.txt +146 -0
  6. data/doc/const_missing.txt +16 -0
  7. data/doc/core_classes.txt +75 -0
  8. data/doc/feature_list.txt +53 -0
  9. data/doc/index.txt +59 -0
  10. data/doc/install_notes.txt +170 -0
  11. data/doc/style.css +3 -0
  12. data/doc/use_cases.txt +18 -0
  13. data/lib/metasm.rb +80 -0
  14. data/lib/metasm/arm.rb +12 -0
  15. data/lib/metasm/arm/debug.rb +39 -0
  16. data/lib/metasm/arm/decode.rb +167 -0
  17. data/lib/metasm/arm/encode.rb +77 -0
  18. data/lib/metasm/arm/main.rb +75 -0
  19. data/lib/metasm/arm/opcodes.rb +177 -0
  20. data/lib/metasm/arm/parse.rb +130 -0
  21. data/lib/metasm/arm/render.rb +55 -0
  22. data/lib/metasm/compile_c.rb +1457 -0
  23. data/lib/metasm/dalvik.rb +8 -0
  24. data/lib/metasm/dalvik/decode.rb +196 -0
  25. data/lib/metasm/dalvik/main.rb +60 -0
  26. data/lib/metasm/dalvik/opcodes.rb +366 -0
  27. data/lib/metasm/decode.rb +213 -0
  28. data/lib/metasm/decompile.rb +2659 -0
  29. data/lib/metasm/disassemble.rb +2068 -0
  30. data/lib/metasm/disassemble_api.rb +1280 -0
  31. data/lib/metasm/dynldr.rb +1329 -0
  32. data/lib/metasm/encode.rb +333 -0
  33. data/lib/metasm/exe_format/a_out.rb +194 -0
  34. data/lib/metasm/exe_format/autoexe.rb +82 -0
  35. data/lib/metasm/exe_format/bflt.rb +189 -0
  36. data/lib/metasm/exe_format/coff.rb +455 -0
  37. data/lib/metasm/exe_format/coff_decode.rb +901 -0
  38. data/lib/metasm/exe_format/coff_encode.rb +1078 -0
  39. data/lib/metasm/exe_format/dex.rb +457 -0
  40. data/lib/metasm/exe_format/dol.rb +145 -0
  41. data/lib/metasm/exe_format/elf.rb +923 -0
  42. data/lib/metasm/exe_format/elf_decode.rb +979 -0
  43. data/lib/metasm/exe_format/elf_encode.rb +1375 -0
  44. data/lib/metasm/exe_format/macho.rb +827 -0
  45. data/lib/metasm/exe_format/main.rb +228 -0
  46. data/lib/metasm/exe_format/mz.rb +164 -0
  47. data/lib/metasm/exe_format/nds.rb +172 -0
  48. data/lib/metasm/exe_format/pe.rb +437 -0
  49. data/lib/metasm/exe_format/serialstruct.rb +246 -0
  50. data/lib/metasm/exe_format/shellcode.rb +114 -0
  51. data/lib/metasm/exe_format/xcoff.rb +167 -0
  52. data/lib/metasm/gui.rb +23 -0
  53. data/lib/metasm/gui/cstruct.rb +373 -0
  54. data/lib/metasm/gui/dasm_coverage.rb +199 -0
  55. data/lib/metasm/gui/dasm_decomp.rb +369 -0
  56. data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
  57. data/lib/metasm/gui/dasm_graph.rb +1354 -0
  58. data/lib/metasm/gui/dasm_hex.rb +543 -0
  59. data/lib/metasm/gui/dasm_listing.rb +599 -0
  60. data/lib/metasm/gui/dasm_main.rb +906 -0
  61. data/lib/metasm/gui/dasm_opcodes.rb +291 -0
  62. data/lib/metasm/gui/debug.rb +1228 -0
  63. data/lib/metasm/gui/gtk.rb +884 -0
  64. data/lib/metasm/gui/qt.rb +495 -0
  65. data/lib/metasm/gui/win32.rb +3004 -0
  66. data/lib/metasm/gui/x11.rb +621 -0
  67. data/lib/metasm/ia32.rb +14 -0
  68. data/lib/metasm/ia32/compile_c.rb +1523 -0
  69. data/lib/metasm/ia32/debug.rb +193 -0
  70. data/lib/metasm/ia32/decode.rb +1167 -0
  71. data/lib/metasm/ia32/decompile.rb +564 -0
  72. data/lib/metasm/ia32/encode.rb +314 -0
  73. data/lib/metasm/ia32/main.rb +233 -0
  74. data/lib/metasm/ia32/opcodes.rb +872 -0
  75. data/lib/metasm/ia32/parse.rb +327 -0
  76. data/lib/metasm/ia32/render.rb +91 -0
  77. data/lib/metasm/main.rb +1193 -0
  78. data/lib/metasm/mips.rb +11 -0
  79. data/lib/metasm/mips/compile_c.rb +7 -0
  80. data/lib/metasm/mips/decode.rb +253 -0
  81. data/lib/metasm/mips/encode.rb +51 -0
  82. data/lib/metasm/mips/main.rb +72 -0
  83. data/lib/metasm/mips/opcodes.rb +443 -0
  84. data/lib/metasm/mips/parse.rb +51 -0
  85. data/lib/metasm/mips/render.rb +43 -0
  86. data/lib/metasm/os/gnu_exports.rb +270 -0
  87. data/lib/metasm/os/linux.rb +1112 -0
  88. data/lib/metasm/os/main.rb +1686 -0
  89. data/lib/metasm/os/remote.rb +527 -0
  90. data/lib/metasm/os/windows.rb +2027 -0
  91. data/lib/metasm/os/windows_exports.rb +745 -0
  92. data/lib/metasm/parse.rb +876 -0
  93. data/lib/metasm/parse_c.rb +3938 -0
  94. data/lib/metasm/pic16c/decode.rb +42 -0
  95. data/lib/metasm/pic16c/main.rb +17 -0
  96. data/lib/metasm/pic16c/opcodes.rb +68 -0
  97. data/lib/metasm/ppc.rb +11 -0
  98. data/lib/metasm/ppc/decode.rb +264 -0
  99. data/lib/metasm/ppc/decompile.rb +251 -0
  100. data/lib/metasm/ppc/encode.rb +51 -0
  101. data/lib/metasm/ppc/main.rb +129 -0
  102. data/lib/metasm/ppc/opcodes.rb +410 -0
  103. data/lib/metasm/ppc/parse.rb +52 -0
  104. data/lib/metasm/preprocessor.rb +1277 -0
  105. data/lib/metasm/render.rb +130 -0
  106. data/lib/metasm/sh4.rb +8 -0
  107. data/lib/metasm/sh4/decode.rb +336 -0
  108. data/lib/metasm/sh4/main.rb +292 -0
  109. data/lib/metasm/sh4/opcodes.rb +381 -0
  110. data/lib/metasm/x86_64.rb +12 -0
  111. data/lib/metasm/x86_64/compile_c.rb +1025 -0
  112. data/lib/metasm/x86_64/debug.rb +59 -0
  113. data/lib/metasm/x86_64/decode.rb +268 -0
  114. data/lib/metasm/x86_64/encode.rb +264 -0
  115. data/lib/metasm/x86_64/main.rb +135 -0
  116. data/lib/metasm/x86_64/opcodes.rb +118 -0
  117. data/lib/metasm/x86_64/parse.rb +68 -0
  118. data/misc/bottleneck.rb +61 -0
  119. data/misc/cheader-findpppath.rb +58 -0
  120. data/misc/hexdiff.rb +74 -0
  121. data/misc/hexdump.rb +55 -0
  122. data/misc/metasm-all.rb +13 -0
  123. data/misc/objdiff.rb +47 -0
  124. data/misc/objscan.rb +40 -0
  125. data/misc/pdfparse.rb +661 -0
  126. data/misc/ppc_pdf2oplist.rb +192 -0
  127. data/misc/tcp_proxy_hex.rb +84 -0
  128. data/misc/txt2html.rb +440 -0
  129. data/samples/a.out.rb +31 -0
  130. data/samples/asmsyntax.rb +77 -0
  131. data/samples/bindiff.rb +555 -0
  132. data/samples/compilation-steps.rb +49 -0
  133. data/samples/cparser_makestackoffset.rb +55 -0
  134. data/samples/dasm-backtrack.rb +38 -0
  135. data/samples/dasmnavig.rb +318 -0
  136. data/samples/dbg-apihook.rb +228 -0
  137. data/samples/dbghelp.rb +143 -0
  138. data/samples/disassemble-gui.rb +102 -0
  139. data/samples/disassemble.rb +133 -0
  140. data/samples/dump_upx.rb +95 -0
  141. data/samples/dynamic_ruby.rb +1929 -0
  142. data/samples/elf_list_needed.rb +46 -0
  143. data/samples/elf_listexports.rb +33 -0
  144. data/samples/elfencode.rb +25 -0
  145. data/samples/exeencode.rb +128 -0
  146. data/samples/factorize-headers-elfimports.rb +77 -0
  147. data/samples/factorize-headers-peimports.rb +109 -0
  148. data/samples/factorize-headers.rb +43 -0
  149. data/samples/gdbclient.rb +583 -0
  150. data/samples/generate_libsigs.rb +102 -0
  151. data/samples/hotfix_gtk_dbg.rb +59 -0
  152. data/samples/install_win_env.rb +78 -0
  153. data/samples/lindebug.rb +924 -0
  154. data/samples/linux_injectsyscall.rb +95 -0
  155. data/samples/machoencode.rb +31 -0
  156. data/samples/metasm-shell.rb +91 -0
  157. data/samples/pe-hook.rb +69 -0
  158. data/samples/pe-ia32-cpuid.rb +203 -0
  159. data/samples/pe-mips.rb +35 -0
  160. data/samples/pe-shutdown.rb +78 -0
  161. data/samples/pe-testrelocs.rb +51 -0
  162. data/samples/pe-testrsrc.rb +24 -0
  163. data/samples/pe_listexports.rb +31 -0
  164. data/samples/peencode.rb +19 -0
  165. data/samples/peldr.rb +494 -0
  166. data/samples/preprocess-flatten.rb +19 -0
  167. data/samples/r0trace.rb +308 -0
  168. data/samples/rubstop.rb +399 -0
  169. data/samples/scan_pt_gnu_stack.rb +54 -0
  170. data/samples/scanpeexports.rb +62 -0
  171. data/samples/shellcode-c.rb +40 -0
  172. data/samples/shellcode-dynlink.rb +146 -0
  173. data/samples/source.asm +34 -0
  174. data/samples/struct_offset.rb +47 -0
  175. data/samples/testpe.rb +32 -0
  176. data/samples/testraw.rb +45 -0
  177. data/samples/win32genloader.rb +132 -0
  178. data/samples/win32hooker-advanced.rb +169 -0
  179. data/samples/win32hooker.rb +96 -0
  180. data/samples/win32livedasm.rb +33 -0
  181. data/samples/win32remotescan.rb +133 -0
  182. data/samples/wintrace.rb +92 -0
  183. data/tests/all.rb +8 -0
  184. data/tests/dasm.rb +39 -0
  185. data/tests/dynldr.rb +35 -0
  186. data/tests/encodeddata.rb +132 -0
  187. data/tests/ia32.rb +82 -0
  188. data/tests/mips.rb +116 -0
  189. data/tests/parse_c.rb +239 -0
  190. data/tests/preprocessor.rb +269 -0
  191. data/tests/x86_64.rb +62 -0
  192. metadata +255 -0
@@ -0,0 +1,901 @@
1
+ # This file is part of Metasm, the Ruby assembly manipulation suite
2
+ # Copyright (C) 2006-2009 Yoann GUILLOT
3
+ #
4
+ # Licence is LGPL, see LICENCE in the top-level directory
5
+
6
+
7
+ require 'metasm/decode'
8
+ require 'metasm/exe_format/coff' unless defined? Metasm::COFF
9
+
10
+ module Metasm
11
+ class COFF
12
+ class OptionalHeader
13
+ decode_hook(:entrypoint) { |coff, ohdr|
14
+ coff.bitsize = (ohdr.signature == 'PE+' ? 64 : 32)
15
+ }
16
+
17
+ # decodes a COFF optional header from coff.cursection
18
+ # also decodes directories in coff.directory
19
+ def decode(coff)
20
+ return set_default_values(coff) if coff.header.size_opthdr == 0
21
+ super(coff)
22
+
23
+ nrva = @numrva
24
+ if @numrva > DIRECTORIES.length
25
+ puts "W: COFF: Invalid directories count #{@numrva}" if $VERBOSE
26
+ nrva = DIRECTORIES.length
27
+ end
28
+
29
+ coff.directory = {}
30
+ DIRECTORIES[0, nrva].each { |dir|
31
+ rva = coff.decode_word
32
+ sz = coff.decode_word
33
+ if rva != 0 or sz != 0
34
+ coff.directory[dir] = [rva, sz]
35
+ end
36
+ }
37
+ end
38
+ end
39
+
40
+ class Symbol
41
+ def decode(coff, strtab='')
42
+ n0, n1 = coff.decode_word, coff.decode_word
43
+ coff.encoded.ptr -= 8
44
+
45
+ super(coff)
46
+
47
+ if n0 == 0 and ne = strtab.index(?\0, n1)
48
+ @name = strtab[n1...ne]
49
+ end
50
+ return if @nr_aux == 0
51
+
52
+ @aux = []
53
+ @nr_aux.times { @aux << coff.encoded.read(18) }
54
+ end
55
+ end
56
+
57
+ class Section
58
+ def decode(coff)
59
+ super(coff)
60
+ coff.decode_section_body(self)
61
+ end
62
+ end
63
+
64
+ class RelocObj
65
+ def decode(coff)
66
+ super(coff)
67
+ @sym = coff.symbols[@symidx]
68
+ end
69
+ end
70
+
71
+ class ExportDirectory
72
+ # decodes a COFF export table from coff.cursection
73
+ def decode(coff)
74
+ super(coff)
75
+
76
+ if coff.sect_at_rva(@libname_p)
77
+ @libname = coff.decode_strz
78
+ end
79
+
80
+ if coff.sect_at_rva(@func_p)
81
+ @exports = []
82
+ addrs = []
83
+ @num_exports.times { addrs << coff.decode_word }
84
+ @num_exports.times { |i|
85
+ e = Export.new
86
+ e.ordinal = i + @ordinal_base
87
+ addr = addrs[i]
88
+ if addr >= coff.directory['export_table'][0] and addr < coff.directory['export_table'][0] + coff.directory['export_table'][1] and coff.sect_at_rva(addr)
89
+ name = coff.decode_strz
90
+ e.forwarder_lib, name = name.split('.', 2)
91
+ if name[0] == ?#
92
+ e.forwarder_ordinal = name[1..-1].to_i
93
+ else
94
+ e.forwarder_name = name
95
+ end
96
+ else
97
+ e.target = e.target_rva = addr
98
+ end
99
+ @exports << e
100
+ }
101
+ end
102
+ if coff.sect_at_rva(@names_p)
103
+ namep = []
104
+ num_names.times { namep << coff.decode_word }
105
+ end
106
+ if coff.sect_at_rva(@ord_p)
107
+ ords = []
108
+ num_names.times { ords << coff.decode_half }
109
+ end
110
+ if namep and ords
111
+ namep.zip(ords).each { |np, oi|
112
+ @exports[oi].name_p = np
113
+ if coff.sect_at_rva(np)
114
+ @exports[oi].name = coff.decode_strz
115
+ end
116
+ }
117
+ end
118
+ end
119
+ end
120
+
121
+ class ImportDirectory
122
+ # decodes all COFF import directories from coff.cursection
123
+ def self.decode_all(coff)
124
+ ret = []
125
+ loop do
126
+ idata = decode(coff)
127
+ break if [idata.ilt_p, idata.libname_p].uniq == [0]
128
+ ret << idata
129
+ end
130
+ ret.each { |idata| idata.decode_inner(coff) }
131
+ ret
132
+ end
133
+
134
+ # decode the tables referenced
135
+ def decode_inner(coff)
136
+ if coff.sect_at_rva(@libname_p)
137
+ @libname = coff.decode_strz
138
+ end
139
+
140
+ if coff.sect_at_rva(@ilt_p) || coff.sect_at_rva(@iat_p)
141
+ addrs = []
142
+ while (a_ = coff.decode_xword) != 0
143
+ addrs << a_
144
+ end
145
+
146
+ @imports = []
147
+
148
+ ord_mask = 1 << (coff.bitsize-1)
149
+ addrs.each { |a|
150
+ i = Import.new
151
+ if (a & ord_mask) != 0
152
+ i.ordinal = a & (~ord_mask)
153
+ else
154
+ i.hintname_p = a
155
+ if coff.sect_at_rva(a)
156
+ i.hint = coff.decode_half
157
+ i.name = coff.decode_strz
158
+ end
159
+ end
160
+ @imports << i
161
+ }
162
+ end
163
+
164
+ if coff.sect_at_rva(@iat_p)
165
+ @iat = []
166
+ while (a = coff.decode_xword) != 0
167
+ @iat << a
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ class ResourceDirectory
174
+ def decode(coff, edata = coff.curencoded, startptr = edata.ptr)
175
+ super(coff, edata)
176
+
177
+ @entries = []
178
+
179
+ nrnames = @nr_names if $DEBUG
180
+ (@nr_names+@nr_id).times {
181
+ e = Entry.new
182
+
183
+ e_id = coff.decode_word(edata)
184
+ e_ptr = coff.decode_word(edata)
185
+
186
+ if not e_id.kind_of? Integer or not e_ptr.kind_of? Integer
187
+ puts 'W: COFF: relocs in the rsrc directory?' if $VERBOSE
188
+ next
189
+ end
190
+
191
+ tmp = edata.ptr
192
+
193
+ if (e_id >> 31) == 1
194
+ if $DEBUG
195
+ nrnames -= 1
196
+ puts "W: COFF: rsrc has invalid id #{e_id}" if nrnames < 0
197
+ end
198
+ e.name_p = e_id & 0x7fff_ffff
199
+ edata.ptr = startptr + e.name_p
200
+ namelen = coff.decode_half(edata)
201
+ e.name_w = edata.read(2*namelen)
202
+ if (chrs = e.name_w.unpack('v*')).all? { |c| c >= 0 and c <= 255 }
203
+ e.name = chrs.pack('C*')
204
+ end
205
+ else
206
+ if $DEBUG
207
+ puts "W: COFF: rsrc has invalid id #{e_id}" if nrnames > 0
208
+ end
209
+ e.id = e_id
210
+ end
211
+
212
+ if (e_ptr >> 31) == 1 # subdir
213
+ e.subdir_p = e_ptr & 0x7fff_ffff
214
+ if startptr + e.subdir_p >= edata.length
215
+ puts 'W: COFF: invalid resource structure: directory too far' if $VERBOSE
216
+ else
217
+ edata.ptr = startptr + e.subdir_p
218
+ e.subdir = ResourceDirectory.new
219
+ e.subdir.decode coff, edata, startptr
220
+ end
221
+ else
222
+ e.dataentry_p = e_ptr
223
+ edata.ptr = startptr + e.dataentry_p
224
+ e.data_p = coff.decode_word(edata)
225
+ sz = coff.decode_word(edata)
226
+ e.codepage = coff.decode_word(edata)
227
+ e.reserved = coff.decode_word(edata)
228
+
229
+ if coff.sect_at_rva(e.data_p)
230
+ e.data = coff.curencoded.read(sz)
231
+ else
232
+ puts 'W: COFF: invalid resource body offset' if $VERBOSE
233
+ break
234
+ end
235
+ end
236
+
237
+ edata.ptr = tmp
238
+ @entries << e
239
+ }
240
+ end
241
+
242
+ def decode_version(coff, lang=nil)
243
+ vers = {}
244
+
245
+ decode_tllv = lambda { |ed, state|
246
+ sptr = ed.ptr
247
+ len, vlen, type = coff.decode_half(ed), coff.decode_half(ed), coff.decode_half(ed)
248
+ tagname = ''
249
+ while c = coff.decode_half(ed) and c != 0
250
+ tagname << (c&255)
251
+ end
252
+ ed.ptr = (ed.ptr + 3) / 4 * 4
253
+
254
+ case state
255
+ when 0
256
+ raise if tagname != 'VS_VERSION_INFO'
257
+ dat = ed.read(vlen)
258
+ dat.unpack('V*').zip([:signature, :strucversion, :fileversionm, :fileversionl, :prodversionm, :prodversionl, :fileflagsmask, :fileflags, :fileos, :filetype, :filesubtype, :filedatem, :filedatel]) { |v, k| vers[k] = v }
259
+ raise if vers[:signature] != 0xfeef04bd
260
+ vers.delete :signature
261
+ vers[:fileversion] = (vers.delete(:fileversionm) << 32) | vers.delete(:fileversionl)
262
+ vers[:prodversion] = (vers.delete(:prodversionm) << 32) | vers.delete(:prodversionl)
263
+ vers[:filedate] = (vers.delete(:filedatem) << 32) | vers.delete(:filedatel)
264
+ nstate = 1
265
+ when 1
266
+ nstate = case tagname
267
+ when 'StringFileInfo'; :strtable
268
+ when 'VarFileInfo'; :var
269
+ else raise
270
+ end
271
+ when :strtable
272
+ nstate = :str
273
+ when :str
274
+ val = ed.read(vlen*2).unpack('v*')
275
+ val.pop if val[-1] == 0
276
+ val = val.pack('C*') if val.all? { |c_| c_ > 0 and c_ < 256 }
277
+ vers[tagname] = val
278
+ when :var
279
+ val = ed.read(vlen).unpack('V*')
280
+ vers[tagname] = val
281
+ end
282
+
283
+ ed.ptr = (ed.ptr + 3) / 4 * 4
284
+ len = ed.length-sptr if len > ed.length-sptr
285
+ while ed.ptr < sptr+len
286
+ decode_tllv[ed, nstate]
287
+ ed.ptr = (ed.ptr + 3) / 4 * 4
288
+ end
289
+ }
290
+
291
+ return if not e = @entries.find { |e_| e_.id == TYPE.index('VERSION') }
292
+ e = e.subdir.entries.first.subdir
293
+ e = e.entries.find { |e_| e_.id == lang } || e.entries.first
294
+ ed = EncodedData.new(e.data)
295
+ decode_tllv[ed, 0]
296
+
297
+ vers
298
+ #rescue
299
+ end
300
+ end
301
+
302
+ class RelocationTable
303
+ # decodes a relocation table from coff.encoded.ptr
304
+ def decode(coff)
305
+ super(coff)
306
+ len = coff.decode_word
307
+ len -= 8
308
+ if len < 0 or len % 2 != 0
309
+ puts "W: COFF: Invalid relocation table length #{len+8}" if $VERBOSE
310
+ coff.curencoded.read(len) if len > 0
311
+ @relocs = []
312
+ return
313
+ end
314
+
315
+ @relocs = coff.curencoded.read(len).unpack(coff.endianness == :big ? 'n*' : 'v*').map { |r| Relocation.new(r&0xfff, r>>12) }
316
+ #(len/2).times { @relocs << Relocation.decode(coff) } # tables may be big, this is too slow
317
+ end
318
+ end
319
+
320
+ class TLSDirectory
321
+ def decode(coff)
322
+ super(coff)
323
+
324
+ if coff.sect_at_va(@callback_p)
325
+ @callbacks = []
326
+ while (ptr = coff.decode_xword) != 0
327
+ # __stdcall void (*ptr)(void* dllhandle, dword reason, void* reserved)
328
+ # (same as dll entrypoint)
329
+ @callbacks << (ptr - coff.optheader.image_base)
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ class LoadConfig
336
+ def decode(coff)
337
+ super(coff)
338
+
339
+ if @sehcount >= 0 and @sehcount < 100 and (@signature == 0x40 or @signature == 0x48) and coff.sect_at_va(@sehtable_p)
340
+ @safeseh = []
341
+ @sehcount.times { @safeseh << coff.decode_xword }
342
+ end
343
+ end
344
+ end
345
+
346
+ class DelayImportDirectory
347
+ def self.decode_all(coff)
348
+ ret = []
349
+ loop do
350
+ didata = decode(coff)
351
+ break if [didata.libname_p, didata.handle_p, didata.iat_p].uniq == [0]
352
+ ret << didata
353
+ end
354
+ ret.each { |didata| didata.decode_inner(coff) }
355
+ ret
356
+ end
357
+
358
+ def decode_inner(coff)
359
+ if coff.sect_at_rva(@libname_p)
360
+ @libname = coff.decode_strz
361
+ end
362
+ # TODO
363
+ end
364
+ end
365
+
366
+ class Cor20Header
367
+ def decode_all(coff)
368
+ if coff.sect_at_rva(@metadata_rva)
369
+ @metadata = coff.curencoded.read(@metadata_sz)
370
+ end
371
+ if coff.sect_at_rva(@resources_rva)
372
+ @resources = coff.curencoded.read(@resources_sz)
373
+ end
374
+ if coff.sect_at_rva(@strongnamesig_rva)
375
+ @strongnamesig = coff.curencoded.read(@strongnamesig_sz)
376
+ end
377
+ if coff.sect_at_rva(@codemgr_rva)
378
+ @codemgr = coff.curencoded.read(@codemgr_sz)
379
+ end
380
+ if coff.sect_at_rva(@vtfixup_rva)
381
+ @vtfixup = coff.curencoded.read(@vtfixup_sz)
382
+ end
383
+ if coff.sect_at_rva(@eatjumps_rva)
384
+ @eatjumps = coff.curencoded.read(@eatjumps_sz)
385
+ end
386
+ if coff.sect_at_rva(@managednativehdr_rva)
387
+ @managednativehdr = coff.curencoded.read(@managednativehdr_sz)
388
+ end
389
+ end
390
+ end
391
+
392
+ class DebugDirectory
393
+ def decode_inner(coff)
394
+ case @type
395
+ when 'CODEVIEW'
396
+ # XXX what is @pointer?
397
+ return if not coff.sect_at_rva(@addr)
398
+ sig = coff.curencoded.read(4)
399
+ case sig
400
+ when 'NB09' # CodeView 4.10
401
+ when 'NB10' # external pdb2.0
402
+ @data = NB10.decode(coff)
403
+ when 'NB11' # CodeView 5.0
404
+ when 'RSDS' # external pdb7.0
405
+ @data = RSDS.decode(coff)
406
+ end
407
+ end
408
+ end
409
+ end
410
+
411
+ attr_accessor :cursection
412
+ def curencoded
413
+ @cursection.encoded
414
+ end
415
+
416
+ def decode_byte( edata = curencoded) ; edata.decode_imm(:u8, @endianness) end
417
+ def decode_half( edata = curencoded) ; edata.decode_imm(:u16, @endianness) end
418
+ def decode_word( edata = curencoded) ; edata.decode_imm(:u32, @endianness) end
419
+ def decode_xword(edata = curencoded) ; edata.decode_imm((@bitsize == 32 ? :u32 : :u64), @endianness) end
420
+ def decode_strz( edata = curencoded) ; super(edata) ; end
421
+
422
+ # converts an RVA (offset from base address of file when loaded in memory) to the section containing it using the section table
423
+ # updates @cursection and @cursection.encoded.ptr to point to the specified address
424
+ # may return self when rva points to the coff header
425
+ # returns nil if none match, 0 never matches
426
+ def sect_at_rva(rva)
427
+ return if not rva or rva <= 0
428
+ if sections and not @sections.empty?
429
+ valign = lambda { |l| EncodedData.align_size(l, @optheader.sect_align) }
430
+ if s = @sections.find { |s_| s_.virtaddr <= rva and s_.virtaddr + valign[s_.virtsize] > rva }
431
+ s.encoded.ptr = rva - s.virtaddr
432
+ @cursection = s
433
+ elsif rva < @sections.map { |s_| s_.virtaddr }.min
434
+ @encoded.ptr = rva
435
+ @cursection = self
436
+ end
437
+ elsif rva <= @encoded.length
438
+ @encoded.ptr = rva
439
+ @cursection = self
440
+ end
441
+ end
442
+
443
+ def sect_at_va(va)
444
+ sect_at_rva(va - @optheader.image_base)
445
+ end
446
+
447
+ def label_rva(name)
448
+ if name.kind_of? Integer
449
+ name
450
+ elsif s = @sections.find { |s_| s_.encoded.export[name] }
451
+ s.virtaddr + s.encoded.export[name]
452
+ else
453
+ @encoded.export[name]
454
+ end
455
+ end
456
+
457
+ # address -> file offset
458
+ # handles LoadedPE
459
+ def addr_to_fileoff(addr)
460
+ addr -= @load_address ||= @optheader.image_base
461
+ return 0 if addr == 0 # sect_at_rva specialcases 0
462
+ if s = sect_at_rva(addr)
463
+ if s.respond_to? :virtaddr
464
+ addr - s.virtaddr + s.rawaddr
465
+ else # header
466
+ addr
467
+ end
468
+ end
469
+ end
470
+
471
+ # file offset -> memory address
472
+ # handles LoadedPE
473
+ def fileoff_to_addr(foff)
474
+ if s = @sections.find { |s_| s_.rawaddr <= foff and s_.rawaddr + s_.rawsize > foff }
475
+ s.virtaddr + foff - s.rawaddr + (@load_address ||= @optheader.image_base)
476
+ elsif foff >= 0 and foff < @optheader.headers_size
477
+ foff + (@load_address ||= @optheader.image_base)
478
+ end
479
+ end
480
+
481
+ def each_section
482
+ if @header.size_opthdr == 0
483
+ @sections.each { |s|
484
+ next if not s.encoded
485
+ l = new_label(s.name)
486
+ s.encoded.add_export(l, 0)
487
+ yield s.encoded, l
488
+ }
489
+ return
490
+ end
491
+ base = @optheader.image_base
492
+ base = 0 if not base.kind_of? Integer
493
+ yield @encoded[0, @optheader.headers_size], base
494
+ @sections.each { |s| yield s.encoded, base + s.virtaddr }
495
+ end
496
+
497
+ # decodes the COFF header, optional header, section headers
498
+ # marks entrypoint and directories as edata.expord
499
+ def decode_header
500
+ @cursection ||= self
501
+ @encoded.ptr ||= 0
502
+ @sections = []
503
+ @header.decode(self)
504
+ optoff = @encoded.ptr
505
+ @optheader.decode(self)
506
+ decode_symbols if @header.num_sym != 0 and not @header.characteristics.include? 'DEBUG_STRIPPED'
507
+ curencoded.ptr = optoff + @header.size_opthdr
508
+ decode_sections
509
+ if sect_at_rva(@optheader.entrypoint)
510
+ curencoded.add_export new_label('entrypoint')
511
+ end
512
+ (DIRECTORIES - ['certificate_table']).each { |d|
513
+ if @directory[d] and sect_at_rva(@directory[d][0])
514
+ curencoded.add_export new_label(d)
515
+ end
516
+ }
517
+ end
518
+
519
+ # decode the COFF symbol table (obj only)
520
+ def decode_symbols
521
+ endptr = @encoded.ptr = @header.ptr_sym + 18*@header.num_sym
522
+ strlen = decode_word
523
+ @encoded.ptr = endptr
524
+ strtab = @encoded.read(strlen)
525
+ @encoded.ptr = @header.ptr_sym
526
+ @symbols = []
527
+ @header.num_sym.times {
528
+ break if @encoded.ptr >= endptr or @encoded.ptr >= @encoded.length
529
+ @symbols << Symbol.decode(self, strtab)
530
+ # keep the reloc.sym_idx accurate
531
+ @symbols.last.nr_aux.times { @symbols << nil }
532
+ }
533
+ end
534
+
535
+ # decode the COFF sections
536
+ def decode_sections
537
+ @header.num_sect.times {
538
+ @sections << Section.decode(self)
539
+ }
540
+ # now decode COFF object relocations
541
+ @sections.each { |s|
542
+ next if s.relocnr == 0
543
+ curencoded.ptr = s.relocaddr
544
+ s.relocs = []
545
+ s.relocnr.times { s.relocs << RelocObj.decode(self) }
546
+ new_label 'pcrel'
547
+ s.relocs.each { |r|
548
+ case r.type
549
+ when 'DIR32'
550
+ s.encoded.reloc[r.va] = Metasm::Relocation.new(Expression[r.sym.name], :u32, @endianness)
551
+ when 'REL32'
552
+ l = new_label('pcrel')
553
+ s.encoded.add_export(l, r.va+4)
554
+ s.encoded.reloc[r.va] = Metasm::Relocation.new(Expression[r.sym.name, :-, l], :u32, @endianness)
555
+ end
556
+ }
557
+ } if not @header.characteristics.include?('RELOCS_STRIPPED')
558
+ symbols.to_a.compact.each { |sym|
559
+ next if not sym.sec_nr.kind_of? Integer
560
+ next if sym.storage != 'EXTERNAL' and (sym.storage != 'STATIC' or sym.value == 0)
561
+ next if not s = @sections[sym.sec_nr-1]
562
+ s.encoded.add_export new_label(sym.name), sym.value
563
+ }
564
+ end
565
+
566
+ # decodes a section content (allows simpler LoadedPE override)
567
+ def decode_section_body(s)
568
+ raw = EncodedData.align_size(s.rawsize, @optheader.file_align)
569
+ virt = EncodedData.align_size(s.virtsize, @optheader.sect_align)
570
+ virt = raw = s.rawsize if @header.size_opthdr == 0
571
+ s.encoded = @encoded[s.rawaddr, [raw, virt].min] || EncodedData.new
572
+ s.encoded.virtsize = virt
573
+ end
574
+
575
+ # decodes COFF export table from directory
576
+ # mark exported names as encoded.export
577
+ def decode_exports
578
+ if @directory['export_table'] and sect_at_rva(@directory['export_table'][0])
579
+ @export = ExportDirectory.decode(self)
580
+ @export.exports.to_a.each { |e|
581
+ if e.name and sect_at_rva(e.target)
582
+ name = e.name
583
+ elsif e.ordinal and sect_at_rva(e.target)
584
+ name = "ord_#{@export.libname}_#{e.ordinal}"
585
+ end
586
+ e.target = curencoded.add_export new_label(name) if name
587
+ }
588
+ end
589
+ end
590
+
591
+ # decodes COFF import tables from directory
592
+ # mark iat entries as encoded.export
593
+ def decode_imports
594
+ if @directory['import_table'] and sect_at_rva(@directory['import_table'][0])
595
+ @imports = ImportDirectory.decode_all(self)
596
+ iatlen = @bitsize/8
597
+ @imports.each { |id|
598
+ if sect_at_rva(id.iat_p)
599
+ ptr = curencoded.ptr
600
+ id.imports.each { |i|
601
+ if i.name
602
+ name = new_label i.name
603
+ elsif i.ordinal
604
+ name = new_label "ord_#{id.libname}_#{i.ordinal}"
605
+ end
606
+ if name
607
+ i.target ||= name
608
+ r = Metasm::Relocation.new(Expression[name], "u#@bitsize".to_sym, @endianness)
609
+ curencoded.reloc[ptr] = r
610
+ curencoded.add_export new_label('iat_'+name), ptr, true
611
+ end
612
+ ptr += iatlen
613
+ }
614
+ end
615
+ }
616
+ end
617
+ end
618
+
619
+ # decodes resources from directory
620
+ def decode_resources
621
+ if @directory['resource_table'] and sect_at_rva(@directory['resource_table'][0])
622
+ @resource = ResourceDirectory.decode(self)
623
+ end
624
+ end
625
+
626
+ # decode the VERSION information from the resources (file version, os, copyright etc)
627
+ def decode_version(lang=0x409)
628
+ decode_resources if not resource
629
+ resource.decode_version(self, lang)
630
+ end
631
+
632
+ # decodes certificate table
633
+ def decode_certificates
634
+ if ct = @directory['certificate_table']
635
+ @certificates = []
636
+ @cursection = self
637
+ @encoded.ptr = ct[0]
638
+ off_end = ct[0]+ct[1]
639
+ while @encoded.ptr < off_end
640
+ certlen = decode_word
641
+ certrev = decode_half
642
+ certtype = decode_half
643
+ certdat = @encoded.read(certlen)
644
+ @certificates << [certrev, certtype, certdat]
645
+ end
646
+ end
647
+ end
648
+
649
+ # decode the COM Cor20 header
650
+ def decode_com
651
+ if @directory['com_runtime'] and sect_at_rva(@directory['com_runtime'][0])
652
+ @com_header = Cor20Header.decode(self)
653
+ if sect_at_rva(@com_header.entrypoint)
654
+ curencoded.add_export new_label('com_entrypoint')
655
+ end
656
+ @com_header.decode_all(self)
657
+ end
658
+ end
659
+
660
+ # decode COFF relocation tables from directory
661
+ def decode_relocs
662
+ if @directory['base_relocation_table'] and sect_at_rva(@directory['base_relocation_table'][0])
663
+ end_ptr = curencoded.ptr + @directory['base_relocation_table'][1]
664
+ @relocations = []
665
+ while curencoded.ptr < end_ptr
666
+ @relocations << RelocationTable.decode(self)
667
+ end
668
+
669
+ # interpret as EncodedData relocations
670
+ relocfunc = ('decode_reloc_' << @header.machine.downcase).to_sym
671
+ if not respond_to? relocfunc
672
+ puts "W: COFF: unsupported relocs for architecture #{@header.machine}" if $VERBOSE
673
+ return
674
+ end
675
+ @relocations.each { |rt|
676
+ rt.relocs.each { |r|
677
+ if s = sect_at_rva(rt.base_addr + r.offset)
678
+ e, p = s.encoded, s.encoded.ptr
679
+ rel = send(relocfunc, r)
680
+ e.reloc[p] = rel if rel
681
+ end
682
+ }
683
+ }
684
+ end
685
+ end
686
+
687
+ # decodes an I386 COFF relocation pointing to encoded.ptr
688
+ def decode_reloc_i386(r)
689
+ case r.type
690
+ when 'ABSOLUTE'
691
+ when 'HIGHLOW'
692
+ addr = decode_word
693
+ if s = sect_at_va(addr)
694
+ label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}")
695
+ Metasm::Relocation.new(Expression[label], :u32, @endianness)
696
+ end
697
+ when 'DIR64'
698
+ addr = decode_xword
699
+ if s = sect_at_va(addr)
700
+ label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}")
701
+ Metasm::Relocation.new(Expression[label], :u64, @endianness)
702
+ end
703
+ else puts "W: COFF: Unsupported i386 relocation #{r.inspect}" if $VERBOSE
704
+ end
705
+ end
706
+
707
+ def decode_debug
708
+ if dd = @directory['debug'] and sect_at_rva(dd[0])
709
+ @debug = []
710
+ p0 = curencoded.ptr
711
+ while curencoded.ptr < p0 + dd[1]
712
+ @debug << DebugDirectory.decode(self)
713
+ end
714
+ @debug.each { |dbg| dbg.decode_inner(self) }
715
+ end
716
+ end
717
+
718
+ # decode TLS directory, including tls callback table
719
+ def decode_tls
720
+ if @directory['tls_table'] and sect_at_rva(@directory['tls_table'][0])
721
+ @tls = TLSDirectory.decode(self)
722
+ if s = sect_at_va(@tls.callback_p)
723
+ s.encoded.add_export 'tls_callback_table'
724
+ @tls.callbacks.each_with_index { |cb, i|
725
+ @tls.callbacks[i] = curencoded.add_export "tls_callback_#{i}" if sect_at_rva(cb)
726
+ }
727
+ end
728
+ end
729
+ end
730
+
731
+ def decode_loadconfig
732
+ if lc = @directory['load_config'] and sect_at_rva(lc[0])
733
+ @loadconfig = LoadConfig.decode(self)
734
+ end
735
+ end
736
+
737
+ def decode_delayimports
738
+ if di = @directory['delay_import_table'] and sect_at_rva(di[0])
739
+ @delayimports = DelayImportDirectory.decode_all(self)
740
+ end
741
+ end
742
+
743
+
744
+ # decodes a COFF file (headers/exports/imports/relocs/sections)
745
+ # starts at encoded.ptr
746
+ def decode
747
+ decode_header
748
+ decode_exports
749
+ decode_imports
750
+ decode_resources
751
+ decode_certificates
752
+ decode_debug
753
+ decode_tls
754
+ decode_loadconfig
755
+ decode_delayimports
756
+ decode_com
757
+ decode_relocs unless nodecode_relocs or ENV['METASM_NODECODE_RELOCS'] # decode relocs last
758
+ end
759
+
760
+ # returns a metasm CPU object corresponding to +header.machine+
761
+ def cpu_from_headers
762
+ case @header.machine
763
+ when 'I386'; Ia32.new
764
+ when 'AMD64'; X86_64.new
765
+ when 'R4000'; MIPS.new(:little)
766
+ else raise "unknown cpu #{@header.machine}"
767
+ end
768
+ end
769
+
770
+ # returns an array including the PE entrypoint and the exported functions entrypoints
771
+ # TODO filter out exported data, include safeseh ?
772
+ def get_default_entrypoints
773
+ ep = []
774
+ ep.concat @tls.callbacks.to_a if tls
775
+ ep << (@optheader.image_base + label_rva(@optheader.entrypoint))
776
+ @export.exports.to_a.each { |e|
777
+ next if e.forwarder_lib or not e.target
778
+ ep << (@optheader.image_base + label_rva(e.target))
779
+ } if export
780
+ ep
781
+ end
782
+
783
+ def dump_section_header(addr, edata)
784
+ s = @sections.find { |s_| s_.virtaddr == addr-@optheader.image_base }
785
+ s ? "\n.section #{s.name.inspect} base=#{Expression[addr]}" :
786
+ addr == @optheader.image_base ? "// exe header at #{Expression[addr]}" : super(addr, edata)
787
+ end
788
+
789
+ # returns an array of [name, addr, length, info]
790
+ def section_info
791
+ [['header', @optheader.image_base, @optheader.headers_size, nil]] +
792
+ @sections.map { |s|
793
+ [s.name, @optheader.image_base + s.virtaddr, s.virtsize, s.characteristics.join(',')]
794
+ }
795
+ end
796
+ end
797
+
798
+ class COFFArchive
799
+ class Member
800
+ def decode(ar)
801
+ @offset = ar.encoded.ptr
802
+
803
+ super(ar)
804
+ raise 'bad member header' + self.inspect if @eoh != "`\n"
805
+
806
+ @name.strip!
807
+ @date = @date.to_i
808
+ @uid = @uid.to_i
809
+ @gid = @gid.to_i
810
+ @mode = @mode.to_i(8)
811
+ @size = @size.to_i
812
+
813
+ @encoded = ar.encoded[ar.encoded.ptr, @size]
814
+ ar.encoded.ptr += @size
815
+ ar.encoded.ptr += 1 if @size & 1 == 1
816
+ end
817
+
818
+ def decode_half ; @encoded.decode_imm(:u16, :big) end
819
+ def decode_word ; @encoded.decode_imm(:u32, :big) end
820
+
821
+ def exe; AutoExe.decode(@encoded) ; end
822
+ end
823
+
824
+ def decode_half(edata = @encoded) ; edata.decode_imm(:u16, :little) end
825
+ def decode_word(edata = @encoded) ; edata.decode_imm(:u32, :little) end
826
+ def decode_strz(edata = @encoded)
827
+ i = edata.data.index(?\0, edata.ptr) || edata.data.index(?\n, edata.ptr) || (edata.length+1)
828
+ edata.read(i+1-edata.ptr).chop
829
+ end
830
+
831
+ def decode_first_linker(m)
832
+ offsets = []
833
+ names = []
834
+ m.encoded.ptr = 0
835
+ numsym = m.decode_word
836
+ numsym.times { offsets << m.decode_word }
837
+ numsym.times { names << decode_strz(m.encoded) }
838
+
839
+ # names[42] is found in object at file offset offsets[42]
840
+ # offsets are sorted by object index (all syms from 1st object, then 2nd etc)
841
+
842
+ @first_linker = names.zip(offsets) #.inject({}) { |h, (n, o)| h.update n => o }
843
+ end
844
+
845
+ def decode_second_linker(m)
846
+ names = []
847
+ mboffsets = []
848
+ indices = []
849
+ m = @members[1]
850
+ m.encoded.ptr = 0
851
+ nummb = decode_word(m.encoded)
852
+ nummb.times { mboffsets << decode_word(m.encoded) }
853
+ numsym = decode_word(m.encoded)
854
+ numsym.times { indices << decode_half(m.encoded) }
855
+ numsym.times { names << decode_strz(m.encoded) }
856
+
857
+ # names[42] is found in object at file offset mboffsets[indices[42]]
858
+ # symbols sorted by symbol name (supposed to be more efficient, but no index into string table...)
859
+
860
+ #names.zip(indices).inject({}) { |h, (n, i)| h.update n => mboffsets[i] }
861
+ @second_linker = [names, mboffsets, indices]
862
+ end
863
+
864
+ def decode_longnames(m)
865
+ @longnames = m.encoded
866
+ end
867
+
868
+ # set real name to archive members
869
+ # look it up in the name table member if needed, or just remove the trailing /
870
+ def fixup_names
871
+ @members.each { |m|
872
+ case m.name
873
+ when '/'
874
+ when '//'
875
+ when /^\/(\d+)/
876
+ @longnames.ptr = $1.to_i
877
+ m.name = decode_strz(@longnames).chomp("/")
878
+ else m.name.chomp! "/"
879
+ end
880
+ }
881
+ end
882
+
883
+ def decode
884
+ @encoded.ptr = 0
885
+ @signature = @encoded.read(8)
886
+ raise InvalidExeFormat, "Invalid COFF Archive signature #{@signature.inspect}" if @signature != "!<arch>\n"
887
+ @members = []
888
+ while @encoded.ptr < @encoded.virtsize
889
+ @members << Member.decode(self)
890
+ end
891
+ @members.each { |m|
892
+ case m.name
893
+ when '/'; @first_linker ? decode_second_linker(m) : decode_first_linker(m)
894
+ when '//'; decode_longnames(m)
895
+ else break
896
+ end
897
+ }
898
+ fixup_names
899
+ end
900
+ end
901
+ end