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.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
|