pedump 0.5.3

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