pedump 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.travis.yml +4 -0
  2. data/Gemfile +10 -6
  3. data/Gemfile.lock +27 -19
  4. data/README.md +37 -25
  5. data/Rakefile +45 -6
  6. data/VERSION +1 -1
  7. data/data/fs.txt +37 -1408
  8. data/data/jc-userdb.txt +14371 -0
  9. data/data/sig.bin +0 -0
  10. data/lib/pedump.rb +355 -618
  11. data/lib/pedump/cli.rb +214 -113
  12. data/lib/pedump/comparer.rb +147 -0
  13. data/lib/pedump/composite_io.rb +56 -0
  14. data/lib/pedump/core.rb +38 -0
  15. data/lib/pedump/core_ext/try.rb +57 -0
  16. data/lib/pedump/loader.rb +393 -0
  17. data/lib/pedump/loader/minidump.rb +187 -0
  18. data/lib/pedump/loader/section.rb +57 -0
  19. data/lib/pedump/logger.rb +67 -0
  20. data/lib/pedump/ne.rb +425 -0
  21. data/lib/pedump/ne/version_info.rb +171 -0
  22. data/lib/pedump/packer.rb +50 -2
  23. data/lib/pedump/pe.rb +121 -0
  24. data/lib/pedump/resources.rb +436 -0
  25. data/lib/pedump/security.rb +58 -0
  26. data/lib/pedump/sig_parser.rb +145 -24
  27. data/lib/pedump/tls.rb +17 -0
  28. data/lib/pedump/unpacker.rb +26 -0
  29. data/lib/pedump/unpacker/aspack.rb +858 -0
  30. data/lib/pedump/unpacker/upx.rb +13 -0
  31. data/lib/pedump/version.rb +1 -1
  32. data/lib/pedump/version_info.rb +15 -10
  33. data/misc/aspack/Makefile +3 -0
  34. data/misc/aspack/aspack_unlzx.c +92 -0
  35. data/misc/aspack/lzxdec.c +479 -0
  36. data/misc/aspack/lzxdec.h +56 -0
  37. data/misc/nedump.c +751 -0
  38. data/pedump.gemspec +75 -25
  39. data/samples/bad/68.exe +0 -0
  40. data/samples/bad/data_dir_15_entries.exe +0 -0
  41. data/spec/65535sects_spec.rb +8 -0
  42. data/spec/bad_imports_spec.rb +20 -0
  43. data/spec/bad_samples_spec.rb +13 -0
  44. data/spec/composite_io_spec.rb +122 -0
  45. data/spec/data/calc.exe_sections.yml +49 -0
  46. data/spec/data/data_dir_15_entries.exe_sections.yml +95 -0
  47. data/spec/dllord_spec.rb +21 -0
  48. data/spec/foldedhdr_spec.rb +28 -0
  49. data/spec/imports_badterm_spec.rb +52 -0
  50. data/spec/imports_vterm_spec.rb +52 -0
  51. data/spec/loader/names_spec.rb +24 -0
  52. data/spec/loader/va_spec.rb +44 -0
  53. data/spec/manyimportsW7_spec.rb +22 -0
  54. data/spec/ne_spec.rb +125 -0
  55. data/spec/packer_spec.rb +17 -0
  56. data/spec/pe_spec.rb +67 -0
  57. data/spec/pedump_spec.rb +16 -4
  58. data/spec/sections_spec.rb +11 -0
  59. data/spec/sig_all_packers_spec.rb +15 -5
  60. data/spec/sig_spec.rb +6 -1
  61. data/spec/spec_helper.rb +15 -3
  62. data/spec/support/samples.rb +24 -0
  63. data/spec/unpackers/aspack_spec.rb +69 -0
  64. data/spec/unpackers/find_spec.rb +21 -0
  65. data/spec/virtsectblXP_spec.rb +12 -0
  66. data/tmp/.keep +0 -0
  67. metadata +146 -35
  68. data/README.md.tpl +0 -90
  69. data/samples/calc.7z +0 -0
  70. data/samples/zlib.dll +0 -0
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'iostruct'
4
+
5
+ class PEdump
6
+
7
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680378(v=vs.85).aspx
8
+ class MINIDUMP_HEADER < IOStruct.new 'a4LLLLLQ',
9
+ :Signature,
10
+ :Version,
11
+ :NumberOfStreams,
12
+ :StreamDirectoryRva,
13
+ :CheckSum,
14
+ :TimeDateStamp,
15
+ :Flags
16
+
17
+ def valid?
18
+ self.Signature == 'MDMP'
19
+ end
20
+ end
21
+
22
+ MINIDUMP_LOCATION_DESCRIPTOR = IOStruct.new 'LL', :DataSize, :Rva
23
+
24
+ class MINIDUMP_DIRECTORY < IOStruct.new 'L', :StreamType, :Location
25
+ def self.read io
26
+ r = super
27
+ r.Location = MINIDUMP_LOCATION_DESCRIPTOR.read(io)
28
+ r
29
+ end
30
+ end
31
+
32
+ MINIDUMP_MEMORY_INFO = IOStruct.new 'QQLLQLLLL',
33
+ :BaseAddress,
34
+ :AllocationBase,
35
+ :AllocationProtect,
36
+ :__alignment1,
37
+ :RegionSize,
38
+ :State,
39
+ :Protect,
40
+ :Type,
41
+ :__alignment2
42
+
43
+ class MINIDUMP_MEMORY_INFO_LIST < IOStruct.new 'LLQ',
44
+ :SizeOfHeader,
45
+ :SizeOfEntry,
46
+ :NumberOfEntries,
47
+ :entries
48
+
49
+ def self.read io
50
+ r = super
51
+ r.entries = r.NumberOfEntries.times.map{ MINIDUMP_MEMORY_INFO.read(io) }
52
+ r
53
+ end
54
+ end
55
+
56
+ MINIDUMP_MEMORY_DESCRIPTOR64 = IOStruct.new 'QQ',
57
+ :StartOfMemoryRange,
58
+ :DataSize
59
+
60
+ class MINIDUMP_MEMORY64_LIST < IOStruct.new 'QQ',
61
+ :NumberOfMemoryRanges,
62
+ :BaseRva,
63
+ :MemoryRanges
64
+
65
+ def self.read io
66
+ r = super
67
+ r.MemoryRanges = r.NumberOfMemoryRanges.times.map{ MINIDUMP_MEMORY_DESCRIPTOR64.read(io) }
68
+ r
69
+ end
70
+
71
+ def entries; self.MemoryRanges; end
72
+ end
73
+
74
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680394(v=vs.85).aspx
75
+ MINIDUMP_STREAM_TYPE = {
76
+ 0 => :UnusedStream,
77
+ 1 => :ReservedStream0,
78
+ 2 => :ReservedStream1,
79
+ 3 => :ThreadListStream,
80
+ 4 => :ModuleListStream,
81
+ 5 => :MemoryListStream,
82
+ 6 => :ExceptionStream,
83
+ 7 => :SystemInfoStream,
84
+ 8 => :ThreadExListStream,
85
+ 9 => :Memory64ListStream, # MINIDUMP_MEMORY64_LIST
86
+ 10 => :CommentStreamA,
87
+ 11 => :CommentStreamW,
88
+ 12 => :HandleDataStream,
89
+ 13 => :FunctionTableStream,
90
+ 14 => :UnloadedModuleListStream,
91
+ 15 => :MiscInfoStream,
92
+ 16 => :MemoryInfoListStream, # MINIDUMP_MEMORY_INFO_LIST
93
+ 17 => :ThreadInfoListStream,
94
+ 18 => :HandleOperationListStream,
95
+ 0xffff => :LastReservedStream
96
+ }
97
+
98
+ class Loader
99
+ class Minidump
100
+ attr_accessor :hdr, :streams, :io
101
+
102
+ def initialize io
103
+ @io = io
104
+ @hdr = MINIDUMP_HEADER.read(@io)
105
+ raise "invalid minidump" unless @hdr.valid?
106
+ end
107
+
108
+ def streams
109
+ @streams ||=
110
+ begin
111
+ @io.seek(@hdr.StreamDirectoryRva)
112
+ @hdr.NumberOfStreams.times.map do
113
+ dir = MINIDUMP_DIRECTORY.read(io)
114
+ dir.Location.empty? ? nil : dir
115
+ end.compact
116
+ end
117
+ end
118
+
119
+ def memory_info_list
120
+ # MINIDUMP_MEMORY_INFO_LIST
121
+ stream = streams.find{ |s| s.StreamType == 16 }
122
+ return nil unless stream
123
+ io.seek stream.Location.Rva
124
+ MINIDUMP_MEMORY_INFO_LIST.read io
125
+ end
126
+
127
+ def memory_list
128
+ # MINIDUMP_MEMORY64_LIST
129
+ stream = streams.find{ |s| s.StreamType == 9 }
130
+ return nil unless stream
131
+ io.seek stream.Location.Rva
132
+ MINIDUMP_MEMORY64_LIST.read io
133
+ end
134
+
135
+ MemoryRange = Struct.new :file_offset, :va, :size
136
+
137
+ # set options[:merge] = true to merge adjacent memory ranges
138
+ def memory_ranges options = {}
139
+ ml = memory_list
140
+ file_offset = ml.BaseRva
141
+ r = []
142
+ if options[:merge]
143
+ ml.entries.each do |x|
144
+ if r.last && r.last.va + r.last.size == x.StartOfMemoryRange
145
+ # if section VA == prev_section.VA + prev_section.SIZE
146
+ # then just increase the size of previous section
147
+ r.last.size += x.DataSize
148
+ else
149
+ r << MemoryRange.new( file_offset, x.StartOfMemoryRange, x.DataSize )
150
+ end
151
+ file_offset += x.DataSize
152
+ end
153
+ else
154
+ ml.entries.each do |x|
155
+ r << MemoryRange.new( file_offset, x.StartOfMemoryRange, x.DataSize )
156
+ file_offset += x.DataSize
157
+ end
158
+ end
159
+ r
160
+ end
161
+
162
+ end # class Minidump
163
+ end # class Loader
164
+ end # module PEdump
165
+
166
+ ##############################################
167
+
168
+ if $0 == __FILE__
169
+ require 'pp'
170
+
171
+ raise "gimme a fname" if ARGV.empty?
172
+ io = open(ARGV.first,"rb")
173
+
174
+ md = PEdump::Loader::Minidump.new io
175
+ pp md.hdr
176
+ puts
177
+ puts "[.] #{md.memory_ranges.size} memory ranges"
178
+ puts "[.] #{md.memory_ranges(:merge => true).size} merged memory ranges"
179
+ puts
180
+
181
+ # pp md.memory_info_list
182
+ # pp md.memory_list
183
+
184
+ md.memory_ranges(:merge => true).each do |mr|
185
+ printf "[.] %8x %8x %8x\n", mr.file_offset, mr.va, mr.size
186
+ end
187
+ end
@@ -0,0 +1,57 @@
1
+ class PEdump::Loader
2
+ class Section
3
+ attr_accessor :hdr
4
+ attr_writer :data
5
+
6
+ EMPTY_DATA = ''.force_encoding('binary')
7
+
8
+ def initialize x = nil, args = {}
9
+ if x.is_a?(PEdump::IMAGE_SECTION_HEADER)
10
+ @hdr = x.dup
11
+ end
12
+ @data = EMPTY_DATA.dup
13
+ @deferred_load_io = args[:deferred_load_io]
14
+ @deferred_load_pos = args[:deferred_load_pos] || (@hdr && @hdr.PointerToRawData)
15
+ @deferred_load_size = args[:deferred_load_size] || (@hdr && @hdr.SizeOfRawData)
16
+ @image_base = args[:image_base] || 0
17
+ end
18
+
19
+ def name; @hdr.Name; end
20
+ def va ; @hdr.VirtualAddress + @image_base; end
21
+ def rva ; @hdr.VirtualAddress; end
22
+ def vsize; @hdr.VirtualSize; end
23
+ def flags; @hdr.Characteristics; end
24
+ def flags= f; @hdr.Characteristics= f; end
25
+
26
+ def data
27
+ if @data.empty? && @deferred_load_io && @deferred_load_pos && @deferred_load_size.to_i > 0
28
+ begin
29
+ old_pos = @deferred_load_io.tell
30
+ @deferred_load_io.seek @deferred_load_pos
31
+ @data = @deferred_load_io.binmode.read(@deferred_load_size) || EMPTY_DATA.dup
32
+ ensure
33
+ if @deferred_load_io && old_pos
34
+ @deferred_load_io.seek old_pos
35
+ @deferred_load_io = nil # prevent read only on 1st access to data
36
+ end
37
+ end
38
+ end
39
+ @data
40
+ end
41
+
42
+ def range
43
+ va...(va+vsize)
44
+ end
45
+
46
+ def inspect
47
+ r = "#<Section"
48
+ r << (" name=%-10s" % name.inspect) if name
49
+ r << " va=%8x vsize=%8x rawsize=%8s" % [
50
+ va, vsize,
51
+ @data.size > 0 ? @data.size.to_s(16) : (@deferred_load_io ? "<defer>" : 0)
52
+ ]
53
+ r << (" dlpos=%8x" % @deferred_load_pos) if @deferred_load_pos
54
+ r << ">"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,67 @@
1
+ require 'awesome_print' # for colored tty logging
2
+
3
+ class PEdump
4
+ class Logger < ::Logger
5
+ def initialize *args
6
+ super
7
+ @formatter = proc do |severity,_,_,msg|
8
+ # quick and dirty way to remove duplicate messages
9
+ if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
10
+ ''
11
+ else
12
+ @prevmsg = msg
13
+ "#{msg}\n"
14
+ end
15
+ end
16
+ @level = WARN
17
+ end
18
+ end
19
+
20
+ def Logger.create params
21
+ logger =
22
+ if params[:logger]
23
+ params[:logger]
24
+ else
25
+ logdev = params[:logdev] || STDERR
26
+ logger_class =
27
+ if params.key?(:color)
28
+ # forced color or not
29
+ params[:color] ? ColoredLogger : Logger
30
+ else
31
+ # set color if logdev is TTY
32
+ (logdev.respond_to?(:tty?) && logdev.tty?) ? ColoredLogger : Logger
33
+ end
34
+ logger_class.new(logdev)
35
+ end
36
+
37
+ logger.level = params[:log_level] if params[:log_level]
38
+ logger
39
+ end
40
+
41
+ class ColoredLogger < ::Logger
42
+ def initialize *args
43
+ super
44
+ @formatter = proc do |severity,_,_,msg|
45
+ # quick and dirty way to remove duplicate messages
46
+ if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
47
+ ''
48
+ else
49
+ @prevmsg = msg
50
+ color =
51
+ case severity
52
+ when 'FATAL'
53
+ :redish
54
+ when 'ERROR'
55
+ :red
56
+ when 'WARN'
57
+ :yellowish
58
+ when 'DEBUG'
59
+ :gray
60
+ end
61
+ "#{color ? msg.send(color) : msg}\n"
62
+ end
63
+ end
64
+ @level = WARN
65
+ end
66
+ end
67
+ end
@@ -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