pedump 0.5.2 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pedump.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  require 'stringio'
3
3
  require 'iostruct'
4
4
  require 'zhexdump'
5
+ require 'set'
5
6
 
6
7
  unless Object.new.respond_to?(:try) && nil.respond_to?(:try)
7
8
  require 'pedump/core_ext/try'
@@ -16,6 +17,7 @@ require 'pedump/security'
16
17
  require 'pedump/packer'
17
18
  require 'pedump/ne'
18
19
  require 'pedump/ne/version_info'
20
+ require 'pedump/te'
19
21
 
20
22
  # pedump.rb by zed_0xff
21
23
  #
@@ -27,6 +29,10 @@ class PEdump
27
29
 
28
30
  VERSION = Version::STRING
29
31
  MAX_ERRORS = 100
32
+ MAX_IMAGE_IMPORT_DESCRIPTORS = 1000
33
+ MAX_EXPORT_NUMBER_OF_NAMES = 16384 # got 7977 in https://pedump.me/03ad7400080678c6b1984f995d36fd04
34
+ GOOD_FUNCTION_NAME_RE = /\A[\x21-\x7f]+\Z/
35
+ SUPPORTED_SIGNATURES = ['MZ', 'ZM', 'VZ']
30
36
 
31
37
  @@logger = nil
32
38
 
@@ -255,7 +261,7 @@ class PEdump
255
261
 
256
262
  # http://ntcore.com/files/richsign.htm
257
263
  class RichHdr < String
258
- attr_accessor :offset, :key # xor key
264
+ attr_accessor :offset, :skip, :key # xor key
259
265
 
260
266
  class Entry < Struct.new(:version,:id,:times)
261
267
  def inspect
@@ -264,8 +270,21 @@ class PEdump
264
270
  end
265
271
 
266
272
  def self.from_dos_stub stub
273
+ #stub.hexdump
267
274
  key = stub[stub.index('Rich')+4,4]
268
275
  start_idx = stub.index(key.xor('DanS'))
276
+ skip = 0
277
+ if start_idx
278
+ skip = 4
279
+ else
280
+ PEdump.logger.warn "[?] cannot find rich_hdr start_idx, using heuristics"
281
+ start_idx = stub.index("$\x00\x00\x00\x00\x00\x00\x00")
282
+ unless start_idx
283
+ PEdump.logger.warn "[?] heuristics failed :("
284
+ return nil
285
+ end
286
+ start_idx += 8
287
+ end
269
288
  end_idx = stub.index('Rich')+8
270
289
  if stub[end_idx..-1].tr("\x00",'') != ''
271
290
  t = stub[end_idx..-1]
@@ -273,14 +292,16 @@ class PEdump
273
292
  PEdump.logger.error "[!] non-zero dos stub after rich_hdr: #{t.inspect}"
274
293
  return nil
275
294
  end
295
+ #stub[start_idx, end_idx-start_idx].hexdump
276
296
  RichHdr.new(stub[start_idx, end_idx-start_idx]).tap do |x|
277
297
  x.key = key
278
298
  x.offset = stub.offset + start_idx
299
+ x.skip = skip
279
300
  end
280
301
  end
281
302
 
282
303
  def dexor
