pedump 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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