metasm 1.0.0

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