283
- self[4..-9].sub(/\A(#{Regexp::escape(key)}){3}/,'').xor(key)
304
+ self[skip..-9].sub(/\A(#{Regexp::escape(key)}){3}/,'').xor(key)
284
305
  end
285
306
 
286
307
  def decode
@@ -318,9 +339,9 @@ class PEdump
318
339
  @mz ||= f && MZ.read(f).tap do |mz|
319
340
  if mz.signature != 'MZ' && mz.signature != 'ZM'
320
341
  if @force
321
- logger.warn "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
342
+ #logger.warn "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
322
343
  else
323
- logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
344
+ #logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
324
345
  return nil
325
346
  end
326
347
  end
@@ -376,6 +397,13 @@ class PEdump
376
397
  def va2file va, h={}
377
398
  return nil if va.nil?
378
399
 
400
+ va0 = va # save for log output of original addr
401
+ if pe?
402
+ # most common case, do nothing
403
+ elsif te?
404
+ va = va - te_shift()
405
+ end
406
+
379
407
  sections.each do |s|
380
408
  if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
381
409
  offset = va - s.VirtualAddress
@@ -407,9 +435,9 @@ class PEdump
407
435
  # TODO: not all VirtualAdresses == 0 case
408
436
 
409
437
  if h[:quiet]
410
- logger.debug "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)} (quiet=true)"
438
+ logger.debug "[?] can't find file_offset of VA 0x#{va0.to_i.to_s(16)} (quiet=true)"
411
439
  else
412
- logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
440
+ logger.error "[?] can't find file_offset of VA 0x#{va0.to_i.to_s(16)}"
413
441
  end
414
442
  nil
415
443
  end
@@ -427,16 +455,22 @@ class PEdump
427
455
  end
428
456
 
429
457
  def _dump_handle h
430
- return unless pe(h) # also calls mz(h)
431
- rich_hdr h
432
- resources h
433
- imports h # also calls tls(h)
434
- exports h
435
- packer h
458
+ if pe(h) # also calls mz(h)
459
+ rich_hdr h
460
+ resources h
461
+ imports h # also calls tls(h)
462
+ exports h
463
+ packer h
464
+ elsif te(h)
465
+ end
436
466
  end
437
467
 
438
468
  def data_directory f=@io
439
- pe(f) && pe.ioh && pe.ioh.DataDirectory
469
+ if pe(f)
470
+ pe.ioh && pe.ioh.DataDirectory
471
+ elsif te(f)
472
+ te.DataDirectory
473
+ end
440
474
  end
441
475
 
442
476
  def sections f=@io
@@ -444,16 +478,52 @@ class PEdump
444
478
  pe.section_table
445
479
  elsif ne(f)
446
480
  ne.segments
481
+ elsif te(f)
482
+ te.sections
447
483
  end
448
484
  end
449
485
  alias :section_table :sections
450
486
 
451
- def ne?
452
- @pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
487
+ def supported_file? f=@io
488
+ pos = f.tell
489
+ sig = f.read(2)
490
+ f.seek(pos)
491
+ if SUPPORTED_SIGNATURES.include?(sig)
492
+ true
493
+ else
494
+ unless @not_supported_sig_warned
495
+ msg = "no supported signature. want: #{SUPPORTED_SIGNATURES.join("/")}, got: #{sig.inspect}"
496
+ if @force
497
+ logger.warn "[?] #{msg}"
498
+ else
499
+ logger.error "[!] #{msg}. (not forced)"
500
+ end
501
+ @not_supported_sig_warned = true
502
+ end
503
+ false
504
+ end
505
+ end
506
+
507
+ def _detect_format
508
+ return :pe if @pe
509
+ return :ne if @ne
510
+ return :te if @te
511
+ return :pe if pe()
512
+ return :ne if ne()
513
+ return :te if te()
514
+ nil
453
515
  end
454
516
 
455
517
  def pe?
456
- @pe ? true : (@ne ? false : (pe ? true : false ))
518
+ _detect_format() == :pe
519
+ end
520
+
521
+ def ne?
522
+ _detect_format() == :ne
523
+ end
524
+
525
+ def te?
526
+ _detect_format() == :te
457
527
  end
458
528
 
459
529
  ##############################################################################
@@ -498,6 +568,8 @@ class PEdump
498
568
  pe_imports(f)
499
569
  elsif ne(f)
500
570
  ne(f).imports
571
+ else
572
+ []
501
573
  end
502
574
  end
503
575
 
@@ -527,7 +599,11 @@ class PEdump
527
599
  # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
528
600
  break
529
601
  end
530
- t=IMAGE_IMPORT_DESCRIPTOR.read(f)
602
+ if r.size >= MAX_IMAGE_IMPORT_DESCRIPTORS
603
+ logger.warn "[!] too many IMAGE_IMPORT_DESCRIPTORs, not reading more than #{r.size}"
604
+ break
605
+ end
606
+ t = IMAGE_IMPORT_DESCRIPTOR.read(f)
531
607
  break if t.Name.to_i == 0 # also catches EOF
532
608
  r << t
533
609
  file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
@@ -536,8 +612,16 @@ class PEdump
536
612
  logger.warn "[?] imports info beyond EOF"
537
613
  end
538
614
 
615
+ n_bad_names = 0
539
616
  logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" if t && !t.empty?
540
- @imports = r.each do |x|
617
+ @imports = r
618
+ r = nil
619
+ @imports.each_with_index do |x, iidx|
620
+ if n_bad_names > MAX_ERRORS
621
+ logger.warn "[!] too many bad imported function names. skipping further imports parsing"
622
+ @imports = @imports[0,iidx]
623
+ break
624
+ end
541
625
  if x.Name.to_i != 0 && (ofs = va2file(x.Name))
542
626
  begin
543
627
  f.seek ofs
@@ -572,12 +656,18 @@ class PEdump
572
656
  logger.warn "[?] import ofs 0x#{ofs.to_s(16)} VA=0x#{t.to_s(16)} beyond EOF"
573
657
  nil
574
658
  else
575
- ImportedFunction.new(
576
- f.read(2).unpack('v').first,
577
- f.gets("\x00").chomp("\x00"),
578
- nil,
579
- va
580
- )
659
+ hint = f.read(2).unpack('v').first
660
+ name = f.gets("\x00").to_s.chomp("\x00")
661
+ if !name.empty? && name !~ GOOD_FUNCTION_NAME_RE
662
+ n_bad_names += 1
663
+ if n_bad_names > MAX_ERRORS
664
+ nil
665
+ else
666
+ ImportedFunction.new(hint, name, nil, va)
667
+ end
668
+ else
669
+ ImportedFunction.new(hint, name, nil, va)
670
+ end
581
671
  end
582
672
  elsif tbl == :original_first_thunk
583
673
  # OriginalFirstThunk entries can not be invalid, show a warning msg
@@ -592,7 +682,7 @@ class PEdump
592
682
  end
593
683
  end
594
684
  x[tbl] && x[tbl].compact!
595
- end
685
+ end # [:original_first_thunk, :first_thunk].each
596
686
  if x.original_first_thunk && !x.first_thunk
597
687
  logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
598
688
  elsif !x.original_first_thunk && x.first_thunk
@@ -603,7 +693,8 @@ class PEdump
603
693
  logger.debug "[?] import table: OriginalFirstThunk != FirstThunk for #{x.module_name}"
604
694
  end
605
695
  end
606
- end
696
+ end # r.each
697
+ @imports
607
698
  end
608
699
 
609
700
  ##############################################################################
@@ -720,9 +811,9 @@ class PEdump
720
811
  ord2name = {}
721
812
  if x.names && x.names.any?
722
813
  n = x.NumberOfNames
723
- if n > 2048
724
- logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to 2048"
725
- n = 2048
814
+ if n > MAX_EXPORT_NUMBER_OF_NAMES
815
+ logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to #{MAX_EXPORT_NUMBER_OF_NAMES}"
816
+ n = MAX_EXPORT_NUMBER_OF_NAMES
726
817
  end
727
818
  n.times do |i|
728
819
  ord2name[x.name_ordinals[i]] ||= []
data/lib/pedump/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'pedump'
2
2
  require 'pedump/packer'
3
+ require 'pedump/rich'
3
4
  require 'pedump/version_info'
4
5
  require 'optparse'
5
6
 
@@ -33,13 +34,14 @@ class PEdump::CLI
33
34
  attr_accessor :data, :argv
34
35
 
35
36
  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'
37
+ %w'mz dos_stub rich pe ne te data_directory sections tls security' +
38
+ %w'strings resources resource_directory imports exports version_info packer web console packer_only' +
39
+ %w'extract' # 'disasm'
38
40
  ).map(&:to_sym)
39
41
 
40
- DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console'.map(&:to_sym)
42
+ DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console extract disasm'.map(&:to_sym)
41
43
 
42
- URL_BASE = "http://pedump.me"
44
+ URL_BASE = "https://pedump.me"
43
45
 
44
46
  def initialize argv = ARGV
45
47
  @argv = argv
@@ -65,8 +67,8 @@ class PEdump::CLI
65
67
  @options[:force] ||= 0
66
68
  @options[:force] += 1
67
69
  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
+ opts.on "-f", "--format FORMAT", [:binary, :c, :dump, :hex, :inspect, :json, :table, :yaml],
71
+ "Output format: bin,c,dump,hex,inspect,json,table,yaml","(default: table)" do |v|
70
72
  @options[:format] = v
71
73
  end
72
74
  KNOWN_ACTIONS.each do |t|
@@ -96,8 +98,24 @@ class PEdump::CLI
96
98
  opts.on "--all", "Dump all but resource-directory (default)" do
97
99
  @actions = DEFAULT_ALL_ACTIONS
98
100
  end
101
+
102
+ opts.separator ''
103
+
104
+ # opts.on "--disasm [X]", "Disassemble a symbol/VA" do |v|
105
+ # @actions << [:disasm, v]
106
+ # end
107
+ opts.on("--extract ID", "Extract a resource/section/data_dir",
108
+ "ID: datadir:EXPORT - datadir by type",
109
+ "ID: resource:0x98478 - resource by offset",
110
+ "ID: resource:ICON/#1 - resource by type & name",
111
+ "ID: section:.text - section by name",
112
+ "ID: section:rva/0x1000 - section by RVA",
113
+ "ID: section:raw/0x400 - section by RAW_PTR",
114
+ ) do |v|
115
+ @actions << [:extract, v]
116
+ end
99
117
  opts.on "--va2file VA", "Convert RVA to file offset" do |va|
100
- @actions << [:va2file,va]
118
+ @actions << [:va2file, va]
101
119
  end
102
120
 
103
121
  opts.separator ''
@@ -133,9 +151,9 @@ class PEdump::CLI
133
151
  @file_name = fname
134
152
 
135
153
  File.open(fname,'rb') do |f|
136
- @pedump = create_pedump fname
154
+ @pedump = create_pedump f
137
155
 
138
- next if !@options[:force] && !@pedump.mz(f)
156
+ next if !@options[:force] && !@pedump.supported_file?(f)
139
157
 
140
158
  @actions.each do |action|
141
159
  case action
@@ -152,8 +170,8 @@ class PEdump::CLI
152
170
  # prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
153
171
  end
154
172
 
155
- def create_pedump fname
156
- PEdump.new(fname, :force => @options[:force]).tap do |x|
173
+ def create_pedump io
174
+ PEdump.new(io, :force => @options[:force]).tap do |x|
157
175
  x.logger.level =
158
176
  case @options[:verbose]
159
177
  when -100..-3
@@ -194,16 +212,14 @@ class PEdump::CLI
194
212
  end
195
213
 
196
214
  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 = '='
215
+ def initialize file, prefix = "[.] uploading: ", io = $stdout
216
+ @file = file
217
+ @io = io
218
+ @prefix = prefix
204
219
  end
205
220
  def read *args
206
- @pbar.inc args.first
221
+ @io.write("\r#{@prefix}#{@file.tell}/#{@file.size} ")
222
+ @io.flush
207
223
  @file.read *args
208
224
  end
209
225
  def method_missing *args
@@ -212,6 +228,10 @@ class PEdump::CLI
212
228
  def respond_to? *args
213
229
  @file.respond_to?(args.first) || super(*args)
214
230
  end
231
+
232
+ def finish!
233
+ @io.write("\r#{@prefix}#{@file.size}/#{@file.size} \n")
234
+ end
215
235
  end
216
236
 
217
237
  def upload f
@@ -224,17 +244,17 @@ class PEdump::CLI
224
244
  require 'open-uri'
225
245
  require 'net/http'
226
246
  require 'net/http/post/multipart'
227
- require 'progressbar'
228
247
 
229
- stdout_sync = STDOUT.sync
230
- STDOUT.sync = true
248
+ stdout_sync = $stdout.sync
249
+ $stdout.sync = true
231
250
 
232
251
  md5 = Digest::MD5.file(f.path).hexdigest
233
252
  @pedump.logger.info "[.] md5: #{md5}"
234
253
  file_url = "#{URL_BASE}/#{md5}/"
235
254
 
236
255
  @pedump.logger.warn "[.] checking if file already uploaded.."
237
- Net::HTTP.start('pedump.me') do |http|
256
+ uri = URI.parse URL_BASE
257
+ Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme == 'https')) do |http|
238
258
  http.open_timeout = 10
239
259
  http.read_timeout = 10
240
260
  # doing HTTP HEAD is a lot faster than open-uri
@@ -250,24 +270,26 @@ class PEdump::CLI
250
270
 
251
271
  f.rewind
252
272
 
253
- # upload with progressbar
273
+ # upload with progress
254
274
  post_url = URI.parse(URL_BASE+'/')
275
+ # UploadIO is from multipart-post
255
276
  uio = UploadIO.new(f, "application/octet-stream", File.basename(f.path))
256
277
  ppx = ProgressProxy.new(uio)
257
278
  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..."
279
+ res = Net::HTTP.start(post_url.host, post_url.port, use_ssl: (post_url.scheme == 'https')) do |http|
280
+ http.request(req)
281
+ end
282
+ ppx.finish!
263
283
 
264
- if (r=open(File.join(URL_BASE,md5,'analyze')).read) != "OK"
265
- raise "invalid server response: #{r}"
284
+ unless [200, 302, 303].include?(res.code.to_i)
285
+ @pedump.logger.fatal "[!] invalid server response: #{res.code} #{res.msg}"
286
+ @pedump.logger.fatal res.body unless res.body.empty?
287
+ exit(1)
266
288
  end
267
289
 
268
290
  puts "[.] uploaded: #{file_url}"
269
291
  ensure
270
- STDOUT.sync = stdout_sync
292
+ $stdout.sync = stdout_sync
271
293
  end
272
294
 
273
295
  def console f
@@ -311,6 +333,10 @@ class PEdump::CLI
311
333
  def dump_action action, f
312
334
  if action.is_a?(Array)
313
335
  case action[0]
336
+ when :disasm
337
+ return
338
+ when :extract
339
+ return extract action[1]
314
340
  when :va2file
315
341
  @pedump.sections(f)
316
342
  va = action[1] =~ /(^0x)|(h$)/i ? action[1].to_i(16) : action[1].to_i
@@ -326,7 +352,7 @@ class PEdump::CLI
326
352
 
327
353
  puts action_title(action) unless @options[:format] == :binary
328
354
 
329
- return dump(data) if [:inspect, :table, :yaml].include?(@options[:format])
355
+ return dump(data) if [:inspect, :table, :json, :yaml].include?(@options[:format])
330
356
 
331
357
  dump_opts = {:name => action}
332
358
  case action
@@ -376,6 +402,9 @@ class PEdump::CLI
376
402
  when :yaml
377
403
  require 'yaml'
378
404
  puts data.to_yaml
405
+ when :json
406
+ require 'json'
407
+ puts data.to_json
379
408
  end
380
409
  end
381
410
 
@@ -454,6 +483,8 @@ class PEdump::CLI
454
483
  case data.first
455
484
  when PEdump::IMAGE_DATA_DIRECTORY
456
485
  dump_data_dir data
486
+ when PEdump::EFI_IMAGE_DATA_DIRECTORY
487
+ dump_efi_data_dir data
457
488
  when PEdump::IMAGE_SECTION_HEADER
458
489
  dump_sections data
459
490
  when PEdump::Resource
@@ -778,19 +809,26 @@ class PEdump::CLI
778
809
  end
779
810
  end
780
811
 
781
-
782
812
  def dump_data_dir data
783
813
  data.each do |row|
784
814
  printf " %-12s rva:0x%8x size:0x %8x\n", row.type, row.va.to_i, row.size.to_i
785
815
  end
786
816
  end
787
817
 
818
+ def dump_efi_data_dir data
819
+ data.each_with_index do |row, idx|
820
+ printf " %-12s rva:0x%8x size:0x %8x\n", PEdump::EFI_IMAGE_DATA_DIRECTORY::TYPES[idx], row.va.to_i, row.size.to_i
821
+ end
822
+ end
823
+
788
824
  def dump_rich_hdr data
789
825
  if decoded = data.decode
790
- puts " LIB_ID VERSION TIMES_USED "
826
+ puts " ID VER COUNT DESCRIPTION"
791
827
  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
828
+ # printf " %5d %4x %7d %4x %12d %8x\n",
829
+ # row.id, row.id, row.version, row.version, row.times, row.times
830
+ printf " %4x %4x %12d %s\n",
831
+ row.id, row.version, row.times, ::PEdump::RICH_IDS[(row.id<<16) + row.version]
794
832
  end
795
833
  else
796
834
  puts "# raw:"
@@ -801,4 +839,79 @@ class PEdump::CLI
801
839
  end
802
840
  end
803
841
 
842
+ def disasm x
843
+ puts "TODO"
844
+ end
845
+
846
+ def extract x
847
+ a = x.split(':',2)
848
+ case a[0]
849
+ when 'datadir'
850
+ extract_datadir a[1]
851
+ when 'resource'
852
+ extract_resource a[1]
853
+ when 'section'
854
+ extract_section a[1]
855
+ else
856
+ raise "invalid #{x.inspect}"
857
+ end
858
+ end
859
+
860
+ def extract_datadir id
861
+ entry = @pedump.data_directory.find{ |x| x.type == id }
862
+ unless entry
863
+ @pedump.logger.fatal "[!] entry #{id.inspect} not found"
864
+ exit(1)
865
+ end
866
+ if entry.size != 0
867
+ IO::copy_stream @pedump.io, $stdout, entry.size, @pedump.va2file(entry.va)
868
+ end
869
+ end
870
+
871
+ def extract_resource id
872
+ res = nil
873
+ if id =~ /\A0x\h+\Z/
874
+ needle = id.to_i(16)
875
+ res = @pedump.resources.find{ |r| r.file_offset == needle }
876
+ elsif id =~ /\A\d+\Z/
877
+ needle = id.to_i(10)
878
+ res = @pedump.resources.find{ |r| r.file_offset == needle }
879
+ elsif id['/']
880
+ type, name = id.split('/', 2)
881
+ res = @pedump.resources.find{ |r| r.type == type && r.name == name }
882
+ else
883
+ @pedump.logger.fatal "[!] invalid resource id #{id.inspect}"
884
+ exit(1)
885
+ end
886
+ unless res
887
+ @pedump.logger.fatal "[!] resource #{id.inspect} not found"
888
+ exit(1)
889
+ end
890
+ IO::copy_stream @pedump.io, $stdout, res.size, res.file_offset
891
+ end
892
+
893
+ def extract_section id
894
+ section = nil
895
+ if id['/']
896
+ a = id.split('/',2)
897
+ if a[0] == 'rva'
898
+ value = a[1].to_i(0) # auto hex/dec, but also can parse oct and bin
899
+ section = @pedump.sections.find{ |s| s.VirtualAddress == value }
900
+ elsif a[0] == 'raw'
901
+ value = a[1].to_i(0) # auto hex/dec, but also can parse oct and bin
902
+ section = @pedump.sections.find{ |s| s.PointerToRawData == value }
903
+ else
904
+ @pedump.logger.fatal "[!] invalid section id #{id.inspect}"
905
+ exit(1)
906
+ end
907
+ else
908
+ section = @pedump.sections.find{ |s| s.Name == id }
909
+ end
910
+ unless section
911
+ @pedump.logger.fatal "[!] section #{id.inspect} not found"
912
+ exit(1)
913
+ end
914
+ IO::copy_stream @pedump.io, $stdout, section.SizeOfRawData, section.PointerToRawData
915
+ end
916
+
804
917
  end # class PEdump::CLI