pedump 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,425 @@
1
+ class PEdump
2
+ # from wine's winnt.h
3
+ class NE < IOStruct.new 'a2CCvvVv4VVv8Vv3CCv4',
4
+ :ne_magic, # 00 NE signature 'NE'
5
+ :ne_ver, # 02 Linker version number
6
+ :ne_rev, # 03 Linker revision number
7
+ :ne_enttab, # 04 Offset to entry table relative to NE
8
+ :ne_cbenttab, # 06 Length of entry table in bytes
9
+ :ne_crc, # 08 Checksum
10
+ :ne_flags, # 0c Flags about segments in this file
11
+ :ne_autodata, # 0e Automatic data segment number
12
+ :ne_heap, # 10 Initial size of local heap
13
+ :ne_stack, # 12 Initial size of stack
14
+ :ne_csip, # 14 Initial CS:IP
15
+ :ne_sssp, # 18 Initial SS:SP
16
+ :ne_cseg, # 1c # of entries in segment table
17
+ :ne_cmod, # 1e # of entries in module reference tab.
18
+ :ne_cbnrestab, # 20 Length of nonresident-name table
19
+ :ne_segtab, # 22 Offset to segment table
20
+ :ne_rsrctab, # 24 Offset to resource table
21
+ :ne_restab, # 26 Offset to resident-name table
22
+ :ne_modtab, # 28 Offset to module reference table
23
+ :ne_imptab, # 2a Offset to imported name table
24
+ :ne_nrestab, # 2c Offset to nonresident-name table
25
+ :ne_cmovent, # 30 # of movable entry points
26
+ :ne_align, # 32 Logical sector alignment shift count
27
+ :ne_cres, # 34 # of resource segments
28
+ :ne_exetyp, # 36 Flags indicating target OS
29
+ :ne_flagsothers, # 37 Additional information flags
30
+ :ne_pretthunks, # 38 Offset to return thunks
31
+ :ne_psegrefbytes, # 3a Offset to segment ref. bytes
32
+ :ne_swaparea, # 3c Reserved by Microsoft
33
+ :ne_expver # 3e Expected Windows version number
34
+
35
+ attr_accessor :io, :offset
36
+
37
+ DEFAULT_CP = 1252
38
+
39
+ def self.cp
40
+ @@cp || DEFAULT_CP
41
+ end
42
+
43
+ def self.cp= cp
44
+ @@cp = cp
45
+ end
46
+
47
+ def self.read io, *args
48
+ self.cp = DEFAULT_CP
49
+ offset = io.tell
50
+ super.tap do |x|
51
+ x.io, x.offset = io, offset
52
+ end
53
+ end
54
+
55
+ class Segment < IOStruct.new 'v4',
56
+ :offset, :size, :flags, :min_alloc_size,
57
+ # manual:
58
+ :file_offset, :relocs
59
+
60
+ FLAG_RELOCINFO = 0x100
61
+
62
+ def data?
63
+ flags & 1 == 1
64
+ end
65
+
66
+ def code?
67
+ !data?
68
+ end
69
+
70
+ def flags_desc
71
+ r = code? ? 'CODE' : 'DATA'
72
+ r << ' ALLOC' if flags & 2 != 0
73
+ r << ' LOADED' if flags & 4 != 0
74
+ r << ((flags & 0x10 != 0) ? ' MOVABLE' : ' FIXED')
75
+ r << ((flags & 0x20 != 0) ? ' PURE' : '')
76
+ r << ((flags & 0x40 != 0) ? ' PRELOAD' : '')
77
+ if code?
78
+ r << ((flags & 0x80 != 0) ? ' EXECUTEONLY' : '')
79
+ else
80
+ r << ((flags & 0x80 != 0) ? ' READONLY' : '')
81
+ end
82
+ r << ((flags & FLAG_RELOCINFO != 0) ? ' RELOCINFO' : '')
83
+ r << ((flags & 0x200 != 0) ? ' DBGINFO' : '')
84
+ r << ((flags & 0x1000 != 0) ? ' DISCARD' : '')
85
+ r
86
+ end
87
+ end
88
+
89
+ class Reloc < IOStruct.new 'CCvvv',
90
+ :source, :type,
91
+ :offset, # offset of the relocation item within the segment
92
+
93
+ # If the relocation type is imported ordinal,
94
+ # the fifth and sixth bytes specify an index to a module's reference table and
95
+ # the seventh and eighth bytes specify a function ordinal value.
96
+
97
+ # If the relocation type is imported name,
98
+ # the fifth and sixth bytes specify an index to a module's reference table and
99
+ # the seventh and eighth bytes specify an offset to an imported-name table.
100
+
101
+ :module_idx,
102
+ :func_idx
103
+
104
+ TYPE_IMPORTORDINAL = 1
105
+ TYPE_IMPORTNAME = 2
106
+ end
107
+
108
+ def segments io=@io
109
+ @segments ||= io &&
110
+ begin
111
+ io.seek ne_segtab+@offset
112
+ ne_cseg.times.map{ Segment.read(io) }.each do |seg|
113
+ seg.file_offset = seg.offset << ne_align
114
+ seg.relocs = []
115
+ if (seg.flags & Segment::FLAG_RELOCINFO) != 0
116
+ io.seek seg.file_offset + seg.size
117
+ nRelocs = io.read(2).unpack('v').first
118
+ seg.relocs = nRelocs.times.map{ Reloc.read(io) }
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ class ResourceGroup < IOStruct.new 'vvV',
125
+ :type_id, :count, :reserved,
126
+ # manual:
127
+ :type, :children
128
+
129
+ def self.read io
130
+ super.tap do |g|
131
+ if g.type_id.to_i == 0
132
+ # type_id = 0 means end of resource groups
133
+ return nil
134
+ else
135
+ # read only if type_id is non-zero,
136
+ g.children = []
137
+ g.count.times do
138
+ break if io.eof?
139
+ g.children << ResourceInfo.read(io)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ class ResourceInfo < IOStruct.new 'v4V',
147
+ :offset, :size, :flags, :name_offset, :reserved,
148
+ # manual:
149
+ :name
150
+ end
151
+
152
+ class Resource < PEdump::Resource
153
+ # NE strings use 8-bit characters
154
+ def parse f, h={}
155
+ self.data = []
156
+ case type
157
+ when 'STRING'
158
+ f.seek file_offset
159
+ 16.times do
160
+ break if f.tell >= file_offset+self.size
161
+ nChars = f.getc.ord
162
+ t =
163
+ if nChars + 1 > self.size
164
+ # TODO: if it's not 1st string in table then truncated size must be less
165
+ PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
166
+ f.read(self.size-1)
167
+ else
168
+ f.read(nChars)
169
+ end
170
+ data <<
171
+ begin
172
+ t.force_encoding("CP#{h[:cp]}").encode!('UTF-8')
173
+ rescue
174
+ t.force_encoding('ASCII')
175
+ end
176
+ end
177
+ when 'VERSION'
178
+ f.seek file_offset
179
+ data << PEdump::NE::VS_VERSIONINFO.read(f)
180
+ else
181
+ super(f)
182
+ end
183
+ end
184
+ end
185
+
186
+ def _id2string id, io, res_base
187
+ if id & 0x8000 == 0
188
+ # offset to name
189
+ io.seek id + res_base
190
+ namesize = (io.getc || 0.chr).ord
191
+ io.read(namesize)
192
+ else
193
+ # numerical id
194
+ "##{id & 0x7fff}"
195
+ end
196
+ end
197
+
198
+ def resource_directory io=@io
199
+ @resource_directory ||=
200
+ begin
201
+ res_base = ne_rsrctab+@offset
202
+ io.seek res_base
203
+ res_shift = io.read(2).unpack('v').first
204
+ unless (0..16).include?(res_shift)
205
+ PEdump.logger.error "[!] invalid res_shift = %d" % res_shift
206
+ return []
207
+ end
208
+ PEdump.logger.info "[.] res_shift = %d" % res_shift
209
+ r = []
210
+ while !io.eof? && (g = ResourceGroup.read(io))
211
+ r << g
212
+ end
213
+ r.each do |g|
214
+ g.type = (g.type_id & 0x8000 != 0) && PEdump::ROOT_RES_NAMES[g.type_id & 0x7fff]
215
+ g.type ||= _id2string( g.type_id, io, res_base)
216
+ g.children.each do |res|
217
+ res.name = _id2string(res.name_offset, io, res_base)
218
+ res.offset ||= 0
219
+ res.offset <<= res_shift
220
+ res.size ||= 0
221
+ res.size <<= res_shift
222
+ end
223
+ end
224
+ r
225
+ end
226
+ end
227
+
228
+ def _detect_codepage a, io=@io
229
+ a.find_all{ |res| res.type == 'VERSION' }.each do |res|
230
+ res.parse(io)
231
+ res.data.each do |vi|
232
+ if vi.respond_to?(:Children) && vi.Children.respond_to?(:each)
233
+ # vi is PEdump::NE::VS_VERSIONINFO
234
+ vi.Children.each do |vfi|
235
+ if vfi.is_a?(PEdump::NE::VarFileInfo) && vfi.Children.is_a?(PEdump::NE::Var)
236
+ var = vfi.Children
237
+ # var is PEdump::NE::Var
238
+ if var.respond_to?(:Value) && var.Value.is_a?(Array) && var.Value.size == 2
239
+ return var.Value.last
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ nil
247
+ end
248
+
249
+ def resources io=@io
250
+ a = []
251
+ resource_directory(io).each do |grp|
252
+ grp.children.each do |res|
253
+ a << (r = Resource.new)
254
+ r.id = (res.name_offset & 0x7fff) if (res.name_offset & 0x8000) != 0
255
+ r.type = grp.type
256
+ r.size = res.size
257
+ r.name = res.name
258
+ r.file_offset = res.offset
259
+ r.reserved = res.reserved
260
+ end
261
+ end
262
+
263
+ # try to detect codepage
264
+ cp = _detect_codepage(a, io)
265
+ if cp
266
+ PEdump::NE.cp = cp # XXX HACK
267
+ PEdump.logger.info "[.] detect_codepage: #{cp.inspect}"
268
+ else
269
+ cp = DEFAULT_CP
270
+ PEdump.logger.info "[.] detect_codepage failed, using default #{cp}"
271
+ end
272
+
273
+ a.each{ |r| r.parse(io, :cp => cp) }
274
+ a
275
+ end
276
+
277
+ def imports io=@io
278
+ @imports ||=
279
+ begin
280
+ io.seek @offset+ne_modtab
281
+ modules = io.read(2*ne_cmod).unpack('v*')
282
+ modules.map! do |ofs|
283
+ io.seek @offset+ne_imptab+ofs
284
+ namelen = io.getc.ord
285
+ io.read(namelen)
286
+ end
287
+
288
+ r = []
289
+ segments(io).each do |seg|
290
+ seg.relocs.each do |rel|
291
+ if rel.type == Reloc::TYPE_IMPORTORDINAL
292
+ r << (f = PEdump::ImportedFunction.new)
293
+ f.module_name = modules[rel.module_idx-1]
294
+ f.ordinal = rel.func_idx
295
+ elsif rel.type == Reloc::TYPE_IMPORTNAME
296
+ r << (f = PEdump::ImportedFunction.new)
297
+ f.module_name = modules[rel.module_idx-1]
298
+ io.seek @offset+ne_imptab+rel.func_idx
299
+ namelen = io.getc.ord
300
+ f.name = io.read(namelen)
301
+ end
302
+ end
303
+ end
304
+ r
305
+ end
306
+ end
307
+
308
+ # first string with ordinal 0 is a module name
309
+ def exports io=@io
310
+ exp_dir = IMAGE_EXPORT_DIRECTORY.new
311
+ exp_dir.functions = []
312
+
313
+ io.seek @offset+ne_restab
314
+ while !io.eof && (namelen = io.getc.ord) > 0
315
+ exp_dir.functions << ExportedFunction.new( io.read(namelen), io.read(2).unpack('v').first, 0 )
316
+ end
317
+ exp_dir.name = exp_dir.functions.shift.name if exp_dir.functions.any?
318
+
319
+ a = []
320
+ io.seek ne_nrestab
321
+ while !io.eof && (namelen = io.getc.ord) > 0
322
+ a << ExportedFunction.new( io.read(namelen), io.read(2).unpack('v').first, 0 )
323
+ end
324
+ exp_dir.description = a.shift.name if a.any?
325
+ exp_dir.functions += a
326
+
327
+ exp_dir.functions.each do |f|
328
+ f.va = entrypoints[f.ord]
329
+ end
330
+
331
+ exp_dir
332
+ end
333
+
334
+ # The entry-table data is organized by bundle, each of which begins with a 2-byte header.
335
+ # The first byte of the header specifies the number of entries in the bundle ( 0 = end of the table).
336
+ # The second byte specifies whether the corresponding segment is movable or fixed.
337
+ # 0xFF = the segment is movable.
338
+ # 0xFE = the entry does not refer to a segment but refers to a constant defined within the module.
339
+ # else it is a segment index.
340
+
341
+ class Bundle < IOStruct.new 'CC', :num_entries, :seg_idx,
342
+ :entries # manual
343
+
344
+ FixedEntry = IOStruct.new 'Cv', :flag, :offset
345
+ MovableEntry = IOStruct.new 'CvCv', :flag, :int3F, :seg_idx, :offset
346
+
347
+ def movable?
348
+ seg_idx == 0xff
349
+ end
350
+
351
+ def self.read io
352
+ super.tap do |bundle|
353
+ return nil if bundle.num_entries == 0
354
+ if bundle.num_entries == 0
355
+ @@eob ||= 0
356
+ @@eob += 1
357
+ return nil if @@eob == 2
358
+ end
359
+ bundle.entries = bundle.seg_idx == 0 ? [] :
360
+ if bundle.movable?
361
+ bundle.num_entries.times.map{ MovableEntry.read(io) }
362
+ else
363
+ bundle.num_entries.times.map{ FixedEntry.read(io) }
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ def bundles io=@io
370
+ io.seek @offset+ne_enttab
371
+ bundles = []
372
+ while bundle = Bundle.read(io)
373
+ bundles << bundle
374
+ end
375
+ bundles
376
+ end
377
+
378
+ def entrypoints io=@io
379
+ @entrypoints ||=
380
+ begin
381
+ r = [0] # entrypoint indexes are 1-based
382
+ bundles(io).each do |b|
383
+ if b.entries.empty?
384
+ b.num_entries.times{ r<<0 }
385
+ else
386
+ b.entries.each do |e|
387
+ if e.is_a?(Bundle::MovableEntry)
388
+ r << (e.seg_idx<<16) + e.offset
389
+ elsif e.is_a?(Bundle::FixedEntry)
390
+ r << (b.seg_idx<<16) + e.offset
391
+ else
392
+ raise "invalid ep #{e.inspect}"
393
+ end
394
+ end
395
+ end
396
+ end
397
+ r
398
+ end
399
+ end
400
+ end
401
+
402
+ def ne f=@io
403
+ return @ne if defined?(@ne)
404
+ @ne ||=
405
+ begin
406
+ ne_offset = mz(f) && mz(f).lfanew
407
+ if ne_offset.nil?
408
+ logger.fatal "[!] NULL NE offset (e_lfanew)."
409
+ nil
410
+ elsif ne_offset > f.size
411
+ logger.fatal "[!] NE offset beyond EOF."
412
+ nil
413
+ else
414
+ f.seek ne_offset
415
+ if f.read(2) == 'NE'
416
+ f.seek ne_offset
417
+ NE.read f
418
+ else
419
+ nil
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ end
@@ -0,0 +1,171 @@
1
+ class PEdump; end
2
+
3
+ class PEdump::NE
4
+ class VS_VERSIONINFO < IOStruct.new( 'v2a16',
5
+ :wLength,
6
+ :wValueLength,
7
+ :szKey, # ASCII string "VS_VERSION_INFO".
8
+ :Padding1,
9
+ # manual:
10
+ :Value, # VS_FIXEDFILEINFO
11
+ :Padding2,
12
+ :Children
13
+ )
14
+ def self.read f, size = SIZE
15
+ super.tap do |vi|
16
+ vi.szKey.chomp!("\x00")
17
+ vi.Padding1 = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
18
+ vi.Value = VS_FIXEDFILEINFO.read(f,vi.wValueLength)
19
+ # As many zero words as necessary to align the Children member on a 32-bit boundary.
20
+ # These bytes are not included in wValueLength. This member is optional.
21
+ vi.Padding2 = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
22
+ vi.Children = [] # An array of zero or one StringFileInfo structures,
23
+ # and zero or one VarFileInfo structures
24
+
25
+ 2.times do
26
+ pos = f.tell
27
+ f.seek(pos+4) # seek 4 bytes forward
28
+ t = f.read(3)
29
+ f.seek(pos) # return back
30
+ case t
31
+ when "Var"
32
+ vi.Children << VarFileInfo.read(f)
33
+ when "Str"
34
+ vi.Children << StringFileInfo.read(f)
35
+ else
36
+ PEdump.logger.warn "[?] invalid VS_VERSIONINFO child type #{t.inspect}"
37
+ break
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class VS_FIXEDFILEINFO < IOStruct.new( 'V13',
45
+ :dwSignature,
46
+ :dwStrucVersion,
47
+ :dwFileVersionMS,
48
+ :dwFileVersionLS,
49
+ :dwProductVersionMS,
50
+ :dwProductVersionLS,
51
+ :dwFileFlagsMask,
52
+ :dwFileFlags,
53
+ :dwFileOS,
54
+ :dwFileType,
55
+ :dwFileSubtype,
56
+ :dwFileDateMS,
57
+ :dwFileDateLS,
58
+ # manual:
59
+ :valid
60
+ )
61
+ def self.read f, size = SIZE
62
+ super.tap do |ffi|
63
+ ffi.valid = (ffi.dwSignature == 0xFEEF04BD)
64
+ end
65
+ end
66
+ end
67
+
68
+ class StringFileInfo < IOStruct.new( 'v2a15',
69
+ :wLength,
70
+ :wValueLength, # always 0
71
+ :szKey, # The ASCII string "StringFileInfo"
72
+ :Padding, # As many zero words as necessary to align the Children member on a 32-bit boundary
73
+ :Children # An array of one or more StringTable structures
74
+ )
75
+ def self.read f, size = SIZE
76
+ pos0 = f.tell
77
+ super.tap do |x|
78
+ x.szKey.chomp!("\x00")
79
+ x.Padding = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
80
+ x.Children = []
81
+ while !f.eof? && f.tell < pos0+x.wLength
82
+ x.Children << StringTable.read(f)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ class StringTable < IOStruct.new( 'v2a9',
89
+ :wLength, # The length, in bytes, of this StringTable structure,
90
+ # including all structures indicated by the Children member.
91
+ :wValueLength, # always 0
92
+ :szKey, # An 8-digit hexadecimal number stored as a ASCII string
93
+ :Padding, # As many zero words as necessary to align the Children member on a 32-bit boundary
94
+ :Children # An array of one or more String structures.
95
+ )
96
+ def self.read f, size = SIZE
97
+ pos0 = f.tell
98
+ super.tap do |x|
99
+ x.szKey.chomp!("\x00")
100
+ x.Padding = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
101
+ x.Children = []
102
+ while !f.eof? && f.tell < pos0+x.wLength
103
+ x.Children << VersionString.read(f)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ class VersionString < IOStruct.new( 'v2',
110
+ :wLength, # The length, in bytes, of this String structure.
111
+ :wValueLength, # The size, in words, of the Value member
112
+ :szKey, # An arbitrary ASCII string
113
+ :Padding, # As many zero words as necessary to align the Value member on a 32-bit boundary
114
+ :Value # A zero-terminated string. See the szKey member description for more information
115
+ )
116
+ def self.read f, size = SIZE
117
+ pos = f.tell
118
+ super.tap do |x|
119
+ x.szKey = f.gets("\x00").to_s.chomp("\x00")
120
+ x.Padding = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
121
+
122
+ value_len = [x.wValueLength*2, x.wLength - (f.tell-pos)].min
123
+ value_len = 0 if value_len < 0
124
+
125
+ cp = PEdump::NE.cp # XXX HACK
126
+
127
+ x.Value = f.read(value_len).to_s.chomp("\x00")
128
+ begin
129
+ x.Value.force_encoding("CP#{cp}").encode!('UTF-8').sub!(/\u0000$/,'')
130
+ rescue
131
+ x.Value.force_encoding("CP1250").encode!('UTF-8').sub!(/\u0000$/,'') rescue nil
132
+ end
133
+ if f.tell%4 > 0
134
+ f.read(4-f.tell%4) # undoc padding?
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ class VarFileInfo < IOStruct.new( 'v2a12',
141
+ :wLength,
142
+ :wValueLength, # always 0
143
+ :szKey, # The ASCII string "VarFileInfo"
144
+ :Padding, # As many zero words as necessary to align the Children member on a 32-bit boundary
145
+ :Children # Typically contains a list of languages that the application or DLL supports
146
+ )
147
+ def self.read f, size = SIZE
148
+ super.tap do |x|
149
+ x.szKey.chomp!("\x00")
150
+ x.Padding = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
151
+ x.Children = Var.read(f)
152
+ end
153
+ end
154
+ end
155
+
156
+ class Var < IOStruct.new( 'v2a12',
157
+ :wLength,
158
+ :wValueLength, # The length, in bytes, of the Value member
159
+ :szKey, # The ASCII string "Translation"
160
+ :Padding, # As many zero words as necessary to align the Children member on a 32-bit boundary
161
+ :Value # An array of one or more values that are language and code page identifier pairs
162
+ )
163
+ def self.read f, size = SIZE
164
+ super.tap do |x|
165
+ x.szKey.chomp!("\x00")
166
+ x.Padding = f.tell%4 > 0 ? f.read(4 - f.tell%4) : nil
167
+ x.Value = f.read(x.wValueLength).unpack('v*')
168
+ end
169
+ end
170
+ end
171
+ end