pedump 0.5.3
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.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +20 -0
- data/README.md +410 -0
- data/Rakefile +179 -0
- data/VERSION +1 -0
- data/bin/pedump +7 -0
- data/data/fs.txt +224 -0
- data/data/jc-userdb.txt +14371 -0
- data/data/sig.bin +0 -0
- data/data/signatures.txt +678 -0
- data/data/userdb.txt +14083 -0
- data/lib/pedump.rb +868 -0
- data/lib/pedump/cli.rb +804 -0
- data/lib/pedump/comparer.rb +147 -0
- data/lib/pedump/composite_io.rb +56 -0
- data/lib/pedump/core.rb +38 -0
- data/lib/pedump/core_ext/try.rb +57 -0
- data/lib/pedump/loader.rb +393 -0
- data/lib/pedump/loader/minidump.rb +351 -0
- data/lib/pedump/loader/section.rb +57 -0
- data/lib/pedump/logger.rb +67 -0
- data/lib/pedump/ne.rb +425 -0
- data/lib/pedump/ne/version_info.rb +171 -0
- data/lib/pedump/packer.rb +173 -0
- data/lib/pedump/pe.rb +121 -0
- data/lib/pedump/resources.rb +436 -0
- data/lib/pedump/security.rb +58 -0
- data/lib/pedump/sig_parser.rb +507 -0
- data/lib/pedump/tls.rb +17 -0
- data/lib/pedump/unpacker.rb +26 -0
- data/lib/pedump/unpacker/aspack.rb +858 -0
- data/lib/pedump/unpacker/upx.rb +13 -0
- data/lib/pedump/version.rb +10 -0
- data/lib/pedump/version_info.rb +171 -0
- data/misc/aspack/Makefile +3 -0
- data/misc/aspack/aspack_unlzx.c +92 -0
- data/misc/aspack/lzxdec.c +479 -0
- data/misc/aspack/lzxdec.h +56 -0
- data/misc/nedump.c +751 -0
- data/pedump.gemspec +109 -0
- metadata +227 -0
data/lib/pedump/ne.rb
ADDED
@@ -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
|