pedump 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,173 @@
1
+ require 'pedump/sig_parser'
2
+
3
+ class PEdump
4
+ class Packer < Struct.new(:name, :re, :ep_only, :size)
5
+
6
+ DATA_ROOT = File.dirname(File.dirname(File.dirname(__FILE__)))
7
+ BIN_SIGS_FILE = File.join(DATA_ROOT, "data", "sig.bin")
8
+
9
+ class Match < Struct.new(:offset, :packer)
10
+ def name
11
+ packer.name
12
+ end
13
+ end
14
+
15
+ class Guess < Match
16
+ def initialize name
17
+ self.offset = 0
18
+ self.packer = Packer.new(name, nil, nil, 0)
19
+ end
20
+ end
21
+
22
+ class << self
23
+ def all
24
+ @@all ||=
25
+ begin
26
+ r = unmarshal
27
+ unless r
28
+ msg = "[?] #{self}: unmarshal failed, using slow text parsing instead"
29
+ if PEdump.respond_to?(:logger) && PEdump.logger
30
+ PEdump.logger.warn msg
31
+ else
32
+ STDERR.puts msg
33
+ end
34
+ r = SigParser.parse
35
+ end
36
+ r
37
+ end
38
+ end
39
+ alias :load :all
40
+
41
+ # default deep-scan flag
42
+ @@deep = false
43
+
44
+ def default_deep
45
+ @@deep
46
+ end
47
+
48
+ def default_deep= value
49
+ @@deep = value
50
+ end
51
+
52
+ def max_size
53
+ @@max_size ||= all.map(&:size).max
54
+ end
55
+
56
+ def of data, h = {}
57
+ if data.respond_to?(:read) && data.respond_to?(:seek) && h[:pedump]
58
+ of_pedump data, h
59
+ elsif data.respond_to?(:read) && data.respond_to?(:seek) && h[:ep_offset]
60
+ of_pe_file data, h
61
+ else
62
+ of_data data
63
+ end
64
+ end
65
+
66
+ # try to determine packer of FILE f, h[:pedump] is a PEdump instance
67
+ def of_pedump f, h
68
+ pedump = h[:pedump]
69
+ pe = pedump.pe
70
+ if !(va=pe.ioh.AddressOfEntryPoint)
71
+ pedump.logger.error "[?] can't find EntryPoint RVA"
72
+ nil
73
+ elsif va == 0 && pe.dll?
74
+ pedump.logger.debug "[.] it's a DLL with no EntryPoint"
75
+ nil
76
+ elsif !(ofs = pedump.va2file(va))
77
+ pedump.logger.error "[?] can't find EntryPoint RVA (0x#{va.to_s(16)}) file offset"
78
+ nil
79
+ else
80
+ r = of_pe_file(f, h.merge({:ep_offset => ofs}))
81
+ return r if r && r.any?
82
+
83
+ # nothing found, try to guess by pe section names
84
+ if pedump.sections
85
+ if pedump.sections.any?{ |s| s.Name.to_s =~ /upx/i }
86
+ return [Guess.new('UPX?')]
87
+ end
88
+ if pedump.sections.any?{ |s| s.Name.to_s =~ /aspack/i }
89
+ return [Guess.new('ASPack?')]
90
+ end
91
+ end
92
+ nil
93
+ end
94
+ end
95
+
96
+ # try to determine packer of FILE f, ep_offset - offset to entrypoint from start of file
97
+ def of_pe_file f, h
98
+ h[:deep] = @@deep unless h.key?(:deep)
99
+ h[:deep] = 1 if h[:deep] == true
100
+ h[:deep] = 0 if h[:deep] == false
101
+
102
+ begin
103
+ f.seek(h[:ep_offset]) # offset of PE EntryPoint from start of file
104
+ rescue
105
+ if h[:pedump] && h[:pedump].logger
106
+ h[:pedump].logger.warn "[?] failed to seek to EP at #{h[:ep_offset]}, skipping packer detect"
107
+ end
108
+ return
109
+ end
110
+
111
+ r = Array(of_data(f.read(max_size)))
112
+ return r if r && r.any? && h[:deep] < 2
113
+ r += scan_whole_file(f,
114
+ :limit => (h[:deep] > 0 ? nil : 1048576),
115
+ :deep => h[:deep]
116
+ ) # scan only 1st mb unless :deep
117
+ end
118
+
119
+ BLOCK_SIZE = 0x10000
120
+
121
+ def scan_whole_file f, h = {}
122
+ h[:limit] ||= f.size
123
+ f.seek( pos = 0 )
124
+ buf = ''.force_encoding('binary')
125
+ sigs =
126
+ if h[:deep].is_a?(Numeric) && h[:deep] > 1
127
+ self.all
128
+ else
129
+ self.find_all{ |sig| !sig.ep_only }
130
+ end
131
+ r = []
132
+ while true
133
+ f.read BLOCK_SIZE, buf
134
+ pos += buf.size
135
+ sigs.each do |sig|
136
+ if idx = buf.index(sig.re)
137
+ r << Match.new(f.tell-buf.size+idx, sig)
138
+ end
139
+ end
140
+ break if f.eof? || pos >= h[:limit]
141
+ # overlap the read for the case when read buffer boundary breaks signature
142
+ f.seek -max_size-2, IO::SEEK_CUR
143
+ pos -= (max_size+2)
144
+ end
145
+ r
146
+ end
147
+
148
+ def of_data data
149
+ r = []
150
+ return r unless data
151
+ each do |packer|
152
+ if (idx=data.index(packer.re)) == 0
153
+ r << Match.new(idx, packer)
154
+ end
155
+ end
156
+ r.any? ? r.sort_by{ |x| -x.packer.size } : nil
157
+ end
158
+
159
+ def method_missing *args, &block
160
+ all.respond_to?(args.first) ? all.send(*args,&block) : super
161
+ end
162
+
163
+ def unmarshal
164
+ File.open(BIN_SIGS_FILE,"rb") do |f|
165
+ Marshal.load(f)
166
+ end
167
+ rescue
168
+ nil
169
+ end
170
+
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,121 @@
1
+ require 'pedump/composite_io'
2
+
3
+ class PEdump
4
+ class PE < Struct.new(
5
+ :signature, # "PE\x00\x00"
6
+ :image_file_header,
7
+ :image_optional_header, # includes data directory
8
+ :section_table
9
+ )
10
+ alias :ifh :image_file_header
11
+ alias :ifh= :image_file_header=
12
+ alias :ioh :image_optional_header
13
+ alias :ioh= :image_optional_header=
14
+ alias :sections :section_table
15
+ alias :sections= :section_table=
16
+ def x64?
17
+ ifh && ifh.Machine == 0x8664
18
+ end
19
+ def dll?
20
+ ifh && ifh.flags.include?('DLL')
21
+ end
22
+
23
+ def pack
24
+ signature + ifh.pack + ioh.pack
25
+ end
26
+
27
+ def self.read f, args = {}
28
+ force = args[:force]
29
+
30
+ pe_offset = f.tell
31
+ pe_sig = f.read 4
32
+ #logger.error "[!] 'NE' format is not supported!" if pe_sig == "NE\x00\x00"
33
+ if pe_sig != "PE\x00\x00"
34
+ if force
35
+ logger.warn "[?] no PE signature (want: 'PE\\x00\\x00', got: #{pe_sig.inspect})"
36
+ else
37
+ logger.debug "[?] no PE signature (want: 'PE\\x00\\x00', got: #{pe_sig.inspect}). (not forced)"
38
+ return nil
39
+ end
40
+ end
41
+ PE.new(pe_sig).tap do |pe|
42
+ pe.image_file_header = IMAGE_FILE_HEADER.read(f)
43
+ ioh_offset = f.tell # offset to IMAGE_OPTIONAL_HEADER
44
+ if pe.ifh.SizeOfOptionalHeader.to_i > 0
45
+ if pe.x64?
46
+ pe.image_optional_header = IMAGE_OPTIONAL_HEADER64.read(f, pe.ifh.SizeOfOptionalHeader)
47
+ else
48
+ pe.image_optional_header = IMAGE_OPTIONAL_HEADER32.read(f, pe.ifh.SizeOfOptionalHeader)
49
+ end
50
+ end
51
+
52
+ if (nToRead=pe.ifh.NumberOfSections.to_i) > 0xffff
53
+ if force.is_a?(Numeric) && force > 1
54
+ logger.warn "[!] too many sections (#{pe.ifh.NumberOfSections}). forced. reading all"
55
+ else
56
+ logger.warn "[!] too many sections (#{pe.ifh.NumberOfSections}). not forced, reading first 65535"
57
+ nToRead = 65535
58
+ end
59
+ end
60
+
61
+ # The Windows loader expects to find the PE section headers after the optional header. It calculates the address of the first section header by adding SizeOfOptionalHeader to the beginning of the optional header.
62
+ # // http://www.phreedom.org/research/tinype/
63
+ f.seek( ioh_offset + pe.ifh.SizeOfOptionalHeader.to_i )
64
+ pe.sections = []
65
+ nToRead.times do
66
+ break if f.eof?
67
+ pe.sections << IMAGE_SECTION_HEADER.read(f)
68
+ end
69
+
70
+ if pe.sections.any?
71
+ # zero all missing values of last section
72
+ pe.sections.last.tap do |last_section|
73
+ last_section.each_pair do |k,v|
74
+ last_section[k] = 0 if v.nil?
75
+ end
76
+ end
77
+ end
78
+
79
+ pe_end = f.tell
80
+ if s=pe.sections.find{ |s| (pe_offset...pe_end).include?(s.va) }
81
+ if args[:pass2]
82
+ # already called with CompositeIO ?
83
+ logger.error "[!] section with va=0x#{s.va.to_s(16)} overwrites PE header! 2nd time?!"
84
+
85
+ elsif pe_end-pe_offset < 0x100_000
86
+ logger.warn "[!] section with va=0x#{s.va.to_s(16)} overwrites PE header! trying to rebuild..."
87
+ f.seek pe_offset
88
+ data = f.read(s.va-pe_offset)
89
+ f.seek s.PointerToRawData
90
+ io = CompositeIO.new(StringIO.new(data), f)
91
+ args1 = args.dup
92
+ args1[:pass2] = true
93
+ return PE.read(io, args1)
94
+ else
95
+ logger.error "[!] section with va=0x#{s.va.to_s(16)} overwrites PE header! too big to rebuild!"
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ def self.logger; PEdump.logger; end
102
+ end
103
+
104
+ def pe f=@io
105
+ @pe ||=
106
+ begin
107
+ pe_offset = mz(f) && mz(f).lfanew
108
+ if pe_offset.nil?
109
+ logger.fatal "[!] NULL PE offset (e_lfanew). cannot continue."
110
+ nil
111
+ elsif pe_offset > f.size
112
+ logger.fatal "[!] PE offset beyond EOF. cannot continue."
113
+ nil
114
+ else
115
+ f.seek pe_offset
116
+ PE.read f, :force => @force
117
+ end
118
+ end
119
+ end
120
+
121
+ end
@@ -0,0 +1,436 @@
1
+ class PEdump
2
+
3
+ def resource_directory f=@io
4
+ @resource_directory ||=
5
+ if pe(f)
6
+ _read_resource_directory_tree(f)
7
+ elsif ne(f)
8
+ ne(f).resource_directory(f)
9
+ end
10
+ end
11
+
12
+ def _read_resource_directory_tree f
13
+ return nil unless pe(f) && pe(f).ioh && f
14
+ res_dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE]
15
+ return [] if !res_dir || (res_dir.va == 0 && res_dir.size == 0)
16
+ res_va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE].va
17
+ res_section = @pe.section_table.find{ |t| t.VirtualAddress == res_va }
18
+ unless res_section
19
+ logger.warn "[?] can't find resource section for va=0x#{res_va.to_s(16)}"
20
+ return []
21
+ end
22
+ f.seek res_section.PointerToRawData
23
+ IMAGE_RESOURCE_DIRECTORY.base = res_section.PointerToRawData
24
+ #@resource_data_base = res_section.PointerToRawData - res_section.VirtualAddress
25
+ IMAGE_RESOURCE_DIRECTORY.read(f)
26
+ end
27
+
28
+ class Resource < Struct.new(:type, :name, :id, :lang, :file_offset, :size, :cp, :reserved, :data, :valid)
29
+ def bitmap_hdr
30
+ bmp_info_hdr = data.find{ |x| x.is_a?(BITMAPINFOHEADER) }
31
+ raise "no BITMAPINFOHEADER for #{self.type} #{self.name}" unless bmp_info_hdr
32
+
33
+ bmp_info_hdr.biHeight/=2 if %w'ICON CURSOR'.include?(type)
34
+
35
+ colors_used = bmp_info_hdr.biClrUsed
36
+ colors_used = 2**bmp_info_hdr.biBitCount if colors_used == 0 && bmp_info_hdr.biBitCount < 16
37
+
38
+ # XXX: one byte in each color is unused!
39
+ @palette_size = colors_used * 4 # each color takes 4 bytes
40
+
41
+ # scanlines are DWORD-aligned and padded to DWORD-align with zeroes
42
+ # XXX: some data may be hidden in padding bytes!
43
+ scanline_size = bmp_info_hdr.biWidth * bmp_info_hdr.biBitCount / 8
44
+ scanline_size += (4-scanline_size%4) if scanline_size % 4 > 0
45
+
46
+ @imgdata_size = scanline_size * bmp_info_hdr.biHeight
47
+ "BM" + [
48
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size + @imgdata_size,
49
+ 0,
50
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size
51
+ ].pack("V3") + bmp_info_hdr.pack
52
+ ensure
53
+ bmp_info_hdr.biHeight*=2 if %w'ICON CURSOR'.include?(type)
54
+ end
55
+
56
+ # only valid for types BITMAP, ICON & CURSOR
57
+ def restore_bitmap src_fname
58
+ File.open(src_fname, "rb") do |f|
59
+ parse f
60
+ if data.first == "PNG"
61
+ "\x89PNG" +f.read(self.size-4)
62
+ else
63
+ bitmap_hdr + f.read(@palette_size + @imgdata_size)
64
+ end
65
+ end
66
+ end
67
+
68
+ # only valid for types BITMAP, ICON & CURSOR
69
+ def restore_icon src_fname
70
+ File.open(src_fname, "rb") do |f|
71
+ parse f
72
+ if data.first == "PNG"
73
+ "\x89PNG" +f.read(self.size-4)
74
+ else
75
+ icondir = [
76
+ 0, # Reserved. Must always be 0.
77
+ 1, # image type: 1 for icon (.ICO), 2 for cursor (.CUR). Other values are invalid
78
+ 1, # number of images in the file
79
+ ].pack("v3")
80
+ bitmap_hdr = data.first # BITMAPINFOHEADER
81
+ icondirentry = ICODIRENTRY.new(
82
+ bitmap_hdr.biWidth,
83
+ bitmap_hdr.biHeight / (%w'ICON CURSOR'.include?(type) ? 2 : 1),
84
+ 0, # XXX: bColors: may be wrong here
85
+ 0,
86
+ 1,
87
+ bitmap_hdr.biBitCount,
88
+ bitmap_hdr.biSizeImage,
89
+ icondir.size + 2 + ICODIRENTRY::SIZE # offset of BMP data from the beginning of ICO file
90
+ )
91
+ # ICONDIRENTRY is 2 bytes larger than ICODIRENTRY
92
+ icondir + icondirentry.pack + "\x00\x00" + bitmap_hdr.pack + f.read(self.size)
93
+ end
94
+ end
95
+ end
96
+
97
+ # only valid for types BITMAP, ICON & CURSOR
98
+ def bitmap_mask src_fname
99
+ File.open(src_fname, "rb") do |f|
100
+ parse f
101
+ bmp_info_hdr = bitmap_hdr
102
+ bitmap_size = BITMAPINFOHEADER::SIZE + @palette_size + @imgdata_size
103
+ return nil if bitmap_size >= self.size
104
+
105
+ mask_size = self.size - bitmap_size
106
+ f.seek file_offset + bitmap_size
107
+
108
+ bmp_info_hdr = BITMAPINFOHEADER.new(*bmp_info_hdr[14..-1].unpack(BITMAPINFOHEADER::FORMAT))
109
+ bmp_info_hdr.biBitCount = 1
110
+ bmp_info_hdr.biCompression = bmp_info_hdr.biSizeImage = 0
111
+ bmp_info_hdr.biClrUsed = bmp_info_hdr.biClrImportant = 2
112
+
113
+ palette = [0,0xffffff].pack('V2')
114
+ @palette_size = palette.size
115
+
116
+ "BM" + [
117
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size + mask_size,
118
+ 0,
119
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size
120
+ ].pack("V3") + bmp_info_hdr.pack + palette + f.read(mask_size)
121
+ end
122
+ end
123
+
124
+ # also sets the file position for restore_bitmap next call
125
+ def parse f
126
+ raise "called parse with type not set" unless self.type
127
+ #return if self.data
128
+
129
+ self.data = []
130
+ return nil unless file_offset
131
+
132
+ case type
133
+ when 'BITMAP','ICON'
134
+ f.seek file_offset
135
+ if f.read(4) == "\x89PNG"
136
+ data << 'PNG'
137
+ else
138
+ f.seek file_offset
139
+ data << BITMAPINFOHEADER.read(f)
140
+ end
141
+ when 'CURSOR'
142
+ f.seek file_offset
143
+ data << CURSOR_HOTSPOT.read(f)
144
+ data << BITMAPINFOHEADER.read(f)
145
+ when 'GROUP_CURSOR'
146
+ f.seek file_offset
147
+ data << CUR_ICO_HEADER.read(f)
148
+ nRead = CUR_ICO_HEADER::SIZE
149
+ data.last.wNumImages.to_i.times do
150
+ if nRead >= self.size
151
+ PEdump.logger.error "[!] refusing to read CURDIRENTRY beyond resource size"
152
+ break
153
+ end
154
+ data << CURDIRENTRY.read(f)
155
+ nRead += CURDIRENTRY::SIZE
156
+ end
157
+ when 'GROUP_ICON'
158
+ f.seek file_offset
159
+ data << CUR_ICO_HEADER.read(f)
160
+ nRead = CUR_ICO_HEADER::SIZE
161
+ data.last.wNumImages.to_i.times do
162
+ if nRead >= self.size
163
+ PEdump.logger.error "[!] refusing to read ICODIRENTRY beyond resource size"
164
+ break
165
+ end
166
+ data << ICODIRENTRY.read(f)
167
+ nRead += ICODIRENTRY::SIZE
168
+ end
169
+ when 'STRING'
170
+ f.seek file_offset
171
+ 16.times do
172
+ break if f.tell >= file_offset+self.size
173
+ nChars = f.read(2).to_s.unpack('v').first.to_i
174
+ t =
175
+ if nChars*2 + 1 > self.size
176
+ # TODO: if it's not 1st string in table then truncated size must be less
177
+ PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
178
+ f.read(self.size-2)
179
+ else
180
+ f.read(nChars*2)
181
+ end
182
+ data <<
183
+ begin
184
+ t.force_encoding('UTF-16LE').encode!('UTF-8')
185
+ rescue
186
+ t.force_encoding('ASCII')
187
+ tt = t.size > 0x10 ? t[0,0x10].inspect+'...' : t.inspect
188
+ PEdump.logger.error "[!] cannot convert #{tt} to UTF-16"
189
+ [nChars,t].pack('va*')
190
+ end
191
+ end
192
+ # XXX: check if readed strings summary length is less than resource data length
193
+ when 'VERSION'
194
+ f.seek file_offset
195
+ data << PEdump::VS_VERSIONINFO.read(f)
196
+ end
197
+
198
+ data.delete_if do |x|
199
+ valid = !x.respond_to?(:valid?) || x.valid?
200
+ PEdump.logger.warn "[?] ignoring invalid #{x.class}" unless valid
201
+ !valid
202
+ end
203
+ ensure
204
+ validate
205
+ end
206
+
207
+ def validate
208
+ self.valid = self.file_offset &&
209
+ case type
210
+ when 'BITMAP','ICON','CURSOR'
211
+ data.any?{ |x| x.is_a?(BITMAPINFOHEADER) && x.valid? } || data.first == 'PNG'
212
+ when 'GROUP_ICON'
213
+ # rough validation
214
+ data.first.is_a?(CUR_ICO_HEADER) && data.size == data.first.wNumImages.to_i+1
215
+ else
216
+ true
217
+ end
218
+ end
219
+
220
+ def valid?
221
+ valid
222
+ end
223
+ end
224
+
225
+ STRING = Struct.new(:id, :lang, :value)
226
+
227
+ def strings f=@io
228
+ r = []
229
+ Array(resources(f)).find_all{ |x| x.type == 'STRING'}.each do |res|
230
+ res.data.each_with_index do |string,idx|
231
+ r << STRING.new( ((res.id.to_i-1)<<4) + idx, res.lang, string ) unless string.empty?
232
+ end
233
+ end
234
+ r
235
+ end
236
+
237
+ # see also http://www.informit.com/articles/article.aspx?p=1186882 about icons format
238
+
239
+ class BITMAPINFOHEADER < IOStruct.new 'V3v2V6',
240
+ :biSize, # BITMAPINFOHEADER::SIZE
241
+ :biWidth,
242
+ :biHeight,
243
+ :biPlanes,
244
+ :biBitCount,
245
+ :biCompression,
246
+ :biSizeImage,
247
+ :biXPelsPerMeter,
248
+ :biYPelsPerMeter,
249
+ :biClrUsed,
250
+ :biClrImportant
251
+
252
+ def valid?
253
+ self.biSize == 40
254
+ end
255
+ end
256
+
257
+ # http://www.devsource.com/c/a/Architecture/Resources-From-PE-I/2/
258
+ CUR_ICO_HEADER = IOStruct.new('v3',
259
+ :wReserved, # always 0
260
+ :wResID, # always 2
261
+ :wNumImages # Number of cursor images/directory entries
262
+ )
263
+
264
+ CURDIRENTRY = IOStruct.new 'v4Vv',
265
+ :wWidth,
266
+ :wHeight, # Divide by 2 to get the actual height.
267
+ :wPlanes,
268
+ :wBitCount,
269
+ :dwBytesInImage,
270
+ :wID
271
+
272
+ CURSOR_HOTSPOT = IOStruct.new 'v2', :x, :y
273
+
274
+ ICODIRENTRY = IOStruct.new 'C4v2Vv',
275
+ :bWidth,
276
+ :bHeight,
277
+ :bColors,
278
+ :bReserved,
279
+ :wPlanes,
280
+ :wBitCount,
281
+ :dwBytesInImage,
282
+ :wID
283
+
284
+ ROOT_RES_NAMES = [nil] + # numeration is started from 1
285
+ %w'CURSOR BITMAP ICON MENU DIALOG STRING FONTDIR FONT ACCELERATORS RCDATA' +
286
+ %w'MESSAGETABLE GROUP_CURSOR' + [nil] + %w'GROUP_ICON' + [nil] +
287
+ %w'VERSION DLGINCLUDE' + [nil] + %w'PLUGPLAY VXD ANICURSOR ANIICON HTML MANIFEST'
288
+
289
+ IMAGE_RESOURCE_DIRECTORY_ENTRY = IOStruct.new 'V2',
290
+ :Name, :OffsetToData,
291
+ :name, :data
292
+
293
+ IMAGE_RESOURCE_DATA_ENTRY = IOStruct.new 'V4',
294
+ :OffsetToData, :Size, :CodePage, :Reserved
295
+
296
+ IMAGE_RESOURCE_DIRECTORY = IOStruct.new 'V2v4',
297
+ :Characteristics, :TimeDateStamp, # 2dw
298
+ :MajorVersion, :MinorVersion, :NumberOfNamedEntries, :NumberOfIdEntries, # 4w
299
+ :entries # manual
300
+ class IMAGE_RESOURCE_DIRECTORY
301
+ class << self
302
+ attr_accessor :base
303
+ alias :read_without_children :read
304
+ def read f, root=true
305
+ if root
306
+ @@loopchk1 = Hash.new(0)
307
+ @@loopchk2 = Hash.new(0)
308
+ @@loopchk3 = Hash.new(0)
309
+ @@nErrors1 = 0
310
+ @@nErrors2 = 0
311
+ elsif (@@loopchk1[f.tell] += 1) > 1
312
+ PEdump.logger.error "[!] #{self}: loop1 detected at file pos #{f.tell}" if @@loopchk1[f.tell] < 2
313
+ return nil
314
+ end
315
+ read_without_children(f).tap do |r|
316
+ nToRead = r.NumberOfNamedEntries.to_i + r.NumberOfIdEntries.to_i
317
+ r.entries = []
318
+ nToRead.times do |i|
319
+ if f.eof?
320
+ PEdump.logger.error "[!] #{self}: #{nToRead} entries in directory, but got EOF on #{i}-th."
321
+ break
322
+ end
323
+ if (@@loopchk2[f.tell] += 1) > 1
324
+ PEdump.logger.error "[!] #{self}: loop2 detected at file pos #{f.tell}" if @@loopchk2[f.tell] < 2
325
+ next
326
+ end
327
+ r.entries << IMAGE_RESOURCE_DIRECTORY_ENTRY.read(f)
328
+ end
329
+ #r.entries.uniq!
330
+ r.entries.each_with_index do |entry,idx|
331
+ entry.name =
332
+ if (entry.Name.to_i & 0x8000_0000 > 0) && f.checked_seek(base + entry.Name & 0x7fff_ffff)
333
+ # Name is an address of unicode string
334
+ nChars = f.read(2).to_s.unpack("v").first.to_i
335
+ begin
336
+ f.read(nChars*2).force_encoding('UTF-16LE').encode!('UTF-8')
337
+ rescue
338
+ PEdump.logger.error "[!] #{self} failed to read entry name: #{$!}"
339
+ if (@@nErrors1+=1) > MAX_ERRORS
340
+ PEdump.logger.warn "[?] too many errors getting resource names, stopped on #{idx} of #{r.entries.size}"
341
+ r.entries = r.entries[0,idx]
342
+ break
343
+
344
+ end
345
+ "???"
346
+ end
347
+ else
348
+ # Name is a numeric id
349
+ "##{entry.Name}"
350
+ end
351
+ if entry.OffsetToData
352
+ if (@@loopchk3[entry.OffsetToData] += 1) > 1
353
+ PEdump.logger.error "[!] #{self}: loop3 detected at file pos #{f.tell}" if @@loopchk3[f.tell] < 2
354
+ if (@@nErrors2+=1) > MAX_ERRORS
355
+ PEdump.logger.warn "[?] too many errors getting resource data, stopped on #{idx} of #{r.entries.size}"
356
+ r.entries = r.entries[0,idx]
357
+ break
358
+
359
+ end
360
+ next
361
+ end
362
+ next unless f.checked_seek(base + entry.OffsetToData & 0x7fff_ffff)
363
+ entry.data =
364
+ if entry.OffsetToData & 0x8000_0000 > 0
365
+ # child is a directory
366
+ IMAGE_RESOURCE_DIRECTORY.read(f,false)
367
+ else
368
+ # child is a resource
369
+ IMAGE_RESOURCE_DATA_ENTRY.read(f)
370
+ end
371
+ end
372
+ end
373
+ @@loopchk1 = @@loopchk2 = @@loopchk3 = nil if root # save some memory
374
+ end
375
+ end
376
+ end
377
+ end
378
+
379
+ def _scan_pe_resources f=@io, dir=nil
380
+ dir ||= resource_directory(f)
381
+ return nil unless dir
382
+ @pe_res_errors ||= 0
383
+ r = []
384
+ dir.entries.each_with_index do |entry,idx|
385
+ case entry.data
386
+ when IMAGE_RESOURCE_DIRECTORY
387
+ if dir == @resource_directory # root resource directory
388
+ entry_type =
389
+ if entry.Name & 0x8000_0000 == 0
390
+ # root resource directory & entry name is a number
391
+ ROOT_RES_NAMES[entry.Name] || entry.name
392
+ else
393
+ entry.name
394
+ end
395
+ r += _scan_pe_resources(f,entry.data).each do |res|
396
+ res.type = entry_type
397
+ res.parse f
398
+ end
399
+ else
400
+ r += _scan_pe_resources(f,entry.data).each do |res|
401
+ res.name = res.name == "##{res.lang}" ? entry.name : "#{entry.name} / #{res.name}"
402
+ res.id ||= entry.Name if entry.Name.is_a?(Numeric) && entry.Name < 0x8000_0000
403
+ end
404
+ end
405
+ when IMAGE_RESOURCE_DATA_ENTRY
406
+ file_offset = va2file(entry.data.OffsetToData, :quiet => (@pe_res_errors > MAX_ERRORS))
407
+ unless file_offset
408
+ @pe_res_errors += 1
409
+ if @pe_res_errors > MAX_ERRORS
410
+ PEdump.logger.warn "[?] too many errors getting resource data, stopped on #{idx} of #{dir.entries.size}"
411
+ break
412
+ end
413
+ end
414
+ r << Resource.new(
415
+ nil, # type
416
+ entry.name,
417
+ nil, # id
418
+ entry.Name, # lang
419
+ #entry.data.OffsetToData + @resource_data_base,
420
+ file_offset,
421
+ entry.data.Size,
422
+ entry.data.CodePage,
423
+ entry.data.Reserved
424
+ )
425
+ else
426
+ if entry.data
427
+ logger.error "[!] invalid resource entry: #{entry.data.inspect}"
428
+ else
429
+ # show NULL entries only in verbose mode
430
+ logger.info "[!] invalid resource entry: #{entry.data.inspect}"
431
+ end
432
+ end
433
+ end
434
+ r.flatten.compact
435
+ end
436
+ end