pedump 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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.rb
ADDED
@@ -0,0 +1,868 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'stringio'
|
3
|
+
require 'iostruct'
|
4
|
+
require 'zhexdump'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
unless Object.new.respond_to?(:try) && nil.respond_to?(:try)
|
8
|
+
require 'pedump/core_ext/try'
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'pedump/core'
|
12
|
+
require 'pedump/pe'
|
13
|
+
require 'pedump/resources'
|
14
|
+
require 'pedump/version_info'
|
15
|
+
require 'pedump/tls'
|
16
|
+
require 'pedump/security'
|
17
|
+
require 'pedump/packer'
|
18
|
+
require 'pedump/ne'
|
19
|
+
require 'pedump/ne/version_info'
|
20
|
+
|
21
|
+
# pedump.rb by zed_0xff
|
22
|
+
#
|
23
|
+
# http://zed.0xff.me
|
24
|
+
# http://github.com/zed-0xff
|
25
|
+
|
26
|
+
class PEdump
|
27
|
+
attr_accessor :fname, :logger, :force, :io
|
28
|
+
|
29
|
+
VERSION = Version::STRING
|
30
|
+
MAX_ERRORS = 100
|
31
|
+
MAX_IMAGE_IMPORT_DESCRIPTORS = 1000
|
32
|
+
|
33
|
+
@@logger = nil
|
34
|
+
|
35
|
+
def initialize io = nil, params = {}
|
36
|
+
if io.is_a?(Hash)
|
37
|
+
@io, params = nil, io
|
38
|
+
else
|
39
|
+
@io = io
|
40
|
+
end
|
41
|
+
@force = params[:force]
|
42
|
+
@logger = @@logger = Logger.create(params)
|
43
|
+
end
|
44
|
+
|
45
|
+
# http://www.delorie.com/djgpp/doc/exe/
|
46
|
+
MZ = IOStruct.new( "a2v13Qv2V6",
|
47
|
+
:signature,
|
48
|
+
:bytes_in_last_block,
|
49
|
+
:blocks_in_file,
|
50
|
+
:num_relocs,
|
51
|
+
:header_paragraphs,
|
52
|
+
:min_extra_paragraphs,
|
53
|
+
:max_extra_paragraphs,
|
54
|
+
:ss,
|
55
|
+
:sp,
|
56
|
+
:checksum,
|
57
|
+
:ip,
|
58
|
+
:cs,
|
59
|
+
:reloc_table_offset,
|
60
|
+
:overlay_number,
|
61
|
+
:reserved0, # 8 reserved bytes
|
62
|
+
:oem_id,
|
63
|
+
:oem_info,
|
64
|
+
:reserved2, # 20 reserved bytes
|
65
|
+
:reserved3,
|
66
|
+
:reserved4,
|
67
|
+
:reserved5,
|
68
|
+
:reserved6,
|
69
|
+
:lfanew
|
70
|
+
)
|
71
|
+
|
72
|
+
# http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
73
|
+
class IMAGE_FILE_HEADER < IOStruct.new( 'v2V3v2',
|
74
|
+
:Machine, # w
|
75
|
+
:NumberOfSections, # w
|
76
|
+
:TimeDateStamp, # dw
|
77
|
+
:PointerToSymbolTable, # dw
|
78
|
+
:NumberOfSymbols, # dw
|
79
|
+
:SizeOfOptionalHeader, # w
|
80
|
+
:Characteristics # w
|
81
|
+
)
|
82
|
+
# Characteristics, http://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=VS.85).aspx)
|
83
|
+
FLAGS = {
|
84
|
+
0x0001 => 'RELOCS_STRIPPED', # Relocation information was stripped from the file.
|
85
|
+
# The file must be loaded at its preferred base address.
|
86
|
+
# If the base address is not available, the loader reports an error.
|
87
|
+
0x0002 => 'EXECUTABLE_IMAGE',
|
88
|
+
0x0004 => 'LINE_NUMS_STRIPPED',
|
89
|
+
0x0008 => 'LOCAL_SYMS_STRIPPED',
|
90
|
+
0x0010 => 'AGGRESIVE_WS_TRIM', # Aggressively trim the working set. This value is obsolete as of Windows 2000.
|
91
|
+
0x0020 => 'LARGE_ADDRESS_AWARE', # The application can handle addresses larger than 2 GB.
|
92
|
+
0x0040 => '16BIT_MACHINE',
|
93
|
+
0x0080 => 'BYTES_REVERSED_LO', # The bytes of the word are reversed. This flag is obsolete.
|
94
|
+
0x0100 => '32BIT_MACHINE',
|
95
|
+
0x0200 => 'DEBUG_STRIPPED',
|
96
|
+
0x0400 => 'REMOVABLE_RUN_FROM_SWAP',
|
97
|
+
0x0800 => 'NET_RUN_FROM_SWAP',
|
98
|
+
0x1000 => 'SYSTEM',
|
99
|
+
0x2000 => 'DLL',
|
100
|
+
0x4000 => 'UP_SYSTEM_ONLY', # The file should be run only on a uniprocessor computer.
|
101
|
+
0x8000 => 'BYTES_REVERSED_HI' # The bytes of the word are reversed. This flag is obsolete.
|
102
|
+
}
|
103
|
+
|
104
|
+
# def initialize *args
|
105
|
+
# super
|
106
|
+
# self.TimeDateStamp = Time.at(self.TimeDateStamp).utc
|
107
|
+
# end
|
108
|
+
def flags
|
109
|
+
FLAGS.find_all{ |k,v| (self.Characteristics & k) != 0 }.map(&:last)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module IMAGE_OPTIONAL_HEADER
|
114
|
+
# DllCharacteristics, http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx)
|
115
|
+
FLAGS = {
|
116
|
+
0x0001 => '0x01', # reserved
|
117
|
+
0x0002 => '0x02', # reserved
|
118
|
+
0x0004 => '0x04', # reserved
|
119
|
+
0x0008 => '0x08', # reserved
|
120
|
+
0x0010 => '0x10', # ?
|
121
|
+
0x0020 => '0x20', # ?
|
122
|
+
0x0040 => 'DYNAMIC_BASE',
|
123
|
+
0x0080 => 'FORCE_INTEGRITY',
|
124
|
+
0x0100 => 'NX_COMPAT',
|
125
|
+
0x0200 => 'NO_ISOLATION',
|
126
|
+
0x0400 => 'NO_SEH',
|
127
|
+
0x0800 => 'NO_BIND',
|
128
|
+
0x1000 => '0x1000', # ?
|
129
|
+
0x2000 => 'WDM_DRIVER',
|
130
|
+
0x4000 => '0x4000', # ?
|
131
|
+
0x8000 => 'TERMINAL_SERVER_AWARE'
|
132
|
+
}
|
133
|
+
def initialize *args
|
134
|
+
super
|
135
|
+
self.extend InstanceMethods
|
136
|
+
end
|
137
|
+
def self.included base
|
138
|
+
base.extend ClassMethods
|
139
|
+
end
|
140
|
+
module ClassMethods
|
141
|
+
def read file, size = nil
|
142
|
+
usual_size = self.const_get('USUAL_SIZE')
|
143
|
+
cSIZE = self.const_get 'SIZE'
|
144
|
+
cFORMAT = self.const_get 'FORMAT'
|
145
|
+
size ||= cSIZE
|
146
|
+
PEdump.logger.warn "[?] unusual size of IMAGE_OPTIONAL_HEADER = #{size} (must be #{usual_size})" if size != usual_size
|
147
|
+
PEdump.logger.warn "[?] #{size-usual_size} spare bytes after IMAGE_OPTIONAL_HEADER" if size > usual_size
|
148
|
+
new(*file.read([size,cSIZE].min).to_s.unpack(cFORMAT)).tap do |ioh|
|
149
|
+
ioh.DataDirectory = []
|
150
|
+
|
151
|
+
# check if "...this address is outside the memory mapped file and is zeroed by the OS"
|
152
|
+
# see http://www.phreedom.org/solar/code/tinype/, section "Removing the data directories"
|
153
|
+
ioh.each_pair{ |k,v| ioh[k] = 0 if v.nil? }
|
154
|
+
|
155
|
+
# http://opcode0x90.wordpress.com/2007/04/22/windows-loader-does-it-differently/
|
156
|
+
# maximum of 0x10 entries, even if bigger
|
157
|
+
[0x10,ioh.NumberOfRvaAndSizes].min.times do |idx|
|
158
|
+
ioh.DataDirectory << IMAGE_DATA_DIRECTORY.read(file)
|
159
|
+
ioh.DataDirectory.last.type = IMAGE_DATA_DIRECTORY::TYPES[idx]
|
160
|
+
end
|
161
|
+
#ioh.DataDirectory.pop while ioh.DataDirectory.last.empty?
|
162
|
+
|
163
|
+
# skip spare bytes, if any. XXX may contain suspicious data
|
164
|
+
file.seek(size-usual_size, IO::SEEK_CUR) if size > usual_size
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
module InstanceMethods
|
169
|
+
def flags
|
170
|
+
FLAGS.find_all{ |k,v| (self.DllCharacteristics & k) != 0 }.map(&:last)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
176
|
+
class IMAGE_OPTIONAL_HEADER32 < IOStruct.new( 'vC2V9v6V4v2V6',
|
177
|
+
:Magic, # w
|
178
|
+
:MajorLinkerVersion, :MinorLinkerVersion, # 2b
|
179
|
+
:SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, # 9dw
|
180
|
+
:BaseOfCode, :BaseOfData, :ImageBase, :SectionAlignment, :FileAlignment,
|
181
|
+
:MajorOperatingSystemVersion, :MinorOperatingSystemVersion, # 6w
|
182
|
+
:MajorImageVersion, :MinorImageVersion, :MajorSubsystemVersion, :MinorSubsystemVersion,
|
183
|
+
:Reserved1, :SizeOfImage, :SizeOfHeaders, :CheckSum, # 4dw
|
184
|
+
:Subsystem, :DllCharacteristics, # 2w
|
185
|
+
:SizeOfStackReserve, :SizeOfStackCommit, :SizeOfHeapReserve, :SizeOfHeapCommit, # 6dw
|
186
|
+
:LoaderFlags, :NumberOfRvaAndSizes,
|
187
|
+
:DataDirectory # readed manually
|
188
|
+
)
|
189
|
+
USUAL_SIZE = 224
|
190
|
+
include IMAGE_OPTIONAL_HEADER
|
191
|
+
end
|
192
|
+
|
193
|
+
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx)
|
194
|
+
class IMAGE_OPTIONAL_HEADER64 < IOStruct.new( 'vC2V5QV2v6V4v2Q4V2',
|
195
|
+
:Magic, # w
|
196
|
+
:MajorLinkerVersion, :MinorLinkerVersion, # 2b
|
197
|
+
:SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, :BaseOfCode, # 5dw
|
198
|
+
:ImageBase, # qw
|
199
|
+
:SectionAlignment, :FileAlignment, # 2dw
|
200
|
+
:MajorOperatingSystemVersion, :MinorOperatingSystemVersion, # 6w
|
201
|
+
:MajorImageVersion, :MinorImageVersion, :MajorSubsystemVersion, :MinorSubsystemVersion,
|
202
|
+
:Reserved1, :SizeOfImage, :SizeOfHeaders, :CheckSum, # 4dw
|
203
|
+
:Subsystem, :DllCharacteristics, # 2w
|
204
|
+
:SizeOfStackReserve, :SizeOfStackCommit, :SizeOfHeapReserve, :SizeOfHeapCommit, # 4qw
|
205
|
+
:LoaderFlags, :NumberOfRvaAndSizes, #2dw
|
206
|
+
:DataDirectory # readed manually
|
207
|
+
)
|
208
|
+
USUAL_SIZE = 240
|
209
|
+
include IMAGE_OPTIONAL_HEADER
|
210
|
+
end
|
211
|
+
|
212
|
+
IMAGE_DATA_DIRECTORY = IOStruct.new( "VV", :va, :size, :type )
|
213
|
+
IMAGE_DATA_DIRECTORY::TYPES =
|
214
|
+
%w'EXPORT IMPORT RESOURCE EXCEPTION SECURITY BASERELOC DEBUG ARCHITECTURE GLOBALPTR TLS LOAD_CONFIG
|
215
|
+
Bound_IAT IAT Delay_IAT CLR_Header'
|
216
|
+
IMAGE_DATA_DIRECTORY::TYPES.each_with_index do |type,idx|
|
217
|
+
IMAGE_DATA_DIRECTORY.const_set(type,idx)
|
218
|
+
end
|
219
|
+
|
220
|
+
IMAGE_SECTION_HEADER = IOStruct.new( 'A8V6v2V',
|
221
|
+
:Name, # A8 6dw
|
222
|
+
:VirtualSize, :VirtualAddress, :SizeOfRawData, :PointerToRawData, :PointerToRelocations, :PointerToLinenumbers,
|
223
|
+
:NumberOfRelocations, :NumberOfLinenumbers, # 2w
|
224
|
+
:Characteristics # dw
|
225
|
+
)
|
226
|
+
class IMAGE_SECTION_HEADER
|
227
|
+
alias :flags :Characteristics
|
228
|
+
alias :va :VirtualAddress
|
229
|
+
def flags_desc
|
230
|
+
r = ''
|
231
|
+
f = self.flags.to_i
|
232
|
+
r << (f & 0x4000_0000 > 0 ? 'R' : '-')
|
233
|
+
r << (f & 0x8000_0000 > 0 ? 'W' : '-')
|
234
|
+
r << (f & 0x2000_0000 > 0 ? 'X' : '-')
|
235
|
+
r << ' CODE' if f & 0x20 > 0
|
236
|
+
|
237
|
+
# section contains initialized data. Almost all sections except executable and the .bss section have this flag set
|
238
|
+
r << ' IDATA' if f & 0x40 > 0
|
239
|
+
|
240
|
+
# section contains uninitialized data (for example, the .bss section)
|
241
|
+
r << ' UDATA' if f & 0x80 > 0
|
242
|
+
|
243
|
+
r << ' DISCARDABLE' if f & 0x02000000 > 0
|
244
|
+
r << ' SHARED' if f & 0x10000000 > 0
|
245
|
+
r
|
246
|
+
end
|
247
|
+
|
248
|
+
def pack
|
249
|
+
to_a.pack FORMAT.tr('A','a') # pad names with NULL bytes on pack()
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx
|
254
|
+
IMAGE_SUBSYSTEMS = %w'UNKNOWN NATIVE WINDOWS_GUI WINDOWS_CUI' + [nil,'OS2_CUI',nil,'POSIX_CUI',nil] +
|
255
|
+
%w'WINDOWS_CE_GUI EFI_APPLICATION EFI_BOOT_SERVICE_DRIVER EFI_RUNTIME_DRIVER EFI_ROM XBOX' +
|
256
|
+
[nil, 'WINDOWS_BOOT_APPLICATION']
|
257
|
+
|
258
|
+
# http://ntcore.com/files/richsign.htm
|
259
|
+
class RichHdr < String
|
260
|
+
attr_accessor :offset, :key # xor key
|
261
|
+
|
262
|
+
class Entry < Struct.new(:version,:id,:times)
|
263
|
+
def inspect
|
264
|
+
"<id=#{id}, version=#{version}, times=#{times}>"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.from_dos_stub stub
|
269
|
+
key = stub[stub.index('Rich')+4,4]
|
270
|
+
start_idx = stub.index(key.xor('DanS'))
|
271
|
+
end_idx = stub.index('Rich')+8
|
272
|
+
if stub[end_idx..-1].tr("\x00",'') != ''
|
273
|
+
t = stub[end_idx..-1]
|
274
|
+
t = "#{t[0,0x100]}..." if t.size > 0x100
|
275
|
+
PEdump.logger.error "[!] non-zero dos stub after rich_hdr: #{t.inspect}"
|
276
|
+
return nil
|
277
|
+
end
|
278
|
+
RichHdr.new(stub[start_idx, end_idx-start_idx]).tap do |x|
|
279
|
+
x.key = key
|
280
|
+
x.offset = stub.offset + start_idx
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def dexor
|
285
|
+
self[4..-9].sub(/\A(#{Regexp::escape(key)}){3}/,'').xor(key)
|
286
|
+
end
|
287
|
+
|
288
|
+
def decode
|
289
|
+
x = dexor
|
290
|
+
if x.size%8 == 0
|
291
|
+
x.unpack('vvV'*(x.size/8)).each_slice(3).map{ |slice| Entry.new(*slice)}
|
292
|
+
else
|
293
|
+
PEdump.logger.error "[?] #{self.class}: dexored size(#{x.size}) must be a multiple of 8"
|
294
|
+
nil
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
class DOSStub < String
|
300
|
+
attr_accessor :offset
|
301
|
+
end
|
302
|
+
|
303
|
+
def logger= l
|
304
|
+
@logger = @@logger = l
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.dump fname, params = {}
|
308
|
+
new(fname, params).dump
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.quiet
|
312
|
+
oldlevel = @@logger.level
|
313
|
+
@@logger.level = ::Logger::FATAL
|
314
|
+
yield
|
315
|
+
ensure
|
316
|
+
@@logger.level = oldlevel
|
317
|
+
end
|
318
|
+
|
319
|
+
def mz f=@io
|
320
|
+
@mz ||= f && MZ.read(f).tap do |mz|
|
321
|
+
if mz.signature != 'MZ' && mz.signature != 'ZM'
|
322
|
+
if @force
|
323
|
+
logger.warn "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
|
324
|
+
else
|
325
|
+
logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
|
326
|
+
return nil
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def dos_stub f=@io
|
333
|
+
@dos_stub ||=
|
334
|
+
begin
|
335
|
+
return nil unless mz = mz(f)
|
336
|
+
dos_stub_offset = mz.header_paragraphs.to_i * 0x10
|
337
|
+
dos_stub_size = mz.lfanew.to_i - dos_stub_offset
|
338
|
+
if dos_stub_offset < 0
|
339
|
+
logger.warn "[?] invalid DOS stub offset #{dos_stub_offset}"
|
340
|
+
nil
|
341
|
+
elsif f && dos_stub_offset > f.size
|
342
|
+
logger.warn "[?] DOS stub offset beyond EOF: #{dos_stub_offset}"
|
343
|
+
nil
|
344
|
+
elsif dos_stub_size < 0
|
345
|
+
logger.warn "[?] invalid DOS stub size #{dos_stub_size}"
|
346
|
+
nil
|
347
|
+
elsif dos_stub_size == 0
|
348
|
+
# no DOS stub, it's ok
|
349
|
+
nil
|
350
|
+
elsif !f
|
351
|
+
# no open file, it's ok
|
352
|
+
nil
|
353
|
+
else
|
354
|
+
return nil if dos_stub_size == MZ::SIZE && dos_stub_offset == 0
|
355
|
+
if dos_stub_size > 0x1000
|
356
|
+
logger.warn "[?] DOS stub size too big (#{dos_stub_size}), limiting to 0x1000"
|
357
|
+
dos_stub_size = 0x1000
|
358
|
+
end
|
359
|
+
f.seek dos_stub_offset
|
360
|
+
DOSStub.new(f.read(dos_stub_size)).tap do |dos_stub|
|
361
|
+
dos_stub.offset = dos_stub_offset
|
362
|
+
if dos_stub['Rich']
|
363
|
+
if @rich_hdr = RichHdr.from_dos_stub(dos_stub)
|
364
|
+
dos_stub[dos_stub.index(@rich_hdr)..-1] = ''
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def rich_hdr f=@io
|
373
|
+
dos_stub(f) && @rich_hdr
|
374
|
+
end
|
375
|
+
alias :rich_header :rich_hdr
|
376
|
+
alias :rich :rich_hdr
|
377
|
+
|
378
|
+
def va2file va, h={}
|
379
|
+
return nil if va.nil?
|
380
|
+
|
381
|
+
sections.each do |s|
|
382
|
+
if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
|
383
|
+
offset = va - s.VirtualAddress
|
384
|
+
return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# not found with regular search. assume any of VirtualSize was 0, and try with RawSize
|
389
|
+
sections.each do |s|
|
390
|
+
if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
|
391
|
+
offset = va - s.VirtualAddress
|
392
|
+
return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# still not found, bad/zero VirtualSizes & RawSizes ?
|
397
|
+
|
398
|
+
# a special case - PE without sections
|
399
|
+
return va if sections.empty?
|
400
|
+
|
401
|
+
# check if only one section
|
402
|
+
if sections.size == 1 || sections.all?{ |s| s.VirtualAddress.to_i == 0 }
|
403
|
+
s = sections.first
|
404
|
+
offset = va - s.VirtualAddress
|
405
|
+
return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
|
406
|
+
#return va - s.VirtualAddress + s.PointerToRawData
|
407
|
+
end
|
408
|
+
|
409
|
+
# TODO: not all VirtualAdresses == 0 case
|
410
|
+
|
411
|
+
if h[:quiet]
|
412
|
+
logger.debug "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)} (quiet=true)"
|
413
|
+
else
|
414
|
+
logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
|
415
|
+
end
|
416
|
+
nil
|
417
|
+
end
|
418
|
+
|
419
|
+
# OPTIONAL: assigns @mz, @rich_hdr, @pe, etc
|
420
|
+
def dump f=@io
|
421
|
+
if f.is_a?(String)
|
422
|
+
File.open(f,'rb'){ |f| _dump_handle(f) }
|
423
|
+
elsif f.is_a?(::IO)
|
424
|
+
_dump_handle f
|
425
|
+
elsif @io
|
426
|
+
_dump_handle @io
|
427
|
+
end
|
428
|
+
self
|
429
|
+
end
|
430
|
+
|
431
|
+
def _dump_handle h
|
432
|
+
return unless pe(h) # also calls mz(h)
|
433
|
+
rich_hdr h
|
434
|
+
resources h
|
435
|
+
imports h # also calls tls(h)
|
436
|
+
exports h
|
437
|
+
packer h
|
438
|
+
end
|
439
|
+
|
440
|
+
def data_directory f=@io
|
441
|
+
pe(f) && pe.ioh && pe.ioh.DataDirectory
|
442
|
+
end
|
443
|
+
|
444
|
+
def sections f=@io
|
445
|
+
if pe(f)
|
446
|
+
pe.section_table
|
447
|
+
elsif ne(f)
|
448
|
+
ne.segments
|
449
|
+
end
|
450
|
+
end
|
451
|
+
alias :section_table :sections
|
452
|
+
|
453
|
+
def ne?
|
454
|
+
@pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
|
455
|
+
end
|
456
|
+
|
457
|
+
def pe?
|
458
|
+
@pe ? true : (@ne ? false : (pe ? true : false ))
|
459
|
+
end
|
460
|
+
|
461
|
+
##############################################################################
|
462
|
+
# imports
|
463
|
+
##############################################################################
|
464
|
+
|
465
|
+
# http://sandsprite.com/CodeStuff/Understanding_imports.html
|
466
|
+
# http://stackoverflow.com/questions/5631317/import-table-it-vs-import-address-table-iat
|
467
|
+
IMAGE_IMPORT_DESCRIPTOR = IOStruct.new 'V5',
|
468
|
+
:OriginalFirstThunk,
|
469
|
+
:TimeDateStamp,
|
470
|
+
:ForwarderChain,
|
471
|
+
:Name,
|
472
|
+
:FirstThunk,
|
473
|
+
# manual:
|
474
|
+
:module_name,
|
475
|
+
:original_first_thunk,
|
476
|
+
:first_thunk
|
477
|
+
|
478
|
+
class ImportedFunction < Struct.new(:hint, :name, :ordinal, :va, :module_name)
|
479
|
+
def == x
|
480
|
+
self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal &&
|
481
|
+
self.module_name == x.module_name
|
482
|
+
end
|
483
|
+
# def <=> x
|
484
|
+
# self.to_a[0..-2] <=> x.to_a[0..-2]
|
485
|
+
# end
|
486
|
+
|
487
|
+
# magic to be able to easy merge :first_thunk & :original_first_thunk arrays
|
488
|
+
# (keeping va different)
|
489
|
+
def hash
|
490
|
+
[hint,name,ordinal,module_name].hash
|
491
|
+
end
|
492
|
+
def eql? x
|
493
|
+
self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal &&
|
494
|
+
self.module_name == x.module_name
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def imports f=@io
|
499
|
+
if pe(f)
|
500
|
+
pe_imports(f)
|
501
|
+
elsif ne(f)
|
502
|
+
ne(f).imports
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def pe_imports f=@io
|
507
|
+
return @imports if @imports
|
508
|
+
return nil unless pe(f) && pe(f).ioh && f
|
509
|
+
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
|
510
|
+
return [] if !dir || (dir.va == 0 && dir.size == 0)
|
511
|
+
file_offset = va2file(dir.va)
|
512
|
+
return nil unless file_offset
|
513
|
+
|
514
|
+
# scan TLS first, to catch many fake imports trick from
|
515
|
+
# http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
|
516
|
+
tls_aoi = nil
|
517
|
+
if (tls = tls(f)) && tls.any?
|
518
|
+
tls_aoi = tls.first.AddressOfIndex.to_i - @pe.ioh.ImageBase.to_i
|
519
|
+
tls_aoi = tls_aoi > 0 ? va2file(tls_aoi) : nil
|
520
|
+
end
|
521
|
+
|
522
|
+
r = []; t = nil
|
523
|
+
if f.checked_seek(file_offset)
|
524
|
+
while true
|
525
|
+
if tls_aoi && tls_aoi == file_offset+16
|
526
|
+
# catched the neat trick! :)
|
527
|
+
# f.tell + 12 = offset of 'FirstThunk' field from start of IMAGE_IMPORT_DESCRIPTOR structure
|
528
|
+
logger.warn "[!] catched the 'imports terminator in TLS trick'"
|
529
|
+
# http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
|
530
|
+
break
|
531
|
+
end
|
532
|
+
if r.size >= MAX_IMAGE_IMPORT_DESCRIPTORS
|
533
|
+
logger.warn "[!] too many IMAGE_IMPORT_DESCRIPTORs, not reading more than #{r.size}"
|
534
|
+
break
|
535
|
+
end
|
536
|
+
t = IMAGE_IMPORT_DESCRIPTOR.read(f)
|
537
|
+
break if t.Name.to_i == 0 # also catches EOF
|
538
|
+
r << t
|
539
|
+
file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
|
540
|
+
end
|
541
|
+
else
|
542
|
+
logger.warn "[?] imports info beyond EOF"
|
543
|
+
end
|
544
|
+
|
545
|
+
n_bad_names = 0
|
546
|
+
logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" if t && !t.empty?
|
547
|
+
@imports = r
|
548
|
+
r = nil
|
549
|
+
@imports.each_with_index do |x, iidx|
|
550
|
+
if n_bad_names > MAX_ERRORS
|
551
|
+
logger.warn "[!] too many bad imported function names. skipping further imports parsing"
|
552
|
+
@imports = @imports[0,iidx]
|
553
|
+
break
|
554
|
+
end
|
555
|
+
if x.Name.to_i != 0 && (ofs = va2file(x.Name))
|
556
|
+
begin
|
557
|
+
f.seek ofs
|
558
|
+
rescue
|
559
|
+
logger.warn "[?] cannot seek to #{ofs} (VA=0x#{x.Name.to_i.to_s(16)} for reading imports, skipped"
|
560
|
+
next
|
561
|
+
end
|
562
|
+
x.module_name = f.gets("\x00").to_s.chomp("\x00")
|
563
|
+
end
|
564
|
+
[:original_first_thunk, :first_thunk].each do |tbl|
|
565
|
+
camel = tbl.capitalize.to_s.gsub(/_./){ |char| char[1..-1].upcase}
|
566
|
+
if x[camel].to_i != 0 && (ofs = va2file(x[camel])) && f.checked_seek(ofs)
|
567
|
+
x[tbl] ||= []
|
568
|
+
if pe.x64?
|
569
|
+
x[tbl] << t while (t = f.read(8).to_s.unpack('Q').first).to_i != 0
|
570
|
+
else
|
571
|
+
x[tbl] << t while (t = f.read(4).to_s.unpack('V').first).to_i != 0
|
572
|
+
end
|
573
|
+
end
|
574
|
+
cache = {}
|
575
|
+
bits = pe.x64? ? 64 : 32
|
576
|
+
mask = 2**(bits-1)
|
577
|
+
idx = -1
|
578
|
+
x[tbl] && x[tbl].map! do |t|
|
579
|
+
idx += 1
|
580
|
+
va = x[camel].to_i + idx*4
|
581
|
+
cache[t] ||=
|
582
|
+
if t & mask > 0 # 0x8000_0000(_0000_0000)
|
583
|
+
ImportedFunction.new(nil,nil,t & (mask-1),va) # 0x7fff_ffff(_ffff_ffff)
|
584
|
+
elsif ofs=va2file(t, :quiet => true)
|
585
|
+
if !f.checked_seek(ofs) || f.eof?
|
586
|
+
logger.warn "[?] import ofs 0x#{ofs.to_s(16)} VA=0x#{t.to_s(16)} beyond EOF"
|
587
|
+
nil
|
588
|
+
else
|
589
|
+
hint = f.read(2).unpack('v').first
|
590
|
+
name = f.gets("\x00").chomp("\x00")
|
591
|
+
if !name.empty? && name !~ /\A[\x33-\x7f]+\Z/
|
592
|
+
n_bad_names += 1
|
593
|
+
if n_bad_names > MAX_ERRORS
|
594
|
+
nil
|
595
|
+
else
|
596
|
+
ImportedFunction.new(hint, name, nil, va)
|
597
|
+
end
|
598
|
+
else
|
599
|
+
ImportedFunction.new(hint, name, nil, va)
|
600
|
+
end
|
601
|
+
end
|
602
|
+
elsif tbl == :original_first_thunk
|
603
|
+
# OriginalFirstThunk entries can not be invalid, show a warning msg
|
604
|
+
logger.warn "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
|
605
|
+
nil
|
606
|
+
elsif tbl == :first_thunk
|
607
|
+
# FirstThunk entries can be invalid, so `info` msg only
|
608
|
+
logger.info "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
|
609
|
+
nil
|
610
|
+
else
|
611
|
+
raise "You are not supposed to be here! O_o"
|
612
|
+
end
|
613
|
+
end
|
614
|
+
x[tbl] && x[tbl].compact!
|
615
|
+
end # [:original_first_thunk, :first_thunk].each
|
616
|
+
if x.original_first_thunk && !x.first_thunk
|
617
|
+
logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
|
618
|
+
elsif !x.original_first_thunk && x.first_thunk
|
619
|
+
logger.info "[?] import table: empty OriginalFirstThunk for #{x.module_name}"
|
620
|
+
elsif logger.debug?
|
621
|
+
# compare all but VAs
|
622
|
+
if x.original_first_thunk != x.first_thunk
|
623
|
+
logger.debug "[?] import table: OriginalFirstThunk != FirstThunk for #{x.module_name}"
|
624
|
+
end
|
625
|
+
end
|
626
|
+
end # r.each
|
627
|
+
@imports
|
628
|
+
end
|
629
|
+
|
630
|
+
##############################################################################
|
631
|
+
# exports
|
632
|
+
##############################################################################
|
633
|
+
|
634
|
+
#http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
635
|
+
IMAGE_EXPORT_DIRECTORY = IOStruct.new 'V2v2V7',
|
636
|
+
:Characteristics,
|
637
|
+
:TimeDateStamp,
|
638
|
+
:MajorVersion, # These fields appear to be unused and are set to 0.
|
639
|
+
:MinorVersion, # These fields appear to be unused and are set to 0.
|
640
|
+
:Name,
|
641
|
+
:Base, # The starting ordinal number for exported functions
|
642
|
+
:NumberOfFunctions, # UNSIGNED!, perfectly valid when = 0xffff_ffff, see corkami/dllord.dll
|
643
|
+
:NumberOfNames,
|
644
|
+
:AddressOfFunctions,
|
645
|
+
:AddressOfNames,
|
646
|
+
:AddressOfNameOrdinals,
|
647
|
+
# manual:
|
648
|
+
:name, :entry_points, :names, :name_ordinals, :functions,
|
649
|
+
:description # NE only
|
650
|
+
|
651
|
+
class ExportedFunction < Struct.new :name, :ord, :va, :file_offset
|
652
|
+
def ordinal
|
653
|
+
self.ord
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
def exports f=@io
|
658
|
+
if pe(f)
|
659
|
+
pe_exports(f)
|
660
|
+
elsif ne(f)
|
661
|
+
ne(f).exports
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
def pe_exports f=@io
|
666
|
+
return @exports if @exports
|
667
|
+
return nil unless pe(f) && pe(f).ioh && f
|
668
|
+
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT]
|
669
|
+
return nil if !dir || (dir.va == 0 && dir.size == 0)
|
670
|
+
va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT].va
|
671
|
+
file_offset = va2file(va)
|
672
|
+
return nil unless file_offset
|
673
|
+
if !f.checked_seek(file_offset) || f.eof?
|
674
|
+
logger.warn "[?] exports info beyond EOF"
|
675
|
+
return nil
|
676
|
+
end
|
677
|
+
@exports = IMAGE_EXPORT_DIRECTORY.read(f).tap do |x|
|
678
|
+
x.entry_points = []
|
679
|
+
x.name_ordinals = []
|
680
|
+
x.names = []
|
681
|
+
if x.Name.to_i != 0 && (ofs = va2file(x.Name))
|
682
|
+
f.seek ofs
|
683
|
+
if f.eof?
|
684
|
+
logger.warn "[?] export ofs 0x#{ofs.to_s(16)} beyond EOF"
|
685
|
+
nil
|
686
|
+
else
|
687
|
+
x.name = f.gets("\x00").chomp("\x00")
|
688
|
+
end
|
689
|
+
end
|
690
|
+
if x.NumberOfFunctions.to_i > 0
|
691
|
+
if x.AddressOfFunctions.to_i !=0 && (ofs = va2file(x.AddressOfFunctions))
|
692
|
+
f.seek ofs
|
693
|
+
x.entry_points = []
|
694
|
+
x.NumberOfFunctions.times do
|
695
|
+
if f.eof?
|
696
|
+
logger.warn "[?] got EOF while reading exports entry_points"
|
697
|
+
break
|
698
|
+
end
|
699
|
+
x.entry_points << f.read(4).unpack('V').first
|
700
|
+
end
|
701
|
+
end
|
702
|
+
if x.AddressOfNameOrdinals.to_i !=0 && (ofs = va2file(x.AddressOfNameOrdinals))
|
703
|
+
f.seek ofs
|
704
|
+
x.name_ordinals = []
|
705
|
+
x.NumberOfNames.times do
|
706
|
+
if f.eof?
|
707
|
+
logger.warn "[?] got EOF while reading exports name_ordinals"
|
708
|
+
break
|
709
|
+
end
|
710
|
+
x.name_ordinals << f.read(2).unpack('v').first + x.Base
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
714
|
+
if x.NumberOfNames.to_i > 0 && x.AddressOfNames.to_i !=0 && (ofs = va2file(x.AddressOfNames))
|
715
|
+
f.seek ofs
|
716
|
+
x.names = []
|
717
|
+
x.NumberOfNames.times do
|
718
|
+
if f.eof?
|
719
|
+
logger.warn "[?] got EOF while reading exports names"
|
720
|
+
break
|
721
|
+
end
|
722
|
+
x.names << f.read(4).unpack('V').first
|
723
|
+
end
|
724
|
+
nErrors = 0
|
725
|
+
x.names.size.times do |i|
|
726
|
+
begin
|
727
|
+
f.seek va2file(x.names[i])
|
728
|
+
x.names[i] = f.gets("\x00").to_s.chomp("\x00")
|
729
|
+
rescue
|
730
|
+
nErrors += 1
|
731
|
+
if nErrors > MAX_ERRORS
|
732
|
+
logger.warn "[?] too many errors getting export names, stopped on #{i} of #{x.names.size}"
|
733
|
+
x.names = x.names[0,i]
|
734
|
+
break
|
735
|
+
end
|
736
|
+
nil
|
737
|
+
end
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
ord2name = {}
|
742
|
+
if x.names && x.names.any?
|
743
|
+
n = x.NumberOfNames
|
744
|
+
if n > 2048
|
745
|
+
logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to 2048"
|
746
|
+
n = 2048
|
747
|
+
end
|
748
|
+
n.times do |i|
|
749
|
+
ord2name[x.name_ordinals[i]] ||= []
|
750
|
+
ord2name[x.name_ordinals[i]] << x.names[i]
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
x.functions = []
|
755
|
+
x.entry_points.each_with_index do |ep,i|
|
756
|
+
names = ord2name[i+x.Base]
|
757
|
+
names = names.join(', ') if names
|
758
|
+
next if ep.to_i == 0 && names.nil?
|
759
|
+
x.functions << ExportedFunction.new(names, i+x.Base, ep)
|
760
|
+
end
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
##############################################################################
|
765
|
+
# TLS
|
766
|
+
##############################################################################
|
767
|
+
|
768
|
+
def tls f=@io
|
769
|
+
@tls ||= pe(f) && pe(f).ioh && f &&
|
770
|
+
begin
|
771
|
+
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::TLS]
|
772
|
+
return nil if !dir || dir.va == 0
|
773
|
+
return nil unless file_offset = va2file(dir.va)
|
774
|
+
f.seek file_offset
|
775
|
+
if f.eof?
|
776
|
+
logger.info "[?] TLS info beyond EOF"
|
777
|
+
return nil
|
778
|
+
end
|
779
|
+
|
780
|
+
klass = @pe.x64? ? IMAGE_TLS_DIRECTORY64 : IMAGE_TLS_DIRECTORY32
|
781
|
+
nEntries = [1,dir.size / klass.const_get('SIZE')].max
|
782
|
+
r = []
|
783
|
+
nEntries.times do
|
784
|
+
break if f.eof? || !(entry = klass.read(f))
|
785
|
+
r << entry
|
786
|
+
end
|
787
|
+
r
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
##############################################################################
|
792
|
+
# resources
|
793
|
+
##############################################################################
|
794
|
+
|
795
|
+
def resources f=@io
|
796
|
+
@resources ||=
|
797
|
+
if pe(f)
|
798
|
+
_scan_pe_resources(f)
|
799
|
+
elsif ne(f)
|
800
|
+
ne(f).resources(f)
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
def version_info f=@io
|
805
|
+
resources(f) && resources(f).find_all{ |res| res.type == 'VERSION' }.map(&:data).flatten
|
806
|
+
end
|
807
|
+
|
808
|
+
##############################################################################
|
809
|
+
# packer / compiler detection
|
810
|
+
##############################################################################
|
811
|
+
|
812
|
+
def packer f=@io
|
813
|
+
@packer ||= pe(f) && @pe.ioh &&
|
814
|
+
begin
|
815
|
+
if PEdump::Packer.all.size == 0
|
816
|
+
logger.error "[?] no packer definitions found"
|
817
|
+
nil
|
818
|
+
else
|
819
|
+
Packer.of f, :pedump => self
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
alias :packers :packer
|
824
|
+
end
|
825
|
+
|
826
|
+
####################################################################################
|
827
|
+
|
828
|
+
if $0 == __FILE__
|
829
|
+
require 'pp'
|
830
|
+
dump = PEdump.new(ARGV.shift).dump
|
831
|
+
if ARGV.any?
|
832
|
+
ARGV.each do |arg|
|
833
|
+
if dump.respond_to?(arg)
|
834
|
+
pp dump.send(arg)
|
835
|
+
elsif arg == 'restore_bitmaps'
|
836
|
+
File.open(dump.fname,"rb") do |fi|
|
837
|
+
r = dump.resources.
|
838
|
+
find_all{ |r| %w'ICON BITMAP CURSOR'.include?(r.type) }.
|
839
|
+
each do |r|
|
840
|
+
fname = r.name.tr("/# ",'_')+".bmp"
|
841
|
+
puts "[.] #{fname}"
|
842
|
+
File.open(fname,"wb"){ |fo| fo << r.restore_bitmap(fi) }
|
843
|
+
if mask = r.bitmap_mask(fi)
|
844
|
+
fname.sub! '.bmp', '.mask.bmp'
|
845
|
+
puts "[.] #{fname}"
|
846
|
+
File.open(fname,"wb"){ |fo| fo << r.bitmap_mask(fi) }
|
847
|
+
end
|
848
|
+
end
|
849
|
+
end
|
850
|
+
exit
|
851
|
+
else
|
852
|
+
puts "[?] invalid arg #{arg.inspect}"
|
853
|
+
end
|
854
|
+
end
|
855
|
+
exit
|
856
|
+
end
|
857
|
+
p dump.mz
|
858
|
+
dump.dos_stub.hexdump if dump.dos_stub
|
859
|
+
puts
|
860
|
+
if dump.rich_hdr
|
861
|
+
dump.rich_hdr.hexdump
|
862
|
+
puts
|
863
|
+
p(dump.rich_hdr.decode)
|
864
|
+
dump.rich_hdr.dexor.hexdump
|
865
|
+
end
|
866
|
+
pp dump.pe
|
867
|
+
pp dump.resources
|
868
|
+
end
|