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