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.
- data/.travis.yml +4 -0
- data/Gemfile +10 -6
- data/Gemfile.lock +27 -19
- data/README.md +37 -25
- data/Rakefile +45 -6
- data/VERSION +1 -1
- data/data/fs.txt +37 -1408
- data/data/jc-userdb.txt +14371 -0
- data/data/sig.bin +0 -0
- data/lib/pedump.rb +355 -618
- data/lib/pedump/cli.rb +214 -113
- 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 +187 -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 +50 -2
- 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 +145 -24
- 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 +1 -1
- data/lib/pedump/version_info.rb +15 -10
- 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 +75 -25
- data/samples/bad/68.exe +0 -0
- data/samples/bad/data_dir_15_entries.exe +0 -0
- data/spec/65535sects_spec.rb +8 -0
- data/spec/bad_imports_spec.rb +20 -0
- data/spec/bad_samples_spec.rb +13 -0
- data/spec/composite_io_spec.rb +122 -0
- data/spec/data/calc.exe_sections.yml +49 -0
- data/spec/data/data_dir_15_entries.exe_sections.yml +95 -0
- data/spec/dllord_spec.rb +21 -0
- data/spec/foldedhdr_spec.rb +28 -0
- data/spec/imports_badterm_spec.rb +52 -0
- data/spec/imports_vterm_spec.rb +52 -0
- data/spec/loader/names_spec.rb +24 -0
- data/spec/loader/va_spec.rb +44 -0
- data/spec/manyimportsW7_spec.rb +22 -0
- data/spec/ne_spec.rb +125 -0
- data/spec/packer_spec.rb +17 -0
- data/spec/pe_spec.rb +67 -0
- data/spec/pedump_spec.rb +16 -4
- data/spec/sections_spec.rb +11 -0
- data/spec/sig_all_packers_spec.rb +15 -5
- data/spec/sig_spec.rb +6 -1
- data/spec/spec_helper.rb +15 -3
- data/spec/support/samples.rb +24 -0
- data/spec/unpackers/aspack_spec.rb +69 -0
- data/spec/unpackers/find_spec.rb +21 -0
- data/spec/virtsectblXP_spec.rb +12 -0
- data/tmp/.keep +0 -0
- metadata +146 -35
- data/README.md.tpl +0 -90
- data/samples/calc.7z +0 -0
- 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
|
data/lib/pedump/ne.rb
ADDED
@@ -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
|