pedump 0.4.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/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +20 -0
- data/README.md +398 -0
- data/README.md.tpl +90 -0
- data/Rakefile +179 -0
- data/VERSION +1 -0
- data/bin/pedump +7 -0
- data/data/fs.txt +1595 -0
- data/data/sig.bin +0 -0
- data/data/signatures.txt +678 -0
- data/data/userdb.txt +14083 -0
- data/lib/pedump.rb +1105 -0
- data/lib/pedump/cli.rb +703 -0
- data/lib/pedump/packer.rb +125 -0
- data/lib/pedump/sig_parser.rb +386 -0
- data/lib/pedump/version.rb +10 -0
- data/lib/pedump/version_info.rb +166 -0
- data/pedump.gemspec +87 -0
- data/samples/calc.7z +0 -0
- data/samples/zlib.dll +0 -0
- data/spec/pedump_spec.rb +7 -0
- data/spec/resource_spec.rb +13 -0
- data/spec/sig_all_packers_spec.rb +14 -0
- data/spec/sig_spec.rb +63 -0
- data/spec/spec_helper.rb +12 -0
- metadata +157 -0
data/lib/pedump/cli.rb
ADDED
@@ -0,0 +1,703 @@
|
|
1
|
+
require 'pedump'
|
2
|
+
require 'pedump/packer'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
unless Object.instance_methods.include?(:try)
|
6
|
+
class Object
|
7
|
+
def try(*x)
|
8
|
+
send(*x) if respond_to?(x.first)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class PEdump::CLI
|
14
|
+
attr_accessor :data, :argv
|
15
|
+
|
16
|
+
KNOWN_ACTIONS = (
|
17
|
+
%w'mz dos_stub rich pe data_directory sections' +
|
18
|
+
%w'strings resources resource_directory imports exports version_info packer web packer_only'
|
19
|
+
).map(&:to_sym)
|
20
|
+
|
21
|
+
DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only'.map(&:to_sym)
|
22
|
+
|
23
|
+
URL_BASE = "http://pedump.me"
|
24
|
+
|
25
|
+
def initialize argv = ARGV
|
26
|
+
@argv = argv
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
@actions = []
|
31
|
+
@options = { :format => :table, :verbose => 0 }
|
32
|
+
optparser = OptionParser.new do |opts|
|
33
|
+
opts.banner = "Usage: pedump [options]"
|
34
|
+
|
35
|
+
opts.on "--version", "Print version information and exit" do
|
36
|
+
puts PEdump::VERSION
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
opts.on "-v", "--verbose", "Run verbosely","(can be used multiple times)" do |v|
|
40
|
+
@options[:verbose] += 1
|
41
|
+
end
|
42
|
+
opts.on "-q", "--quiet", "Silent any warnings","(can be used multiple times)" do |v|
|
43
|
+
@options[:verbose] -= 1
|
44
|
+
end
|
45
|
+
opts.on "-F", "--force", "Try to dump by all means","(can cause exceptions & heavy wounds)" do |v|
|
46
|
+
@options[:force] ||= 0
|
47
|
+
@options[:force] += 1
|
48
|
+
end
|
49
|
+
opts.on "-f", "--format FORMAT", [:binary, :c, :dump, :hex, :inspect, :table],
|
50
|
+
"Output format: bin,c,dump,hex,inspect,table","(default: table)" do |v|
|
51
|
+
@options[:format] = v
|
52
|
+
end
|
53
|
+
KNOWN_ACTIONS.each do |t|
|
54
|
+
a = [
|
55
|
+
"--#{t.to_s.tr('_','-')}",
|
56
|
+
eval("lambda{ |_| @actions << :#{t.to_s.tr('-','_')} }")
|
57
|
+
]
|
58
|
+
a.unshift(a[0][1,2].upcase) if a[0] =~ /--(((ex|im)port|section|resource)s|version-info)/
|
59
|
+
a.unshift(a[0][1,2]) if a[0] =~ /--strings/
|
60
|
+
opts.on *a
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on "--deep", "packer deep scan, significantly slower" do
|
64
|
+
@options[:deep] ||= 0
|
65
|
+
@options[:deep] += 1
|
66
|
+
PEdump::Packer.default_deep = @options[:deep]
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on '-P', "--packer-only", "packer/compiler detect only,","mimics 'file' command output" do
|
70
|
+
@actions << :packer_only
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on "--all", "Dump all but resource-directory (default)" do
|
74
|
+
@actions = DEFAULT_ALL_ACTIONS
|
75
|
+
end
|
76
|
+
opts.on "--va2file VA", "Convert RVA to file offset" do |va|
|
77
|
+
@actions << [:va2file,va]
|
78
|
+
end
|
79
|
+
opts.on "-W", "--web", "Uploads files to a #{URL_BASE}","for a nice HTML tables with image previews,","candies & stuff" do
|
80
|
+
@actions << :web
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if (@argv = optparser.parse(@argv)).empty?
|
85
|
+
puts optparser.help
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
if (@actions-KNOWN_ACTIONS).any?{ |x| !x.is_a?(Array) }
|
90
|
+
puts "[?] unknown actions: #{@actions-KNOWN_ACTIONS}"
|
91
|
+
@actions.delete_if{ |x| !KNOWN_ACTIONS.include?(x) }
|
92
|
+
end
|
93
|
+
@actions = DEFAULT_ALL_ACTIONS if @actions.empty?
|
94
|
+
|
95
|
+
if @actions.include?(:packer_only)
|
96
|
+
raise "[!] can't mix --packer-only with other actions" if @actions.size > 1
|
97
|
+
dump_packer_only(argv)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
argv.each_with_index do |fname,idx|
|
102
|
+
@need_fname_header = (argv.size > 1)
|
103
|
+
@file_idx = idx
|
104
|
+
@file_name = fname
|
105
|
+
|
106
|
+
File.open(fname,'rb') do |f|
|
107
|
+
@pedump = create_pedump fname
|
108
|
+
|
109
|
+
next if !@options[:force] && !@pedump.mz(f)
|
110
|
+
|
111
|
+
@actions.each do |action|
|
112
|
+
if action == :web
|
113
|
+
upload f
|
114
|
+
else
|
115
|
+
dump_action action,f
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
rescue Errno::EPIPE
|
121
|
+
# output interrupt, f.ex. when piping output to a 'head' command
|
122
|
+
# prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
|
123
|
+
end
|
124
|
+
|
125
|
+
def create_pedump fname
|
126
|
+
PEdump.new(fname, :force => @options[:force]).tap do |x|
|
127
|
+
x.logger.level =
|
128
|
+
case @options[:verbose]
|
129
|
+
when -100..-3
|
130
|
+
Logger::FATAL + 1
|
131
|
+
when -2
|
132
|
+
Logger::FATAL
|
133
|
+
when -1
|
134
|
+
Logger::ERROR
|
135
|
+
when 0
|
136
|
+
Logger::WARN # default
|
137
|
+
when 1
|
138
|
+
Logger::INFO
|
139
|
+
when 2..100
|
140
|
+
Logger::DEBUG
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def dump_packer_only fnames
|
146
|
+
max_fname_len = fnames.map(&:size).max
|
147
|
+
fnames.each do |fname|
|
148
|
+
File.open(fname,'rb') do |f|
|
149
|
+
@pedump = create_pedump fname
|
150
|
+
packers = @pedump.packers(f)
|
151
|
+
pname = Array(packers).first.try(:packer).try(:name)
|
152
|
+
pname ||= "unknown" if @options[:verbose] > 0
|
153
|
+
printf("%-*s %s\n", max_fname_len+1, "#{fname}:", pname) if pname
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class ProgressProxy
|
159
|
+
attr_reader :pbar
|
160
|
+
|
161
|
+
def initialize file
|
162
|
+
@file = file
|
163
|
+
@pbar = ProgressBar.new("[.] uploading", file.size, STDOUT)
|
164
|
+
@pbar.try(:file_transfer_mode)
|
165
|
+
@pbar.bar_mark = '='
|
166
|
+
end
|
167
|
+
def read *args
|
168
|
+
@pbar.inc args.first
|
169
|
+
@file.read *args
|
170
|
+
end
|
171
|
+
def method_missing *args
|
172
|
+
@file.send *args
|
173
|
+
end
|
174
|
+
def respond_to? *args
|
175
|
+
@file.respond_to?(*args) || super(*args)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def upload f
|
180
|
+
if @pedump.mz(f).signature != 'MZ'
|
181
|
+
@pedump.logger.error "[!] refusing to upload a non-MZ file"
|
182
|
+
return
|
183
|
+
end
|
184
|
+
|
185
|
+
require 'digest/md5'
|
186
|
+
require 'open-uri'
|
187
|
+
require 'net/http/post/multipart'
|
188
|
+
require 'progressbar'
|
189
|
+
|
190
|
+
stdout_sync = STDOUT.sync
|
191
|
+
STDOUT.sync = true
|
192
|
+
|
193
|
+
md5 = Digest::MD5.file(f.path).hexdigest
|
194
|
+
@pedump.logger.info "[.] md5: #{md5}"
|
195
|
+
file_url = "#{URL_BASE}/#{md5}/"
|
196
|
+
|
197
|
+
@pedump.logger.info "[.] checking if file already uploaded.."
|
198
|
+
begin
|
199
|
+
if (r=open(file_url).read) == "OK"
|
200
|
+
@pedump.logger.warn "[.] file already uploaded: #{file_url}"
|
201
|
+
return
|
202
|
+
else
|
203
|
+
raise "invalid server response: #{r}"
|
204
|
+
end
|
205
|
+
rescue OpenURI::HTTPError
|
206
|
+
raise unless $!.to_s == "404 Not Found"
|
207
|
+
end
|
208
|
+
|
209
|
+
f.rewind
|
210
|
+
|
211
|
+
# upload with progressbar
|
212
|
+
post_url = URI.parse(URL_BASE+'/')
|
213
|
+
uio = UploadIO.new(f, "application/octet-stream", File.basename(f.path))
|
214
|
+
ppx = ProgressProxy.new(uio)
|
215
|
+
req = Net::HTTP::Post::Multipart.new post_url.path, "file" => ppx
|
216
|
+
res = Net::HTTP.start(post_url.host, post_url.port){ |http| http.request(req) }
|
217
|
+
ppx.pbar.finish
|
218
|
+
|
219
|
+
puts
|
220
|
+
puts "[.] analyzing..."
|
221
|
+
|
222
|
+
if (r=open(File.join(URL_BASE,md5,'analyze')).read) != "OK"
|
223
|
+
raise "invalid server response: #{r}"
|
224
|
+
end
|
225
|
+
|
226
|
+
puts "[.] uploaded: #{file_url}"
|
227
|
+
ensure
|
228
|
+
STDOUT.sync = stdout_sync
|
229
|
+
end
|
230
|
+
|
231
|
+
def action_title action
|
232
|
+
if @need_fname_header
|
233
|
+
@need_fname_header = false
|
234
|
+
puts if @file_idx > 0
|
235
|
+
puts "# -----------------------------------------------"
|
236
|
+
puts "# #@file_name"
|
237
|
+
puts "# -----------------------------------------------"
|
238
|
+
end
|
239
|
+
|
240
|
+
s = action.to_s.upcase.tr('_',' ')
|
241
|
+
s += " Header" if [:mz, :pe, :rich].include?(action)
|
242
|
+
s = "Packer / Compiler" if action == :packer
|
243
|
+
"\n=== %s ===\n\n" % s
|
244
|
+
end
|
245
|
+
|
246
|
+
def dump_action action, f
|
247
|
+
if action.is_a?(Array)
|
248
|
+
case action[0]
|
249
|
+
when :va2file
|
250
|
+
@pedump.sections(f)
|
251
|
+
va = action[1] =~ /(^0x)|(h$)/i ? action[1].to_i(16) : action[1].to_i
|
252
|
+
file_offset = @pedump.va2file(va)
|
253
|
+
printf "va2file(0x%x) = 0x%x (%d)\n", va, file_offset, file_offset
|
254
|
+
return
|
255
|
+
else raise "unknown action #{action.inspect}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
data = @pedump.send(action, f)
|
260
|
+
return if !data || (data.respond_to?(:empty?) && data.empty?)
|
261
|
+
|
262
|
+
puts action_title(action)
|
263
|
+
|
264
|
+
return dump(data) if [:inspect, :table].include?(@options[:format])
|
265
|
+
|
266
|
+
dump_opts = {:name => action}
|
267
|
+
case action
|
268
|
+
when :pe
|
269
|
+
@pedump.pe.ifh.TimeDateStamp = @pedump.pe.ifh.TimeDateStamp.to_i
|
270
|
+
data = @pedump.pe.signature + (@pedump.pe.ifh.try(:pack)||'') + (@pedump.pe.ioh.try(:pack)||'')
|
271
|
+
@pedump.pe.ifh.TimeDateStamp = Time.at(@pedump.pe.ifh.TimeDateStamp)
|
272
|
+
when :resources
|
273
|
+
return dump_resources(data)
|
274
|
+
when :strings
|
275
|
+
return dump_strings(data)
|
276
|
+
when :imports
|
277
|
+
return dump_imports(data)
|
278
|
+
when :exports
|
279
|
+
return dump_exports(data)
|
280
|
+
when :version_info
|
281
|
+
return dump_version_info(data)
|
282
|
+
else
|
283
|
+
if data.is_a?(Struct) && data.respond_to?(:pack)
|
284
|
+
data = data.pack
|
285
|
+
elsif data.is_a?(Array) && data.all?{ |x| x.is_a?(Struct) && x.respond_to?(:pack)}
|
286
|
+
data = data.map(&:pack).join
|
287
|
+
end
|
288
|
+
end
|
289
|
+
dump data, dump_opts
|
290
|
+
end
|
291
|
+
|
292
|
+
def dump data, opts = {}
|
293
|
+
case opts[:format] || @options[:format] || :dump
|
294
|
+
when :dump, :hexdump
|
295
|
+
puts hexdump(data)
|
296
|
+
when :hex
|
297
|
+
puts data.each_byte.map{ |x| "%02x" % x }.join(' ')
|
298
|
+
when :binary
|
299
|
+
print data
|
300
|
+
when :c
|
301
|
+
name = opts[:name] || "foo"
|
302
|
+
puts "// #{data.size} bytes total"
|
303
|
+
puts "unsigned char #{name}[] = {"
|
304
|
+
data.unpack('C*').each_slice(12) do |row|
|
305
|
+
puts " " + row.map{ |c| "0x%02x," % c}.join(" ")
|
306
|
+
end
|
307
|
+
puts "};"
|
308
|
+
when :inspect
|
309
|
+
require 'pp'
|
310
|
+
pp data
|
311
|
+
when :table
|
312
|
+
dump_table data
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
COMMENTS = {
|
317
|
+
:Machine => {
|
318
|
+
0x014c => 'x86',
|
319
|
+
0x0200 => 'Intel Itanium',
|
320
|
+
0x8664 => 'x64',
|
321
|
+
'default' => '???'
|
322
|
+
},
|
323
|
+
:Magic => {
|
324
|
+
0x010b => '32-bit executable',
|
325
|
+
0x020b => '64-bit executable',
|
326
|
+
0x0107 => 'ROM image',
|
327
|
+
'default' => '???'
|
328
|
+
},
|
329
|
+
:Subsystem => PEdump::IMAGE_SUBSYSTEMS
|
330
|
+
}
|
331
|
+
|
332
|
+
def _flags2string flags
|
333
|
+
return '' if !flags || flags.empty?
|
334
|
+
a = [flags.shift.dup]
|
335
|
+
flags.each do |f|
|
336
|
+
if (a.last.size + f.size) < 40
|
337
|
+
a.last << ", " << f
|
338
|
+
else
|
339
|
+
a << f.dup
|
340
|
+
end
|
341
|
+
end
|
342
|
+
a.join("\n"+ ' '*58)
|
343
|
+
end
|
344
|
+
|
345
|
+
def dump_generic_table data
|
346
|
+
data.each_pair do |k,v|
|
347
|
+
next if [:DataDirectory, :section_table].include?(k)
|
348
|
+
case v
|
349
|
+
when Numeric
|
350
|
+
case k
|
351
|
+
when /\AMajor.*Version\Z/
|
352
|
+
printf "%30s: %24s\n", k.to_s.sub('Major',''), "#{v}.#{data[k.to_s.sub('Major','Minor')]}"
|
353
|
+
when /\AMinor.*Version\Z/
|
354
|
+
when /TimeDateStamp/
|
355
|
+
printf "%30s: %24s\n", k, Time.at(v).strftime('"%Y-%m-%d %H:%M:%S"')
|
356
|
+
else
|
357
|
+
comment = ''
|
358
|
+
if COMMENTS[k]
|
359
|
+
comment = COMMENTS[k][v] || (COMMENTS[k].is_a?(Hash) ? COMMENTS[k]['default'] : '') || ''
|
360
|
+
elsif data.is_a?(PEdump::IMAGE_FILE_HEADER) && k == :Characteristics
|
361
|
+
comment = _flags2string(data.flags)
|
362
|
+
elsif k == :DllCharacteristics
|
363
|
+
comment = _flags2string(data.flags)
|
364
|
+
end
|
365
|
+
comment.strip!
|
366
|
+
comment = " #{comment}" unless comment.empty?
|
367
|
+
printf "%30s: %10d %12s%s\n", k, v, v<10 ? v : ("0x"+v.to_s(16)), comment
|
368
|
+
end
|
369
|
+
when Struct
|
370
|
+
# IMAGE_FILE_HEADER:
|
371
|
+
# IMAGE_OPTIONAL_HEADER:
|
372
|
+
printf "\n# %s:\n", v.class.to_s.split('::').last
|
373
|
+
dump_table v
|
374
|
+
when Time
|
375
|
+
printf "%30s: %24s\n", k, v.strftime('"%Y-%m-%d %H:%M:%S"')
|
376
|
+
else
|
377
|
+
printf "%30s: %24s\n", k, v.to_s.inspect
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def dump_table data
|
383
|
+
if data.is_a?(Struct)
|
384
|
+
return dump_res_dir(data) if data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
385
|
+
return dump_exports(data) if data.is_a?(PEdump::IMAGE_EXPORT_DIRECTORY)
|
386
|
+
dump_generic_table data
|
387
|
+
elsif data.is_a?(Enumerable) && data.map(&:class).uniq.size == 1
|
388
|
+
case data.first
|
389
|
+
when PEdump::IMAGE_DATA_DIRECTORY
|
390
|
+
dump_data_dir data
|
391
|
+
when PEdump::IMAGE_SECTION_HEADER
|
392
|
+
dump_sections data
|
393
|
+
when PEdump::Resource
|
394
|
+
dump_resources data
|
395
|
+
when PEdump::STRING
|
396
|
+
dump_strings data
|
397
|
+
when PEdump::IMAGE_IMPORT_DESCRIPTOR
|
398
|
+
dump_imports data
|
399
|
+
when PEdump::Packer::Match
|
400
|
+
dump_packers data
|
401
|
+
when PEdump::VS_VERSIONINFO
|
402
|
+
dump_version_info data
|
403
|
+
else
|
404
|
+
puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
|
405
|
+
end
|
406
|
+
elsif data.is_a?(PEdump::DOSStub)
|
407
|
+
puts hexdump(data)
|
408
|
+
elsif data.is_a?(PEdump::RichHdr)
|
409
|
+
dump_rich_hdr data
|
410
|
+
else
|
411
|
+
puts "[?] Don't know how to display #{data.inspect[0,50]}... as a table"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def dump_version_info data
|
416
|
+
if @options[:format] != :table
|
417
|
+
File.open(@file_name,'rb') do |f|
|
418
|
+
@pedump.resources.find_all{ |r| r.type == 'VERSION'}.each do |res|
|
419
|
+
f.seek res.file_offset
|
420
|
+
data = f.read(res.size)
|
421
|
+
dump data
|
422
|
+
end
|
423
|
+
end
|
424
|
+
return
|
425
|
+
end
|
426
|
+
|
427
|
+
fmt = " %-20s: %s\n"
|
428
|
+
data.each do |vi|
|
429
|
+
puts "# VS_FIXEDFILEINFO:"
|
430
|
+
|
431
|
+
if @options[:verbose] > 0 || vi.Value.dwSignature != 0xfeef04bd
|
432
|
+
printf(fmt, "Signature", "0x#{vi.Value.dwSignature.to_s(16)}")
|
433
|
+
end
|
434
|
+
|
435
|
+
printf fmt, 'FileVersion', [
|
436
|
+
vi.Value.dwFileVersionMS >> 16,
|
437
|
+
vi.Value.dwFileVersionMS & 0xffff,
|
438
|
+
vi.Value.dwFileVersionLS >> 16,
|
439
|
+
vi.Value.dwFileVersionLS & 0xffff
|
440
|
+
].join('.')
|
441
|
+
|
442
|
+
printf fmt, 'ProductVersion', [
|
443
|
+
vi.Value.dwProductVersionMS >> 16,
|
444
|
+
vi.Value.dwProductVersionMS & 0xffff,
|
445
|
+
vi.Value.dwProductVersionLS >> 16,
|
446
|
+
vi.Value.dwProductVersionLS & 0xffff
|
447
|
+
].join('.')
|
448
|
+
|
449
|
+
vi.Value.each_pair do |k,v|
|
450
|
+
next if k[/[ML]S$/] || k == :valid || k == :dwSignature
|
451
|
+
printf fmt, k.to_s.sub(/^dw/,''), v > 9 ? "0x#{v.to_s(16)}" : v
|
452
|
+
end
|
453
|
+
|
454
|
+
vi.Children.each do |file_info|
|
455
|
+
case file_info
|
456
|
+
when PEdump::StringFileInfo
|
457
|
+
file_info.Children.each do |string_table|
|
458
|
+
puts "\n# StringTable #{string_table.szKey}:"
|
459
|
+
string_table.Children.each do |string|
|
460
|
+
printf fmt, string.szKey, string.Value.inspect
|
461
|
+
end
|
462
|
+
end
|
463
|
+
when PEdump::VarFileInfo
|
464
|
+
puts
|
465
|
+
printf fmt, "VarFileInfo", '[ 0x' + file_info.Children.Value.map{|v| v.to_s(16)}.join(", 0x") + ' ]'
|
466
|
+
else
|
467
|
+
puts "[?] unknown child type: #{file_info.inspect}, use -fi to inspect"
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def dump_packers data
|
474
|
+
if @options[:verbose] > 0
|
475
|
+
data.each do |p|
|
476
|
+
printf "%8x %4d %s\n", p.offset, p.packer.size, p.packer.name
|
477
|
+
end
|
478
|
+
else
|
479
|
+
# show only largest detected unless verbose output requested
|
480
|
+
puts " #{data.first.packer.name}"
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def dump_exports data
|
485
|
+
printf "# module %s\n# flags=0x%x ts=%s version=%d.%d ord_base=%d\n",
|
486
|
+
data.name.inspect,
|
487
|
+
data.Characteristics.to_i,
|
488
|
+
Time.at(data.TimeDateStamp.to_i).strftime('"%Y-%m-%d %H:%M:%S"'),
|
489
|
+
data.MajorVersion, data.MinorVersion,
|
490
|
+
data.Base
|
491
|
+
|
492
|
+
if @options[:verbose] > 0
|
493
|
+
[%w'Names', %w'EntryPoints Functions', %w'Ordinals NameOrdinals'].each do |x|
|
494
|
+
va = data["AddressOf"+x.last]
|
495
|
+
ofs = @pedump.va2file(va) || '?'
|
496
|
+
printf "# %-12s rva=0x%08x file_offset=%8s\n", x.first, va, ofs
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
printf "# nFuncs=%d nNames=%d\n",
|
501
|
+
data.NumberOfFunctions,
|
502
|
+
data.NumberOfNames
|
503
|
+
|
504
|
+
return unless data.name_ordinals.any? || data.entry_points.any? || data.names.any?
|
505
|
+
|
506
|
+
puts
|
507
|
+
|
508
|
+
ord2name = {}
|
509
|
+
data.NumberOfNames.times do |i|
|
510
|
+
ord2name[data.name_ordinals[i]] ||= []
|
511
|
+
ord2name[data.name_ordinals[i]] << data.names[i]
|
512
|
+
end
|
513
|
+
|
514
|
+
printf "%5s %8s %s\n", "ORD", "ENTRY_VA", "NAME"
|
515
|
+
data.NumberOfFunctions.times do |i|
|
516
|
+
ep = data.entry_points[i]
|
517
|
+
names = ord2name[i+data.Base].try(:join,', ')
|
518
|
+
next if ep.to_i == 0 && names.nil?
|
519
|
+
printf "%5d %8x %s\n", i + data.Base, ep, names
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
def dump_imports data
|
524
|
+
fmt = "%-15s %5s %5s %s\n"
|
525
|
+
printf fmt, "MODULE_NAME", "HINT", "ORD", "FUNCTION_NAME"
|
526
|
+
data.each do |iid|
|
527
|
+
# image import descriptor
|
528
|
+
(Array(iid.original_first_thunk) + Array(iid.first_thunk)).uniq.each do |f|
|
529
|
+
next unless f
|
530
|
+
# imported function
|
531
|
+
printf fmt,
|
532
|
+
iid.module_name,
|
533
|
+
f.hint ? f.hint.to_s(16) : '',
|
534
|
+
f.ordinal ? f.ordinal.to_s(16) : '',
|
535
|
+
f.name
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def dump_strings data
|
541
|
+
printf "%5s %5s %4s %s\n", "ID", "ID", "LANG", "STRING"
|
542
|
+
prev_lang = nil
|
543
|
+
data.sort_by{|s| [s.lang, s.id] }.each do |s|
|
544
|
+
#puts if prev_lang && prev_lang != s.lang
|
545
|
+
printf "%5d %5x %4x %s\n", s.id, s.id, s.lang, s.value.inspect
|
546
|
+
prev_lang = s.lang
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def dump_res_dir entry, level = 0
|
551
|
+
if entry.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
552
|
+
# root entry
|
553
|
+
printf "dir? %8s %8s %5s %5s", "FLAGS", "TIMESTMP", "VERS", 'nEnt'
|
554
|
+
printf " | %-15s %8s | ", "NAME", "OFFSET"
|
555
|
+
printf "data? %8s %8s %5s %8s\n", 'DATA_OFS', 'DATA_SZ', 'CP', 'RESERVED'
|
556
|
+
end
|
557
|
+
|
558
|
+
dir =
|
559
|
+
case entry
|
560
|
+
when PEdump::IMAGE_RESOURCE_DIRECTORY
|
561
|
+
entry
|
562
|
+
when PEdump::IMAGE_RESOURCE_DIRECTORY_ENTRY
|
563
|
+
entry.data
|
564
|
+
end
|
565
|
+
|
566
|
+
fmt1 = "DIR: %8x %8x %5s %5d"
|
567
|
+
fmt1s = fmt1.tr("xd\nDIR:","ss ") % ['','','','']
|
568
|
+
|
569
|
+
if dir.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
570
|
+
printf fmt1,
|
571
|
+
dir.Characteristics, dir.TimeDateStamp,
|
572
|
+
[dir.MajorVersion,dir.MinorVersion].join('.'),
|
573
|
+
dir.NumberOfNamedEntries + dir.NumberOfIdEntries
|
574
|
+
else
|
575
|
+
print fmt1s
|
576
|
+
end
|
577
|
+
|
578
|
+
name =
|
579
|
+
case level
|
580
|
+
when 0 then "ROOT"
|
581
|
+
when 1 then PEdump::ROOT_RES_NAMES[entry.Name] || entry.name
|
582
|
+
else entry.name
|
583
|
+
end
|
584
|
+
|
585
|
+
printf " | %-15s", name
|
586
|
+
printf("\n%s %15s",fmt1s,'') if name.size > 15
|
587
|
+
printf " %8x | ", entry.respond_to?(:OffsetToData) ? entry.OffsetToData : 0
|
588
|
+
|
589
|
+
if dir.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
590
|
+
puts
|
591
|
+
dir.entries.each do |child|
|
592
|
+
dump_res_dir child, level+1
|
593
|
+
end
|
594
|
+
elsif dir
|
595
|
+
printf "DATA: %8x %8x %5s %8x\n", dir.OffsetToData, dir.Size, dir.CodePage, dir.Reserved
|
596
|
+
else
|
597
|
+
puts # null dir
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
# def dump_res_dir0 dir, level=0, dir_entry = nil
|
602
|
+
# dir_entry ||= PEdump::IMAGE_RESOURCE_DIRECTORY_ENTRY.new
|
603
|
+
# printf "%-10s %8x %8x %8x %5s %5d\n", dir_entry.name || "ROOT", dir_entry.OffsetToData.to_i,
|
604
|
+
# dir.Characteristics, dir.TimeDateStamp,
|
605
|
+
# [dir.MajorVersion,dir.MinorVersion].join('.'),
|
606
|
+
# dir.NumberOfNamedEntries + dir.NumberOfIdEntries
|
607
|
+
# dir.entries.each do |child|
|
608
|
+
# if child.data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
609
|
+
# dump_res_dir child.data, level+1, child
|
610
|
+
# else
|
611
|
+
# print " "*(level+1) + "CHILD"
|
612
|
+
# child.data.each_pair do |k,v|
|
613
|
+
# print " #{k[0,2]}=#{v}"
|
614
|
+
# end
|
615
|
+
# puts
|
616
|
+
# #p child
|
617
|
+
# end
|
618
|
+
# end
|
619
|
+
# end
|
620
|
+
|
621
|
+
def dump_resources data
|
622
|
+
keys = []; fmt = []
|
623
|
+
fmt << "%11x " ; keys << :file_offset
|
624
|
+
fmt << "%5d " ; keys << :cp
|
625
|
+
fmt << "%5x " ; keys << :lang
|
626
|
+
fmt << "%8d " ; keys << :size
|
627
|
+
fmt << "%-13s "; keys << :type
|
628
|
+
fmt << "%s\n" ; keys << :name
|
629
|
+
printf fmt.join.tr('dx','s'), *keys.map(&:to_s).map(&:upcase)
|
630
|
+
data.each do |res|
|
631
|
+
fmt.each_with_index do |f,i|
|
632
|
+
v = res.send(keys[i])
|
633
|
+
if f['x']
|
634
|
+
printf f.tr('x','s'), v.to_i < 10 ? v.to_s : "0x#{v.to_s(16)}"
|
635
|
+
else
|
636
|
+
printf f, v
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def dump_sections data
|
643
|
+
printf " %-8s %8s %8s %8s %8s %5s %8s %5s %8s %8s\n",
|
644
|
+
'NAME', 'RVA', 'VSZ','RAW_SZ','RAW_PTR','nREL','REL_PTR','nLINE','LINE_PTR','FLAGS'
|
645
|
+
data.each do |s|
|
646
|
+
name = s.Name[/[^a-z0-9_.]/i] ? s.Name.inspect : s.Name
|
647
|
+
name = "#{name}\n " if name.size > 8
|
648
|
+
printf " %-8s %8x %8x %8x %8x %5x %8x %5x %8x %8x %s\n", name.to_s,
|
649
|
+
s.VirtualAddress.to_i, s.VirtualSize.to_i,
|
650
|
+
s.SizeOfRawData.to_i, s.PointerToRawData.to_i,
|
651
|
+
s.NumberOfRelocations.to_i, s.PointerToRelocations.to_i,
|
652
|
+
s.NumberOfLinenumbers.to_i, s.PointerToLinenumbers.to_i,
|
653
|
+
s.flags.to_i, s.flags_desc
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
def dump_data_dir data
|
658
|
+
data.each do |row|
|
659
|
+
printf " %-12s rva:0x%8x size:0x %8x\n", row.type, row.va.to_i, row.size.to_i
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
def dump_rich_hdr data
|
664
|
+
if decoded = data.decode
|
665
|
+
puts " LIB_ID VERSION TIMES_USED "
|
666
|
+
decoded.each do |row|
|
667
|
+
printf " %5d %2x %7d %4x %7d %3x\n",
|
668
|
+
row.id, row.id, row.version, row.version, row.times, row.times
|
669
|
+
end
|
670
|
+
else
|
671
|
+
puts "# raw:"
|
672
|
+
puts hexdump(data)
|
673
|
+
puts
|
674
|
+
puts "# dexored:"
|
675
|
+
puts hexdump(data.dexor)
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
def hexdump data, h = {}
|
680
|
+
offset = h[:offset] || 0
|
681
|
+
add = h[:add] || 0
|
682
|
+
size = h[:size] || (data.size-offset)
|
683
|
+
tail = h[:tail] || "\n"
|
684
|
+
width = h[:width] || 0x10 # row width, in bytes
|
685
|
+
|
686
|
+
size = data.size-offset if size+offset > data.size
|
687
|
+
|
688
|
+
r = ''; s = ''
|
689
|
+
r << "%08x: " % (offset + add)
|
690
|
+
ascii = ''
|
691
|
+
size.times do |i|
|
692
|
+
if i%width==0 && i>0
|
693
|
+
r << "%s |%s|\n%08x: " % [s, ascii, offset + add + i]
|
694
|
+
ascii = ''; s = ''
|
695
|
+
end
|
696
|
+
s << " " if i%width%8==0
|
697
|
+
c = data[offset+i].ord
|
698
|
+
s << "%02x " % c
|
699
|
+
ascii << ((32..126).include?(c) ? c.chr : '.')
|
700
|
+
end
|
701
|
+
r << "%-*s |%-*s|%s" % [width*3+width/8+(width%8==0?0:1), s, width, ascii, tail]
|
702
|
+
end
|
703
|
+
end
|