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,1078 @@
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/encode'
8
+ require 'metasm/exe_format/coff' unless defined? Metasm::COFF
9
+
10
+ module Metasm
11
+ class COFF
12
+ class OptionalHeader
13
+ # encodes an Optional header and the directories
14
+ def encode(coff)
15
+ opth = super(coff)
16
+
17
+ DIRECTORIES[0, @numrva].each { |d|
18
+ if d = coff.directory[d]
19
+ d = d.dup
20
+ d[0] = Expression[d[0], :-, coff.label_at(coff.encoded, 0)] if d[0].kind_of?(::String)
21
+ else
22
+ d = [0, 0]
23
+ end
24
+ opth << coff.encode_word(d[0]) << coff.encode_word(d[1])
25
+ }
26
+
27
+ opth
28
+ end
29
+
30
+ # find good default values for optheader members, based on coff.sections
31
+ def set_default_values(coff)
32
+ @signature ||= (coff.bitsize == 64 ? 'PE+' : 'PE')
33
+ @link_ver_maj ||= 1
34
+ @link_ver_min ||= 0
35
+ @sect_align ||= 0x1000
36
+ align = lambda { |sz| EncodedData.align_size(sz, @sect_align) }
37
+ @code_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_CODE' }.inject(0) { |sum, s| sum + align[s.virtsize] }
38
+ @data_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_DATA' }.inject(0) { |sum, s| sum + align[s.virtsize] }
39
+ @udata_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_UDATA' }.inject(0) { |sum, s| sum + align[s.virtsize] }
40
+ @entrypoint = Expression[@entrypoint, :-, coff.label_at(coff.encoded, 0)] if entrypoint and not @entrypoint.kind_of?(::Integer)
41
+ tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_CODE' }
42
+ @base_of_code ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0)
43
+ tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_DATA' }
44
+ @base_of_data ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0)
45
+ @file_align ||= 0x200
46
+ @os_ver_maj ||= 4
47
+ @subsys_maj ||= 4
48
+ @stack_reserve||= 0x100000
49
+ @stack_commit ||= 0x1000
50
+ @heap_reserve ||= 0x100000
51
+ @heap_commit ||= 0x1000
52
+ @numrva ||= DIRECTORIES.length
53
+
54
+ super(coff)
55
+ end
56
+ end
57
+
58
+ class Section
59
+ # find good default values for section header members, defines rawaddr/rawsize as new_label for later fixup
60
+ def set_default_values(coff)
61
+ @name ||= ''
62
+ @virtsize ||= @encoded.virtsize
63
+ @virtaddr ||= Expression[coff.label_at(@encoded, 0, 'sect_start'), :-, coff.label_at(coff.encoded, 0)]
64
+ @rawsize ||= coff.new_label('sect_rawsize')
65
+ @rawaddr ||= coff.new_label('sect_rawaddr')
66
+
67
+ super(coff)
68
+ end
69
+ end
70
+
71
+ class ExportDirectory
72
+ # encodes an export directory
73
+ def encode(coff)
74
+ edata = {}
75
+ %w[edata addrtable namptable ord_table libname nametable].each { |name|
76
+ edata[name] = EncodedData.new
77
+ }
78
+ label = lambda { |n| coff.label_at(edata[n], 0, n) }
79
+ rva = lambda { |n| Expression[label[n], :-, coff.label_at(coff.encoded, 0)] }
80
+ rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] }
81
+
82
+ # ordinal base: smallest number > 1 to honor ordinals, minimize gaps
83
+ olist = @exports.map { |e| e.ordinal }.compact
84
+ # start with lowest ordinal, substract all exports unused to fill ordinal sequence gaps
85
+ omin = olist.min.to_i
86
+ gaps = olist.empty? ? 0 : olist.max+1 - olist.min - olist.length
87
+ noord = @exports.length - olist.length
88
+ @ordinal_base ||= [omin - (noord - gaps), 1].max
89
+
90
+ @libname_p = rva['libname']
91
+ @num_exports = [@exports.length, @exports.map { |e| e.ordinal }.compact.max.to_i - @ordinal_base].max
92
+ @num_names = @exports.find_all { |e| e.name }.length
93
+ @func_p = rva['addrtable']
94
+ @names_p = rva['namptable']
95
+ @ord_p = rva['ord_table']
96
+
97
+ edata['edata'] << super(coff)
98
+
99
+ edata['libname'] << @libname << 0
100
+
101
+ elist = @exports.find_all { |e| e.name and not e.ordinal }.sort_by { |e| e.name }
102
+ @exports.find_all { |e| e.ordinal }.sort_by { |e| e.ordinal }.each { |e| elist.insert(e.ordinal-@ordinal_base, e) }
103
+ elist.each { |e|
104
+ if not e
105
+ # export by ordinal with gaps
106
+ # XXX test this value with the windows loader
107
+ edata['addrtable'] << coff.encode_word(0xffff_ffff)
108
+ next
109
+ end
110
+ if e.forwarder_lib
111
+ edata['addrtable'] << coff.encode_word(rva_end['nametable'])
112
+ edata['nametable'] << e.forwarder_lib << ?. <<
113
+ if not e.forwarder_name
114
+ "##{e.forwarder_ordinal}"
115
+ else
116
+ e.forwarder_name
117
+ end << 0
118
+ else
119
+ edata['addrtable'] << coff.encode_word(Expression[e.target, :-, coff.label_at(coff.encoded, 0)])
120
+ end
121
+ if e.name
122
+ edata['ord_table'] << coff.encode_half(edata['addrtable'].virtsize/4 - 1)
123
+ edata['namptable'] << coff.encode_word(rva_end['nametable'])
124
+ edata['nametable'] << e.name << 0
125
+ end
126
+ }
127
+
128
+ # sorted by alignment directives
129
+ %w[edata addrtable namptable ord_table libname nametable].inject(EncodedData.new) { |ed, name| ed << edata[name] }
130
+ end
131
+
132
+ def set_default_values(coff)
133
+ @timestamp ||= Time.now.to_i
134
+ @libname ||= 'metalib'
135
+ @ordinal_base ||= 1
136
+
137
+ super(coff)
138
+ end
139
+ end
140
+
141
+ class ImportDirectory
142
+ # encodes all import directories + iat
143
+ def self.encode(coff, ary)
144
+ edata = { 'iat' => [] }
145
+ %w[idata ilt nametable].each { |name| edata[name] = EncodedData.new }
146
+
147
+ ary.each { |i| i.encode(coff, edata) }
148
+
149
+ it = edata['idata'] <<
150
+ coff.encode_word(0) <<
151
+ coff.encode_word(0) <<
152
+ coff.encode_word(0) <<
153
+ coff.encode_word(0) <<
154
+ coff.encode_word(0) <<
155
+ edata['ilt'] <<
156
+ edata['nametable']
157
+
158
+ iat = edata['iat'] # why not fragmented ?
159
+
160
+ [it, iat]
161
+ end
162
+
163
+ # encodes an import directory + iat + names in the edata hash received as arg
164
+ def encode(coff, edata)
165
+ edata['iat'] << EncodedData.new
166
+ # edata['ilt'] = edata['iat']
167
+ label = lambda { |n| coff.label_at(edata[n], 0, n) }
168
+ rva = lambda { |n| Expression[label[n], :-, coff.label_at(coff.encoded, 0)] }
169
+ rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] }
170
+
171
+ @libname_p = rva_end['nametable']
172
+ @ilt_p = rva_end['ilt']
173
+ @iat_p ||= Expression[coff.label_at(edata['iat'].last, 0, 'iat'), :-, coff.label_at(coff.encoded, 0)]
174
+ edata['idata'] << super(coff)
175
+
176
+ edata['nametable'] << @libname << 0
177
+
178
+ ord_mask = 1 << (coff.bitsize - 1)
179
+ @imports.each { |i|
180
+ edata['iat'].last.add_export i.target, edata['iat'].last.virtsize if i.target
181
+ if i.ordinal
182
+ ptr = coff.encode_xword(Expression[i.ordinal, :|, ord_mask])
183
+ else
184
+ edata['nametable'].align 2
185
+ ptr = coff.encode_xword(rva_end['nametable'])
186
+ edata['nametable'] << coff.encode_half(i.hint || 0) << i.name << 0
187
+ end
188
+ edata['ilt'] << ptr
189
+ edata['iat'].last << ptr
190
+ }
191
+ edata['ilt'] << coff.encode_xword(0)
192
+ edata['iat'].last << coff.encode_xword(0)
193
+ end
194
+ end
195
+
196
+ class TLSDirectory
197
+ def encode(coff)
198
+ cblist = EncodedData.new
199
+ @callback_p = coff.label_at(cblist, 0, 'callback_p')
200
+ @callbacks.to_a.each { |cb|
201
+ cblist << coff.encode_xword(cb)
202
+ }
203
+ cblist << coff.encode_xword(0)
204
+
205
+ dir = super(coff)
206
+
207
+ [dir, cblist]
208
+ end
209
+
210
+ def set_default_values(coff)
211
+ @start_va ||= 0
212
+ @end_va ||= @start_va
213
+
214
+ super(coff)
215
+ end
216
+ end
217
+
218
+ class RelocationTable
219
+ # encodes a COFF relocation table
220
+ def encode(coff)
221
+ rel = super(coff) << coff.encode_word(8 + 2*@relocs.length)
222
+ @relocs.each { |r| rel << r.encode(coff) }
223
+ rel
224
+ end
225
+
226
+ def set_default_values(coff)
227
+ # @base_addr is an rva
228
+ @base_addr = Expression[@base_addr, :-, coff.label_at(coff.encoded, 0)] if @base_addr.kind_of?(::String)
229
+
230
+ # align relocation table size
231
+ if @relocs.length % 2 != 0
232
+ r = Relocation.new
233
+ r.type = 0
234
+ r.offset = 0
235
+ @relocs << r
236
+ end
237
+
238
+ super(coff)
239
+ end
240
+ end
241
+
242
+ class ResourceDirectory
243
+ # compiles ressource directories
244
+ def encode(coff, edata = nil)
245
+ if not edata
246
+ # init recursion
247
+ edata = {}
248
+ subtables = %w[table names dataentries data]
249
+ subtables.each { |n| edata[n] = EncodedData.new }
250
+ encode(coff, edata)
251
+ return subtables.inject(EncodedData.new) { |sum, n| sum << edata[n] }
252
+ end
253
+
254
+ label = lambda { |n| coff.label_at(edata[n], 0, n) }
255
+ # data 'rva' are real rvas (from start of COFF)
256
+ rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] }
257
+ # names and table 'rva' are relative to the beginning of the resource directory
258
+ off_end = lambda { |n| Expression[[label[n], :-, coff.label_at(edata['table'], 0)], :+, edata[n].virtsize] }
259
+
260
+ # build name_w if needed
261
+ @entries.each { |e| e.name_w = e.name.unpack('C*').pack('v*') if e.name and not e.name_w }
262
+
263
+ # fixup forward references to us, as subdir
264
+ edata['table'].fixup @curoff_label => edata['table'].virtsize if defined? @curoff_label
265
+
266
+ @nr_names = @entries.find_all { |e| e.name_w }.length
267
+ @nr_id = @entries.find_all { |e| e.id }.length
268
+ edata['table'] << super(coff)
269
+
270
+ # encode entries, sorted by names nocase, then id
271
+ @entries.sort_by { |e| e.name_w ? [0, e.name_w.downcase] : [1, e.id] }.each { |e|
272
+ if e.name_w
273
+ edata['table'] << coff.encode_word(Expression[off_end['names'], :|, 1 << 31])
274
+ edata['names'] << coff.encode_half(e.name_w.length/2) << e.name_w
275
+ else
276
+ edata['table'] << coff.encode_word(e.id)
277
+ end
278
+
279
+ if e.subdir
280
+ e.subdir.curoff_label = coff.new_label('rsrc_curoff')
281
+ edata['table'] << coff.encode_word(Expression[e.subdir.curoff_label, :|, 1 << 31])
282
+ else # data entry
283
+ edata['table'] << coff.encode_word(off_end['dataentries'])
284
+
285
+ edata['dataentries'] <<
286
+ coff.encode_word(rva_end['data']) <<
287
+ coff.encode_word(e.data.length) <<
288
+ coff.encode_word(e.codepage || 0) <<
289
+ coff.encode_word(e.reserved || 0)
290
+
291
+ edata['data'] << e.data
292
+ end
293
+ }
294
+
295
+ # recurse
296
+ @entries.find_all { |e| e.subdir }.each { |e| e.subdir.encode(coff, edata) }
297
+ end
298
+ end
299
+
300
+
301
+ # computes the checksum for a given COFF file
302
+ # may not work with overlapping sections
303
+ def self.checksum(str, endianness = :little)
304
+ coff = load str
305
+ coff.endianness = endianness
306
+ coff.decode_header
307
+ coff.encoded.ptr = 0
308
+
309
+ flen = 0
310
+ csum = 0
311
+ # negate old checksum
312
+ oldcs = coff.encode_word(coff.optheader.checksum)
313
+ oldcs.ptr = 0
314
+ csum -= coff.decode_half(oldcs)
315
+ csum -= coff.decode_half(oldcs)
316
+
317
+ # checksum header
318
+ raw = coff.encoded.read(coff.optheader.headers_size)
319
+ flen += coff.optheader.headers_size
320
+
321
+ coff.sections.each { |s|
322
+ coff.encoded.ptr = s.rawaddr
323
+ raw << coff.encoded.read(s.rawsize)
324
+ flen += s.rawsize
325
+ }
326
+ raw.unpack(endianness == :little ? 'v*' : 'n*').each { |s|
327
+ csum += s
328
+ csum = (csum & 0xffff) + (csum >> 16) if (csum >> 16) > 0
329
+ }
330
+ csum + flen
331
+ end
332
+
333
+
334
+ def encode_byte(w) Expression[w].encode(:u8, @endianness, (caller if $DEBUG)) end
335
+ def encode_half(w) Expression[w].encode(:u16, @endianness, (caller if $DEBUG)) end
336
+ def encode_word(w) Expression[w].encode(:u32, @endianness, (caller if $DEBUG)) end
337
+ def encode_xword(w) Expression[w].encode((@bitsize == 32 ? :u32 : :u64), @endianness, (caller if $DEBUG)) end
338
+
339
+
340
+ # adds a new compiler-generated section
341
+ def encode_append_section(s)
342
+ if (s.virtsize || s.encoded.virtsize) < 4096
343
+ # find section to merge with
344
+ # XXX check following sections for hardcoded base address ?
345
+
346
+ char = s.characteristics.dup
347
+ secs = @sections.dup
348
+ # do not merge non-discardable in discardable
349
+ if not char.delete 'MEM_DISCARDABLE'
350
+ secs.delete_if { |ss| ss.characteristics.include? 'MEM_DISCARDABLE' }
351
+ end
352
+ # do not merge shared w/ non-shared
353
+ if char.delete 'MEM_SHARED'
354
+ secs.delete_if { |ss| not ss.characteristics.include? 'MEM_SHARED' }
355
+ else
356
+ secs.delete_if { |ss| ss.characteristics.include? 'MEM_SHARED' }
357
+ end
358
+ secs.delete_if { |ss| ss.virtsize.kind_of?(::Integer) or ss.rawsize.kind_of?(::Integer) or secs[secs.index(ss)+1..-1].find { |ss_| ss_.virtaddr.kind_of?(::Integer) } }
359
+
360
+ # try to find superset of characteristics
361
+ if target = secs.find { |ss| (ss.characteristics & char) == char }
362
+ target.encoded.align 8
363
+ puts "PE: merging #{s.name} in #{target.name} (#{target.encoded.virtsize})" if $DEBUG
364
+ s.encoded = target.encoded << s.encoded
365
+ else
366
+ @sections << s
367
+ end
368
+ else
369
+ @sections << s
370
+ end
371
+ end
372
+
373
+ # encodes the export table as a new section, updates directory['export_table']
374
+ def encode_exports
375
+ edata = @export.encode self
376
+
377
+ # must include name tables (for forwarders)
378
+ @directory['export_table'] = [label_at(edata, 0, 'export_table'), edata.virtsize]
379
+
380
+ s = Section.new
381
+ s.name = '.edata'
382
+ s.encoded = edata
383
+ s.characteristics = %w[MEM_READ]
384
+ encode_append_section s
385
+ end
386
+
387
+ # encodes the import tables as a new section, updates directory['import_table'] and directory['iat']
388
+ def encode_imports
389
+ idata, iat = ImportDirectory.encode(self, @imports)
390
+
391
+ @directory['import_table'] = [label_at(idata, 0, 'idata'), idata.virtsize]
392
+
393
+ s = Section.new
394
+ s.name = '.idata'
395
+ s.encoded = idata
396
+ s.characteristics = %w[MEM_READ MEM_WRITE MEM_DISCARDABLE]
397
+ encode_append_section s
398
+
399
+ if @imports.first and @imports.first.iat_p.kind_of? Integer
400
+ ordiat = @imports.zip(iat).sort_by { |id, it| id.iat_p.kind_of?(Integer) ? id.iat_p : 1<<65 }.map { |id, it| it }
401
+ else
402
+ ordiat = iat
403
+ end
404
+
405
+ @directory['iat'] = [label_at(ordiat.first, 0, 'iat'),
406
+ Expression[label_at(ordiat.last, ordiat.last.virtsize, 'iat_end'), :-, label_at(ordiat.first, 0)]] if not ordiat.empty?
407
+
408
+ iat_s = nil
409
+
410
+ plt = Section.new
411
+ plt.name = '.plt'
412
+ plt.encoded = EncodedData.new
413
+ plt.characteristics = %w[MEM_READ MEM_EXECUTE]
414
+
415
+ @imports.zip(iat) { |id, it|
416
+ if id.iat_p.kind_of? Integer and s = @sections.find { |s_| s_.virtaddr <= id.iat_p and s_.virtaddr + (s_.virtsize || s_.encoded.virtsize) > id.iat_p }
417
+ id.iat = it # will be fixed up after encode_section
418
+ else
419
+ # XXX should not be mixed (for @directory['iat'][1])
420
+ if not iat_s
421
+ iat_s = Section.new
422
+ iat_s.name = '.iat'
423
+ iat_s.encoded = EncodedData.new
424
+ iat_s.characteristics = %w[MEM_READ MEM_WRITE]
425
+ encode_append_section iat_s
426
+ end
427
+ iat_s.encoded << it
428
+ end
429
+
430
+ id.imports.each { |i|
431
+ if i.thunk
432
+ arch_encode_thunk(plt.encoded, i)
433
+ end
434
+ }
435
+ }
436
+
437
+ encode_append_section plt if not plt.encoded.empty?
438
+ end
439
+
440
+ # encodes a thunk to imported function
441
+ def arch_encode_thunk(edata, import)
442
+ case @cpu.shortname
443
+ when 'ia32', 'x64'
444
+ shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded }
445
+ if @cpu.generate_PIC
446
+ if @cpu.shortname == 'x64'
447
+ edata << shellcode["#{import.thunk}: jmp [rip-$_+#{import.target}]"]
448
+ return
449
+ end
450
+ # sections starts with a helper function that returns the address of metasm_intern_geteip in eax (PIC)
451
+ if not @sections.find { |s| s.encoded and s.encoded.export['metasm_intern_geteip'] } and edata.empty?
452
+ edata << shellcode["metasm_intern_geteip: call 42f\n42:\npop eax\nsub eax, 42b-metasm_intern_geteip\nret"]
453
+ end
454
+ edata << shellcode["#{import.thunk}: call metasm_intern_geteip\njmp [eax+#{import.target}-metasm_intern_geteip]"]
455
+ else
456
+ edata << shellcode["#{import.thunk}: jmp [#{import.target}]"]
457
+ end
458
+ else raise EncodeError, 'E: COFF: encode import thunk: unsupported architecture'
459
+ end
460
+ end
461
+
462
+ def encode_tls
463
+ dir, cbtable = @tls.encode(self)
464
+ @directory['tls_table'] = [label_at(dir, 0, 'tls_table'), dir.virtsize]
465
+
466
+ s = Section.new
467
+ s.name = '.tls'
468
+ s.encoded = EncodedData.new << dir << cbtable
469
+ s.characteristics = %w[MEM_READ MEM_WRITE]
470
+ encode_append_section s
471
+ end
472
+
473
+ # encodes relocation tables in a new section .reloc, updates @directory['base_relocation_table']
474
+ def encode_relocs
475
+ if @relocations.empty?
476
+ rt = RelocationTable.new
477
+ rt.base_addr = 0
478
+ rt.relocs = []
479
+ @relocations << rt
480
+ end
481
+ relocs = @relocations.inject(EncodedData.new) { |edata, rt_| edata << rt_.encode(self) }
482
+
483
+ @directory['base_relocation_table'] = [label_at(relocs, 0, 'reloc_table'), relocs.virtsize]
484
+
485
+ s = Section.new
486
+ s.name = '.reloc'
487
+ s.encoded = relocs
488
+ s.characteristics = %w[MEM_READ MEM_DISCARDABLE]
489
+ encode_append_section s
490
+ end
491
+
492
+ # creates the @relocations from sections.encoded.reloc
493
+ def create_relocation_tables
494
+ @relocations = []
495
+
496
+ # create a fake binding with all exports, to find only-image_base-dependant relocs targets
497
+ # not foolproof, but works in standard cases
498
+ startaddr = curaddr = label_at(@encoded, 0, 'coff_start')
499
+ binding = {}
500
+ @sections.each { |s|
501
+ binding.update s.encoded.binding(curaddr)
502
+ curaddr = Expression[curaddr, :+, s.encoded.virtsize]
503
+ }
504
+
505
+ # for each section.encoded, make as many RelocationTables as needed
506
+ @sections.each { |s|
507
+
508
+ # rt.base_addr temporarily holds the offset from section_start, and is fixed up to rva before '@reloc << rt'
509
+ rt = RelocationTable.new
510
+
511
+ s.encoded.reloc.each { |off, rel|
512
+ # check that the relocation looks like "program_start + integer" when bound using the fake binding
513
+ # XXX allow :i32 etc
514
+ if rel.endianness == @endianness and [:u32, :a32, :u64, :a64].include?(rel.type) and
515
+ rel.target.bind(binding).reduce.kind_of?(Expression) and
516
+ Expression[rel.target, :-, startaddr].bind(binding).reduce.kind_of?(::Integer)
517
+ # winner !
518
+
519
+ # build relocation
520
+ r = RelocationTable::Relocation.new
521
+ r.offset = off & 0xfff
522
+ r.type = { :u32 => 'HIGHLOW', :u64 => 'DIR64', :a32 => 'HIGHLOW', :a64 => 'DIR64' }[rel.type]
523
+
524
+ # check if we need to start a new relocation table
525
+ if rt.base_addr and (rt.base_addr & ~0xfff) != (off & ~0xfff)
526
+ rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr]
527
+ @relocations << rt
528
+ rt = RelocationTable.new
529
+ end
530
+
531
+ # initialize reloc table base address if needed
532
+ if not rt.base_addr
533
+ rt.base_addr = off & ~0xfff
534
+ end
535
+
536
+ (rt.relocs ||= []) << r
537
+ elsif $DEBUG and not rel.target.bind(binding).reduce.kind_of?(Integer)
538
+ puts "W: COFF: Ignoring weird relocation #{rel.inspect} when building relocation tables"
539
+ end
540
+ }
541
+
542
+ if rt and rt.relocs
543
+ rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr]
544
+ @relocations << rt
545
+ end
546
+ }
547
+ end
548
+
549
+ def encode_resource
550
+ res = @resource.encode self
551
+
552
+ @directory['resource_table'] = [label_at(res, 0, 'resource_table'), res.virtsize]
553
+
554
+ s = Section.new
555
+ s.name = '.rsrc'
556
+ s.encoded = res
557
+ s.characteristics = %w[MEM_READ]
558
+ encode_append_section s
559
+ end
560
+
561
+ # initialize the header from target/cpu/etc, target in ['exe' 'dll' 'kmod' 'obj']
562
+ def pre_encode_header(target = 'exe', want_relocs=true)
563
+ target = {:bin => 'exe', :lib => 'dll', :obj => 'obj', 'sys' => 'kmod', 'drv' => 'kmod'}.fetch(target, target)
564
+
565
+ @header.machine ||= case @cpu.shortname
566
+ when 'x64'; 'AMD64'
567
+ when 'ia32'; 'I386'
568
+ end
569
+ @optheader.signature ||= case @cpu.size
570
+ when 32; 'PE'
571
+ when 64; 'PE+'
572
+ end
573
+ @bitsize = (@optheader.signature == 'PE+' ? 64 : 32)
574
+
575
+ # setup header flags
576
+ tmp = %w[LINE_NUMS_STRIPPED LOCAL_SYMS_STRIPPED DEBUG_STRIPPED] +
577
+ case target
578
+ when 'exe'; %w[EXECUTABLE_IMAGE]
579
+ when 'dll'; %w[EXECUTABLE_IMAGE DLL]
580
+ when 'kmod'; %w[EXECUTABLE_IMAGE]
581
+ when 'obj'; []
582
+ end
583
+ if @cpu.size == 32
584
+ tmp << 'x32BIT_MACHINE'
585
+ else
586
+ tmp << 'LARGE_ADDRESS_AWARE'
587
+ end
588
+ tmp << 'RELOCS_STRIPPED' if not want_relocs
589
+ @header.characteristics ||= tmp
590
+
591
+ @optheader.subsystem ||= case target
592
+ when 'exe', 'dll'; 'WINDOWS_GUI'
593
+ when 'kmod'; 'NATIVE'
594
+ end
595
+
596
+ tmp = []
597
+ tmp << 'NX_COMPAT'
598
+ tmp << 'DYNAMIC_BASE' if want_relocs
599
+ @optheader.dll_characts ||= tmp
600
+ end
601
+
602
+ # resets the values in the header that may have been
603
+ # modified by your script (eg section count, size, imagesize, etc)
604
+ # call this whenever you decode a file, modify it, and want to reencode it later
605
+ def invalidate_header
606
+ # set those values to nil, they will be
607
+ # recomputed during encode_header
608
+ [:code_size, :data_size, :udata_size, :base_of_code, :base_of_data,
609
+ :sect_align, :file_align, :image_size, :headers_size, :checksum].each { |m| @optheader.send("#{m}=", nil) }
610
+ [:num_sect, :ptr_sym, :num_sym, :size_opthdr].each { |m| @header.send("#{m}=", nil) }
611
+ end
612
+
613
+ # appends the header/optheader/directories/section table to @encoded
614
+ def encode_header
615
+ # encode section table, add CONTAINS_* flags from other characteristics flags
616
+ s_table = EncodedData.new
617
+
618
+ @sections.each { |s|
619
+ if s.characteristics.kind_of? Array and s.characteristics.include? 'MEM_READ'
620
+ if s.characteristics.include? 'MEM_EXECUTE'
621
+ s.characteristics |= ['CONTAINS_CODE']
622
+ elsif s.encoded
623
+ if s.encoded.rawsize == 0
624
+ s.characteristics |= ['CONTAINS_UDATA']
625
+ else
626
+ s.characteristics |= ['CONTAINS_DATA']
627
+ end
628
+ end
629
+ end
630
+ s.rawaddr = nil if s.rawaddr.kind_of?(::Integer) # XXX allow to force rawaddr ?
631
+ s_table << s.encode(self)
632
+ }
633
+
634
+ # encode optional header
635
+ @optheader.image_size ||= new_label('image_size')
636
+ @optheader.image_base ||= label_at(@encoded, 0)
637
+ @optheader.headers_size ||= new_label('headers_size')
638
+ @optheader.checksum ||= new_label('checksum')
639
+ @optheader.subsystem ||= 'WINDOWS_GUI'
640
+ @optheader.numrva = nil
641
+ opth = @optheader.encode(self)
642
+
643
+ # encode header
644
+ @header.machine ||= 'UNKNOWN'
645
+ @header.num_sect ||= sections.length
646
+ @header.time ||= Time.now.to_i & -255
647
+ @header.size_opthdr ||= opth.virtsize
648
+ @encoded << @header.encode(self) << opth << s_table
649
+ end
650
+
651
+ # append the section bodies to @encoded, and link the resulting binary
652
+ def encode_sections_fixup
653
+ @encoded.align @optheader.file_align
654
+ if @optheader.headers_size.kind_of?(::String)
655
+ @encoded.fixup! @optheader.headers_size => @encoded.virtsize
656
+ @optheader.headers_size = @encoded.virtsize
657
+ end
658
+
659
+ baseaddr = @optheader.image_base.kind_of?(::Integer) ? @optheader.image_base : 0x400000
660
+ binding = @encoded.binding(baseaddr)
661
+
662
+ curaddr = baseaddr + @optheader.headers_size
663
+ @sections.each { |s|
664
+ # align
665
+ curaddr = EncodedData.align_size(curaddr, @optheader.sect_align)
666
+ if s.rawaddr.kind_of?(::String)
667
+ @encoded.fixup! s.rawaddr => @encoded.virtsize
668
+ s.rawaddr = @encoded.virtsize
669
+ end
670
+ if s.virtaddr.kind_of?(::Integer)
671
+ raise "E: COFF: cannot encode section #{s.name}: hardcoded address too short" if curaddr > baseaddr + s.virtaddr
672
+ curaddr = baseaddr + s.virtaddr
673
+ end
674
+ binding.update s.encoded.binding(curaddr)
675
+ curaddr += s.virtsize
676
+
677
+ pre_sz = @encoded.virtsize
678
+ @encoded << s.encoded[0, s.encoded.rawsize]
679
+ @encoded.align @optheader.file_align
680
+ if s.rawsize.kind_of?(::String)
681
+ @encoded.fixup! s.rawsize => (@encoded.virtsize - pre_sz)
682
+ s.rawsize = @encoded.virtsize - pre_sz
683
+ end
684
+ }
685
+
686
+ # not aligned ? spec says it is, visual studio does not
687
+ binding[@optheader.image_size] = curaddr - baseaddr if @optheader.image_size.kind_of?(::String)
688
+
689
+ # patch the iat where iat_p was defined
690
+ # sort to ensure a 0-terminated will not overwrite an entry
691
+ # (try to dump notepad.exe, which has a forwarder;)
692
+ @imports.find_all { |id| id.iat_p.kind_of? Integer }.sort_by { |id| id.iat_p }.each { |id|
693
+ s = sect_at_rva(id.iat_p)
694
+ @encoded[s.rawaddr + s.encoded.ptr, id.iat.virtsize] = id.iat
695
+ binding.update id.iat.binding(baseaddr + id.iat_p)
696
+ } if imports
697
+
698
+ @encoded.fill
699
+ @encoded.fixup! binding
700
+
701
+ if @optheader.checksum.kind_of?(::String) and @encoded.reloc.length == 1
702
+ # won't work if there are other unresolved relocs
703
+ checksum = self.class.checksum(@encoded.data, @endianness)
704
+ @encoded.fixup @optheader.checksum => checksum
705
+ @optheader.checksum = checksum
706
+ end
707
+ end
708
+
709
+ # encode a COFF file, building export/import/reloc tables if needed
710
+ # creates the base relocation tables (need for references to IAT not known before)
711
+ # defaults to generating relocatable files, eg ALSR-aware
712
+ # pass want_relocs=false to avoid the file overhead induced by this
713
+ def encode(target = 'exe', want_relocs = true)
714
+ @encoded = EncodedData.new
715
+ label_at(@encoded, 0, 'coff_start')
716
+ pre_encode_header(target, want_relocs)
717
+ autoimport
718
+ encode_exports if export
719
+ encode_imports if imports
720
+ encode_resource if resource
721
+ encode_tls if tls
722
+ create_relocation_tables if want_relocs
723
+ encode_relocs if relocations
724
+ encode_header
725
+ encode_sections_fixup
726
+ @encoded.data
727
+ end
728
+
729
+ def parse_init
730
+ # ahem...
731
+ # a fake object, which when appended makes us parse '.text', which creates a real default section
732
+ # forwards to it this first appendage.
733
+ # allows the user to specify its own section if he wishes, and to use .text if he doesn't
734
+ if not defined? @cursource or not @cursource
735
+ @cursource = ::Object.new
736
+ class << @cursource
737
+ attr_accessor :coff
738
+ def <<(*a)
739
+ t = Preprocessor::Token.new(nil)
740
+ t.raw = '.text'
741
+ coff.parse_parser_instruction t
742
+ coff.cursource.send(:<<, *a)
743
+ end
744
+ end
745
+ @cursource.coff = self
746
+ end
747
+ @source ||= {}
748
+ super()
749
+ end
750
+
751
+ # handles compiler meta-instructions
752
+ #
753
+ # syntax:
754
+ # .section "<section name>" <perm list> <base>
755
+ # section name is a string (may be quoted)
756
+ # perms are in 'r' 'w' 'x' 'shared' 'discard', may be concatenated (in this order), may be prefixed by 'no' to remove the attribute for an existing section
757
+ # base is the token 'base', the token '=' and an immediate expression
758
+ # default sections:
759
+ # .text = .section '.text' rx
760
+ # .data = .section '.data' rw
761
+ # .rodata = .section '.rodata' r
762
+ # .bss = .section '.bss' rw
763
+ # .entrypoint | .entrypoint <label>
764
+ # defines the label as the program entrypoint
765
+ # without argument, creates a label used as entrypoint
766
+ # .libname "<name>"
767
+ # defines the string to be used as exported library name (should be the same as the file name, may omit extension)
768
+ # .export ["<exported_name>"] [<ordinal>] [<label_name>]
769
+ # exports the specified label with the specified name (label_name defaults to exported_name)
770
+ # if exported_name is an unquoted integer, the export is by ordinal. XXX if the ordinal starts with '0', the integer is interpreted as octal
771
+ # .import "<libname>" "<import_name|ordinal>" [<thunk_name>] [<label_name>]
772
+ # imports a symbol from a library
773
+ # if the thunk name is specified and not 'nil', the compiler will generate a thunk that can be called (in ia32, 'call thunk' == 'call [import_name]')
774
+ # the thunk is position-independent, and should be used instead of the indirect call form, for imported functions
775
+ # label_name is the label to attribute to the location that will receive the address of the imported symbol, defaults to import_name (iat_<import_name> if thunk == iname)
776
+ # .image_base <base>
777
+ # specifies the COFF prefered load address, base is an immediate expression
778
+ #
779
+ def parse_parser_instruction(instr)
780
+ readstr = lambda {
781
+ @lexer.skip_space
782
+ raise instr, 'string expected' if not t = @lexer.readtok or (t.type != :string and t.type != :quoted)
783
+ t.value || t.raw
784
+ }
785
+ check_eol = lambda {
786
+ @lexer.skip_space
787
+ raise instr, 'eol expected' if t = @lexer.nexttok and t.type != :eol
788
+ }
789
+ case instr.raw.downcase
790
+ when '.text', '.data', '.rodata', '.bss'
791
+ sname = instr.raw.downcase
792
+ if not @sections.find { |s| s.name == sname }
793
+ s = Section.new
794
+ s.name = sname
795
+ s.encoded = EncodedData.new
796
+ s.characteristics = case sname
797
+ when '.text'; %w[MEM_READ MEM_EXECUTE]
798
+ when '.data', '.bss'; %w[MEM_READ MEM_WRITE]
799
+ when '.rodata'; %w[MEM_READ]
800
+ end
801
+ @sections << s
802
+ end
803
+ @cursource = @source[sname] ||= []
804
+ check_eol[] if instr.backtrace # special case for magic @cursource
805
+
806
+ when '.section'
807
+ # .section <section name|"section name"> [(no)r w x shared discard] [base=<expr>]
808
+ sname = readstr[]
809
+ if not s = @sections.find { |s_| s_.name == sname }
810
+ s = Section.new
811
+ s.name = sname
812
+ s.encoded = EncodedData.new
813
+ s.characteristics = []
814
+ @sections << s
815
+ end
816
+ loop do
817
+ @lexer.skip_space
818
+ break if not tok = @lexer.nexttok or tok.type != :string
819
+ case @lexer.readtok.raw.downcase
820
+ when /^(no)?(r)?(w)?(x)?(shared)?(discard)?$/
821
+ ar = []
822
+ ar << 'MEM_READ' if $2
823
+ ar << 'MEM_WRITE' if $3
824
+ ar << 'MEM_EXECUTE' if $4
825
+ ar << 'MEM_SHARED' if $5
826
+ ar << 'MEM_DISCARDABLE' if $6
827
+ if $1; s.characteristics -= ar
828
+ else s.characteristics |= ar
829
+ end
830
+ when 'base'
831
+ @lexer.skip_space
832
+ @lexer.unreadtok tok if not tok = @lexer.readtok or tok.type != :punct or tok.raw != '='
833
+ raise instr, 'invalid base' if not s.virtaddr = Expression.parse(@lexer).reduce or not s.virtaddr.kind_of?(::Integer)
834
+ if not @optheader.image_base
835
+ @optheader.image_base = (s.virtaddr-0x80) & 0xfff00000
836
+ puts "Warning: no image_base specified, using #{Expression[@optheader.image_base]}" if $VERBOSE
837
+ end
838
+ s.virtaddr -= @optheader.image_base
839
+ else raise instr, 'unknown parameter'
840
+ end
841
+ end
842
+ @cursource = @source[sname] ||= []
843
+ check_eol[]
844
+
845
+ when '.libname'
846
+ # export directory library name
847
+ # .libname <libname|"libname">
848
+ @export ||= ExportDirectory.new
849
+ @export.libname = readstr[]
850
+ check_eol[]
851
+
852
+ when '.export'
853
+ # .export <export name|ordinal|"export name"> [ordinal] [label to export if different]
854
+ @lexer.skip_space
855
+ raise instr, 'string expected' if not tok = @lexer.readtok or (tok.type != :string and tok.type != :quoted)
856
+ exportname = tok.value || tok.raw
857
+ if tok.type == :string and (?0..?9).include? tok.raw[0]
858
+ exportname = Integer(exportname) rescue raise(tok, "bad ordinal value, try quotes #{' or rm leading 0' if exportname[0] == ?0}")
859
+ end
860
+
861
+ @lexer.skip_space
862
+ tok = @lexer.readtok
863
+ if tok and tok.type == :string and (?0..?9).include? tok.raw[0]
864
+ (eord = Integer(tok.raw)) rescue @lexer.unreadtok(tok)
865
+ else @lexer.unreadtok(tok)
866
+ end
867
+
868
+ @lexer.skip_space
869
+ tok = @lexer.readtok
870
+ if tok and tok.type == :string
871
+ exportlabel = tok.raw
872
+ else
873
+ @lexer.unreadtok tok
874
+ end
875
+
876
+ @export ||= ExportDirectory.new
877
+ @export.exports ||= []
878
+ e = ExportDirectory::Export.new
879
+ if exportname.kind_of? Integer
880
+ e.ordinal = exportname
881
+ else
882
+ e.name = exportname
883
+ e.ordinal = eord if eord
884
+ end
885
+ e.target = exportlabel || exportname
886
+ @export.exports << e
887
+ check_eol[]
888
+
889
+ when '.import'
890
+ # .import <libname|"libname"> <imported sym|"imported sym"> [label of plt thunk|nil] [label of iat element if != symname]
891
+ libname = readstr[]
892
+ i = ImportDirectory::Import.new
893
+
894
+ @lexer.skip_space
895
+ raise instr, 'string expected' if not tok = @lexer.readtok or (tok.type != :string and tok.type != :quoted)
896
+ if tok.type == :string and (?0..?9).include? tok.raw[0]
897
+ i.ordinal = Integer(tok.raw)
898
+ else
899
+ i.name = tok.value || tok.raw
900
+ end
901
+
902
+ @lexer.skip_space
903
+ if tok = @lexer.readtok and tok.type == :string
904
+ i.thunk = tok.raw if tok.raw != 'nil'
905
+ @lexer.skip_space
906
+ tok = @lexer.readtok
907
+ end
908
+ if tok and tok.type == :string
909
+ i.target = tok.raw
910
+ else
911
+ i.target = ((i.thunk == i.name) ? ('iat_' + i.name) : (i.name ? i.name : (i.thunk ? 'iat_' + i.thunk : raise(instr, 'need iat label'))))
912
+ @lexer.unreadtok tok
913
+ end
914
+ raise tok, 'import target exists' if i.target != new_label(i.target)
915
+
916
+ @imports ||= []
917
+ if not id = @imports.find { |id_| id_.libname == libname }
918
+ id = ImportDirectory.new
919
+ id.libname = libname
920
+ id.imports = []
921
+ @imports << id
922
+ end
923
+ id.imports << i
924
+
925
+ check_eol[]
926
+
927
+ when '.entrypoint'
928
+ # ".entrypoint <somelabel/expression>" or ".entrypoint" (here)
929
+ @lexer.skip_space
930
+ if tok = @lexer.nexttok and tok.type == :string
931
+ raise instr, 'syntax error' if not entrypoint = Expression.parse(@lexer)
932
+ else
933
+ entrypoint = new_label('entrypoint')
934
+ @cursource << Label.new(entrypoint, instr.backtrace.dup)
935
+ end
936
+ @optheader.entrypoint = entrypoint
937
+ check_eol[]
938
+
939
+ when '.image_base'
940
+ raise instr if not base = Expression.parse(@lexer) or !(base = base.reduce).kind_of?(::Integer)
941
+ @optheader.image_base = base
942
+ check_eol[]
943
+
944
+ when '.subsystem'
945
+ @lexer.skip_space
946
+ raise instr if not tok = @lexer.readtok
947
+ @optheader.subsystem = tok.raw
948
+ check_eol[]
949
+
950
+ else super(instr)
951
+ end
952
+ end
953
+
954
+ def assemble(*a)
955
+ parse(*a) if not a.empty?
956
+ @source.each { |k, v|
957
+ raise "no section named #{k} ?" if not s = @sections.find { |s_| s_.name == k }
958
+ s.encoded << assemble_sequence(v, @cpu)
959
+ v.clear
960
+ }
961
+ end
962
+
963
+ # defines __PE__
964
+ def tune_prepro(l)
965
+ l.define_weak('__PE__', 1)
966
+ l.define_weak('__MS_X86_64_ABI__') if @cpu and @cpu.shortname == 'x64'
967
+ end
968
+
969
+ def tune_cparser(cp)
970
+ super(cp)
971
+ cp.llp64 if @cpu.size == 64
972
+ end
973
+
974
+ # honors C attributes: export, export_as(foo), import_from(kernel32), entrypoint
975
+ # import by ordinal: extern __stdcall int anyname(int) __attribute__((import_from(ws2_32:28)));
976
+ # can alias imports with int mygpaddr_alias() attr(import_from(kernel32:GetProcAddr))
977
+ def read_c_attrs(cp)
978
+ cp.toplevel.symbol.each_value { |v|
979
+ next if not v.kind_of? C::Variable
980
+ if v.has_attribute 'export' or ea = v.has_attribute_var('export_as')
981
+ @export ||= ExportDirectory.new
982
+ @export.exports ||= []
983
+ e = ExportDirectory::Export.new
984
+ begin
985
+ e.ordinal = Integer(ea || v.name)
986
+ rescue ArgumentError
987
+ e.name = ea || v.name
988
+ end
989
+ e.target = v.name
990
+ @export.exports << e
991
+ end
992
+ if v.has_attribute('import') or ln = v.has_attribute_var('import_from')
993
+ ln ||= WindowsExports::EXPORT[v.name]
994
+ raise "unknown library for #{v.name}" if not ln
995
+ i = ImportDirectory::Import.new
996
+ if ln.include? ':'
997
+ ln, name = ln.split(':')
998
+ begin
999
+ i.ordinal = Integer(name)
1000
+ rescue ArgumentError
1001
+ i.name = name
1002
+ end
1003
+ else
1004
+ i.name = v.name
1005
+ end
1006
+ if v.type.kind_of? C::Function
1007
+ i.thunk = v.name
1008
+ i.target = 'iat_'+i.thunk
1009
+ else
1010
+ i.target = v.name
1011
+ end
1012
+
1013
+ @imports ||= []
1014
+ if not id = @imports.find { |id_| id_.libname == ln }
1015
+ id = ImportDirectory.new
1016
+ id.libname = ln
1017
+ id.imports = []
1018
+ @imports << id
1019
+ end
1020
+ id.imports << i
1021
+ end
1022
+ if v.has_attribute 'entrypoint'
1023
+ @optheader.entrypoint = v.name
1024
+ end
1025
+ }
1026
+ end
1027
+
1028
+ # try to resolve automatically COFF import tables from self.sections.encoded.relocations
1029
+ # and WindowsExports::EXPORT
1030
+ # if the relocation target is '<symbolname>' or 'iat_<symbolname>, link to the IAT address, if it is '<symbolname> + <expr>',
1031
+ # link to a thunk (plt-like)
1032
+ # if the relocation is not found, try again after appending 'fallback_append' to the symbol (eg wsprintf => wsprintfA)
1033
+ def autoimport(fallback_append='A')
1034
+ WindowsExports rescue return # autorequire
1035
+ autoexports = WindowsExports::EXPORT.dup
1036
+ @sections.each { |s|
1037
+ next if not s.encoded
1038
+ s.encoded.export.keys.each { |e| autoexports.delete e }
1039
+ }
1040
+ @sections.each { |s|
1041
+ next if not s.encoded
1042
+ s.encoded.reloc.each_value { |r|
1043
+ if r.target.op == :+ and not r.target.lexpr and r.target.rexpr.kind_of?(::String)
1044
+ sym = target = r.target.rexpr
1045
+ sym = sym[4..-1] if sym[0, 4] == 'iat_'
1046
+ elsif r.target.op == :- and r.target.rexpr.kind_of?(::String) and r.target.lexpr.kind_of?(::String)
1047
+ sym = thunk = r.target.lexpr
1048
+ end
1049
+ if not dll = autoexports[sym]
1050
+ sym += fallback_append if sym.kind_of?(::String) and fallback_append.kind_of?(::String)
1051
+ next if not dll = autoexports[sym]
1052
+ end
1053
+
1054
+ @imports ||= []
1055
+ next if @imports.find { |id| id.imports.find { |ii| ii.name == sym } }
1056
+ if not id = @imports.find { |id_| id_.libname =~ /^#{dll}(\.dll)?$/i }
1057
+ id = ImportDirectory.new
1058
+ id.libname = dll
1059
+ id.imports = []
1060
+ @imports << id
1061
+ end
1062
+ if not i = id.imports.find { |i_| i_.name == sym }
1063
+ i = ImportDirectory::Import.new
1064
+ i.name = sym
1065
+ id.imports << i
1066
+ end
1067
+ if (target and i.target and (i.target != target or i.thunk == target)) or
1068
+ (thunk and i.thunk and (i.thunk != thunk or i.target == thunk))
1069
+ puts "autoimport: conflict for #{target} #{thunk} #{i.inspect}" if $VERBOSE
1070
+ else
1071
+ i.target ||= new_label(target || 'iat_' + thunk)
1072
+ i.thunk ||= thunk if thunk
1073
+ end
1074
+ }
1075
+ }
1076
+ end
1077
+ end
1078
+ end