pedump 0.4.0 → 0.5.0

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