pedump 0.4.8 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,12 +3,20 @@ pedump
3
3
 
4
4
  Description
5
5
  -----------
6
- A pure ruby implementation of win32 PE binary files dumper, including:
6
+ A pure ruby implementation of win32 PE binary files dumper.
7
7
 
8
- * MZ Header
8
+ Supported formats:
9
+
10
+ * DOS MZ EXE
11
+ * win16 NE
12
+ * win32 PE
13
+ * win64 PE
14
+
15
+ Can dump:
16
+
17
+ * MZ/NE/PE Header
9
18
  * DOS stub
10
19
  * ['Rich' Header](http://ntcore.com/files/richsign.htm)
11
- * PE Header
12
20
  * Data Directory
13
21
  * Sections
14
22
  * Resources
@@ -41,9 +49,11 @@ Usage
41
49
  --dos-stub
42
50
  --rich
43
51
  --pe
52
+ --ne
44
53
  --data-directory
45
54
  -S, --sections
46
55
  --tls
56
+ --security
47
57
  -s, --strings
48
58
  -R, --resources
49
59
  --resource-directory
@@ -289,26 +299,7 @@ Usage
289
299
  KERNEL32.dll 21f TlsAlloc
290
300
  KERNEL32.dll 220 TlsFree
291
301
  KERNEL32.dll 1fd SetLastError
292
- KERNEL32.dll 221 TlsGetValue
293
- KERNEL32.dll 62 ExitProcess
294
- KERNEL32.dll 1b8 ReadFile
295
- KERNEL32.dll 16 CloseHandle
296
- KERNEL32.dll 24f WriteFile
297
- KERNEL32.dll 83 FlushFileBuffers
298
- KERNEL32.dll e9 GetModuleFileNameA
299
- KERNEL32.dll 98 GetCPInfo
300
- KERNEL32.dll 92 GetACP
301
- KERNEL32.dll f6 GetOEMCP
302
- KERNEL32.dll 8b FreeEnvironmentStringsA
303
- KERNEL32.dll d0 GetEnvironmentStrings
304
- KERNEL32.dll 8c FreeEnvironmentStringsW
305
- KERNEL32.dll d2 GetEnvironmentStringsW
306
- KERNEL32.dll 242 WideCharToMultiByte
307
- KERNEL32.dll 2b CreateFileA
308
- KERNEL32.dll 1f8 SetFilePointer
309
- KERNEL32.dll 206 SetStdHandle
310
- KERNEL32.dll 178 LoadLibraryA
311
- KERNEL32.dll 1ef SetEndOfFile
302
+ ...
312
303
 
313
304
  ### Exports
314
305
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.8
1
+ 0.4.9
data/lib/pedump.rb CHANGED
@@ -7,6 +7,8 @@ require 'pedump/version_info'
7
7
  require 'pedump/tls'
8
8
  require 'pedump/security'
9
9
  require 'pedump/packer'
10
+ require 'pedump/ne'
11
+ require 'pedump/ne/version_info'
10
12
 
11
13
  # pedump.rb by zed_0xff
12
14
  #
@@ -363,23 +365,6 @@ class PEdump
363
365
  alias :rich_header :rich_hdr
364
366
  alias :rich :rich_hdr
365
367
 
366
- def pe f=@io
367
- @pe ||=
368
- begin
369
- pe_offset = mz(f) && mz(f).lfanew
370
- if pe_offset.nil?
371
- logger.fatal "[!] NULL PE offset (e_lfanew). cannot continue."
372
- nil
373
- elsif pe_offset > f.size
374
- logger.fatal "[!] PE offset beyond EOF. cannot continue."
375
- nil
376
- else
377
- f.seek pe_offset
378
- PE.read f, :force => @force
379
- end
380
- end
381
- end
382
-
383
368
  def va2file va, h={}
384
369
  return nil if va.nil?
385
370
 
@@ -439,10 +424,22 @@ class PEdump
439
424
  end
440
425
 
441
426
  def sections f=@io
442
- pe(f) && pe.section_table
427
+ if pe(f)
428
+ pe.section_table
429
+ elsif ne(f)
430
+ ne.segments
431
+ end
443
432
  end
444
433
  alias :section_table :sections
445
434
 
435
+ def ne?
436
+ @pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
437
+ end
438
+
439
+ def pe?
440
+ @pe ? true : (@ne ? false : (pe ? true : false ))
441
+ end
442
+
446
443
  ##############################################################################
447
444
  # imports
448
445
  ##############################################################################
@@ -460,7 +457,7 @@ class PEdump
460
457
  :original_first_thunk,
461
458
  :first_thunk
462
459
 
463
- class ImportedFunction < Struct.new(:hint, :name, :ordinal, :va)
460
+ class ImportedFunction < Struct.new(:hint, :name, :ordinal, :va, :module_name)
464
461
  # def == x
465
462
  # self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal
466
463
  # end
@@ -479,6 +476,14 @@ class PEdump
479
476
  end
480
477
 
481
478
  def imports f=@io
479
+ if pe(f)
480
+ pe_imports(f)
481
+ elsif ne(f)
482
+ ne(f).imports
483
+ end
484
+ end
485
+
486
+ def pe_imports f=@io
482
487
  return @imports if @imports
483
488
  return nil unless pe(f) && pe(f).ioh && f
484
489
  dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
@@ -594,9 +599,20 @@ class PEdump
594
599
  :AddressOfNames,
595
600
  :AddressOfNameOrdinals,
596
601
  # manual:
597
- :name, :entry_points, :names, :name_ordinals
602
+ :name, :entry_points, :names, :name_ordinals, :functions,
603
+ :description # NE only
604
+
605
+ ExportedFunction = Struct.new :name, :ord, :va, :file_offset
598
606
 
599
607
  def exports f=@io
608
+ if pe(f)
609
+ pe_exports(f)
610
+ elsif ne(f)
611
+ ne(f).exports
612
+ end
613
+ end
614
+
615
+ def pe_exports f=@io
600
616
  return @exports if @exports
601
617
  return nil unless pe(f) && pe(f).ioh && f
602
618
  dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT]
@@ -661,6 +677,21 @@ class PEdump
661
677
  f.gets("\x00").to_s.chomp("\x00")
662
678
  end
663
679
  end
680
+
681
+ ord2name = {}
682
+ if x.names && x.names.any?
683
+ x.NumberOfNames.times do |i|
684
+ ord2name[x.name_ordinals[i]] ||= []
685
+ ord2name[x.name_ordinals[i]] << x.names[i]
686
+ end
687
+ end
688
+
689
+ x.functions = []
690
+ x.entry_points.each_with_index do |ep,i|
691
+ names = ord2name[i+x.Base].try(:join,', ')
692
+ next if ep.to_i == 0 && names.nil?
693
+ x.functions << ExportedFunction.new(names, i+x.Base, ep)
694
+ end
664
695
  end
665
696
  end
666
697
 
@@ -696,7 +727,12 @@ class PEdump
696
727
  ##############################################################################
697
728
 
698
729
  def resources f=@io
699
- @resources ||= _scan_resources(f)
730
+ @resources ||=
731
+ if pe(f)
732
+ _scan_pe_resources(f)
733
+ elsif ne(f)
734
+ ne(f).resources(f)
735
+ end
700
736
  end
701
737
 
702
738
  def version_info f=@io
data/lib/pedump/cli.rb CHANGED
@@ -41,7 +41,7 @@ class PEdump::CLI
41
41
  attr_accessor :data, :argv
42
42
 
43
43
  KNOWN_ACTIONS = (
44
- %w'mz dos_stub rich pe data_directory sections tls security' +
44
+ %w'mz dos_stub rich pe ne data_directory sections tls security' +
45
45
  %w'strings resources resource_directory imports exports version_info packer web packer_only'
46
46
  ).map(&:to_sym)
47
47
 
@@ -431,16 +431,18 @@ class PEdump::CLI
431
431
  dump_resources data
432
432
  when PEdump::STRING
433
433
  dump_strings data
434
- when PEdump::IMAGE_IMPORT_DESCRIPTOR
434
+ when PEdump::IMAGE_IMPORT_DESCRIPTOR, PEdump::ImportedFunction
435
435
  dump_imports data
436
436
  when PEdump::Packer::Match
437
437
  dump_packers data
438
- when PEdump::VS_VERSIONINFO
438
+ when PEdump::VS_VERSIONINFO, PEdump::NE::VS_VERSIONINFO
439
439
  dump_version_info data
440
440
  when PEdump::IMAGE_TLS_DIRECTORY32, PEdump::IMAGE_TLS_DIRECTORY64
441
441
  dump_tls data
442
442
  when PEdump::WIN_CERTIFICATE
443
443
  dump_security data
444
+ when PEdump::NE::Segment
445
+ dump_ne_segments data
444
446
  else
445
447
  puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
446
448
  end
@@ -498,38 +500,38 @@ class PEdump::CLI
498
500
  puts "# VS_FIXEDFILEINFO:"
499
501
 
500
502
  if @options[:verbose] > 0 || vi.Value.dwSignature != 0xfeef04bd
501
- printf(fmt, "Signature", "0x#{vi.Value.dwSignature.to_s(16)}")
503
+ printf(fmt, "Signature", "0x#{vi.Value.dwSignature.to_i.to_s(16)}")
502
504
  end
503
505
 
504
506
  printf fmt, 'FileVersion', [
505
- vi.Value.dwFileVersionMS >> 16,
506
- vi.Value.dwFileVersionMS & 0xffff,
507
- vi.Value.dwFileVersionLS >> 16,
508
- vi.Value.dwFileVersionLS & 0xffff
507
+ vi.Value.dwFileVersionMS.to_i >> 16,
508
+ vi.Value.dwFileVersionMS.to_i & 0xffff,
509
+ vi.Value.dwFileVersionLS.to_i >> 16,
510
+ vi.Value.dwFileVersionLS.to_i & 0xffff
509
511
  ].join('.')
510
512
 
511
513
  printf fmt, 'ProductVersion', [
512
- vi.Value.dwProductVersionMS >> 16,
513
- vi.Value.dwProductVersionMS & 0xffff,
514
- vi.Value.dwProductVersionLS >> 16,
515
- vi.Value.dwProductVersionLS & 0xffff
514
+ vi.Value.dwProductVersionMS.to_i >> 16,
515
+ vi.Value.dwProductVersionMS.to_i & 0xffff,
516
+ vi.Value.dwProductVersionLS.to_i >> 16,
517
+ vi.Value.dwProductVersionLS.to_i & 0xffff
516
518
  ].join('.')
517
519
 
518
520
  vi.Value.each_pair do |k,v|
519
521
  next if k[/[ML]S$/] || k == :valid || k == :dwSignature
520
- printf fmt, k.to_s.sub(/^dw/,''), v > 9 ? "0x#{v.to_s(16)}" : v
522
+ printf fmt, k.to_s.sub(/^dw/,''), v.to_i > 9 ? "0x#{v.to_s(16)}" : v
521
523
  end
522
524
 
523
525
  vi.Children.each do |file_info|
524
526
  case file_info
525
- when PEdump::StringFileInfo
527
+ when PEdump::StringFileInfo, PEdump::NE::StringFileInfo
526
528
  file_info.Children.each do |string_table|
527
529
  puts "\n# StringTable #{string_table.szKey}:"
528
530
  string_table.Children.each do |string|
529
531
  printf fmt, string.szKey, string.Value.inspect
530
532
  end
531
533
  end
532
- when PEdump::VarFileInfo
534
+ when PEdump::VarFileInfo, PEdump::NE::VarFileInfo
533
535
  puts
534
536
  printf fmt, "VarFileInfo", '[ 0x' + file_info.Children.Value.map{|v| v.to_s(16)}.join(", 0x") + ' ]'
535
537
  else
@@ -551,58 +553,68 @@ class PEdump::CLI
551
553
  end
552
554
 
553
555
  def dump_exports data
554
- printf "# module %s\n# flags=0x%x ts=%s version=%d.%d ord_base=%d\n",
555
- data.name.inspect,
556
- data.Characteristics.to_i,
557
- Time.at(data.TimeDateStamp.to_i).utc.strftime('"%Y-%m-%d %H:%M:%S"'),
558
- data.MajorVersion.to_i, data.MinorVersion.to_i,
559
- data.Base.to_i
556
+ printf "# module %s\n", data.name.inspect
557
+ printf "# description %s\n", data.description.inspect if data.description
558
+
559
+ if data.Characteristics || data.TimeDateStamp || data.MajorVersion || data.MinorVersion || data.Base
560
+ printf "# flags=0x%x ts=%s version=%d.%d ord_base=%d\n",
561
+ data.Characteristics.to_i,
562
+ Time.at(data.TimeDateStamp.to_i).utc.strftime('"%Y-%m-%d %H:%M:%S"'),
563
+ data.MajorVersion.to_i, data.MinorVersion.to_i,
564
+ data.Base.to_i
565
+ end
560
566
 
561
567
  if @options[:verbose] > 0
562
568
  [%w'Names', %w'EntryPoints Functions', %w'Ordinals NameOrdinals'].each do |x|
563
569
  va = data["AddressOf"+x.last]
564
570
  ofs = @pedump.va2file(va) || '?'
565
- printf "# %-12s rva=0x%08x file_offset=%8s\n", x.first, va, ofs
571
+ printf("# %-12s rva=0x%08x file_offset=%8s\n", x.first, va, ofs) if va
566
572
  end
567
573
  end
568
574
 
569
- printf "# nFuncs=%d nNames=%d\n",
570
- data.NumberOfFunctions.to_i,
571
- data.NumberOfNames.to_i
572
-
573
- return unless data.name_ordinals.any? || data.entry_points.any? || data.names.any?
574
-
575
- puts
576
-
577
- ord2name = {}
578
- if data.names && data.names.any?
579
- data.NumberOfNames.times do |i|
580
- ord2name[data.name_ordinals[i]] ||= []
581
- ord2name[data.name_ordinals[i]] << data.names[i]
582
- end
575
+ if data.NumberOfFunctions || data.NumberOfNames
576
+ printf "# nFuncs=%d nNames=%d\n", data.NumberOfFunctions.to_i, data.NumberOfNames.to_i
583
577
  end
584
578
 
585
- printf "%5s %8s %s\n", "ORD", "ENTRY_VA", "NAME"
586
- data.entry_points.each_with_index do |ep,i|
587
- names = ord2name[i+data.Base].try(:join,', ')
588
- next if ep.to_i == 0 && names.nil?
589
- printf "%5x %8x %s\n", i + data.Base, ep, names
579
+ if data.functions && data.functions.any?
580
+ puts
581
+ if @pedump.ne?
582
+ printf "%5s %9s %s\n", "ORD", "SEG:OFFS", "NAME"
583
+ data.functions.each do |f|
584
+ printf "%5x %4x:%04x %s\n", f.ord, f.va>>16, f.va&0xffff, f.name
585
+ end
586
+ else
587
+ printf "%5s %8s %s\n", "ORD", "ENTRY_VA", "NAME"
588
+ data.functions.each do |f|
589
+ printf "%5x %8x %s\n", f.ord, f.va, f.name
590
+ end
591
+ end
590
592
  end
591
593
  end
592
594
 
593
595
  def dump_imports data
594
596
  fmt = "%-15s %5s %5s %s\n"
595
597
  printf fmt, "MODULE_NAME", "HINT", "ORD", "FUNCTION_NAME"
596
- data.each do |iid|
597
- # image import descriptor
598
- (Array(iid.original_first_thunk) + Array(iid.first_thunk)).uniq.each do |f|
599
- next unless f
600
- # imported function
598
+ data.each do |x|
599
+ case x
600
+ when PEdump::IMAGE_IMPORT_DESCRIPTOR
601
+ (Array(x.original_first_thunk) + Array(x.first_thunk)).uniq.each do |f|
602
+ next unless f
603
+ # imported function
604
+ printf fmt,
605
+ x.module_name,
606
+ f.hint ? f.hint.to_s(16) : '',
607
+ f.ordinal ? f.ordinal.to_s(16) : '',
608
+ f.name
609
+ end
610
+ when PEdump::ImportedFunction
601
611
  printf fmt,
602
- iid.module_name,
603
- f.hint ? f.hint.to_s(16) : '',
604
- f.ordinal ? f.ordinal.to_s(16) : '',
605
- f.name
612
+ x.module_name,
613
+ x.hint ? x.hint.to_s(16) : '',
614
+ x.ordinal ? x.ordinal.to_s(16) : '',
615
+ x.name
616
+ else
617
+ raise "invalid #{x.inspect}"
606
618
  end
607
619
  end
608
620
  end
@@ -612,7 +624,7 @@ class PEdump::CLI
612
624
  prev_lang = nil
613
625
  data.sort_by{|s| [s.lang, s.id] }.each do |s|
614
626
  #puts if prev_lang && prev_lang != s.lang
615
- printf "%5d %5x %4x %s\n", s.id, s.id, s.lang, s.value.inspect
627
+ printf "%5d %5x %4s %s\n", s.id, s.id, s.lang && s.lang.to_s(16), s.value.inspect
616
628
  prev_lang = s.lang
617
629
  end
618
630
  end
@@ -699,11 +711,15 @@ class PEdump::CLI
699
711
  printf fmt.join.tr('dx','s'), *keys.map(&:to_s).map(&:upcase)
700
712
  data.each do |res|
701
713
  fmt.each_with_index do |f,i|
702
- v = res.send(keys[i])
703
- if f['x']
704
- printf f.tr('x','s'), v.to_i < 10 ? v.to_s : "0x#{v.to_s(16)}"
714
+ if v = res.send(keys[i])
715
+ if f['x']
716
+ printf f.tr('x','s'), v.to_i < 10 ? v.to_s : "0x#{v.to_s(16)}"
717
+ else
718
+ printf f, v
719
+ end
705
720
  else
706
- printf f, v
721
+ # NULL value
722
+ printf f.tr('xd','s'), ''
707
723
  end
708
724
  end
709
725
  end
@@ -724,6 +740,16 @@ class PEdump::CLI
724
740
  end
725
741
  end
726
742
 
743
+ def dump_ne_segments data
744
+ fmt = "%2x %6x %6x %9x %9x %6x %s\n"
745
+ printf fmt.tr('x','s'), *%w'# OFFSET SIZE MIN_ALLOC FILE_OFFS FLAGS', ''
746
+ data.each_with_index do |seg,idx|
747
+ printf fmt, idx+1, seg.offset, seg.size, seg.min_alloc_size, seg.file_offset, seg.flags,
748
+ seg.flags_desc
749
+ end
750
+ end
751
+
752
+
727
753
  def dump_data_dir data
728
754
  data.each do |row|
729
755
  printf " %-12s rva:0x%8x size:0x %8x\n", row.type, row.va.to_i, row.size.to_i
data/lib/pedump/ne.rb ADDED
@@ -0,0 +1,425 @@
1
+ class PEdump
2
+ # from wine's winnt.h
3
+ class NE < PEdump.create_struct 'a2CCvvVv4VVv8Vv3CCv4',
4
+ :ne_magic, # 00 NE signature 'NE'
5
+ :ne_ver, # 02 Linker version number
6
+ :ne_rev, # 03 Linker revision number
7
+ :ne_enttab, # 04 Offset to entry table relative to NE
8
+ :ne_cbenttab, # 06 Length of entry table in bytes
9
+ :ne_crc, # 08 Checksum
10
+ :ne_flags, # 0c Flags about segments in this file
11
+ :ne_autodata, # 0e Automatic data segment number
12
+ :ne_heap, # 10 Initial size of local heap
13
+ :ne_stack, # 12 Initial size of stack
14
+ :ne_csip, # 14 Initial CS:IP
15
+ :ne_sssp, # 18 Initial SS:SP
16
+ :ne_cseg, # 1c # of entries in segment table
17
+ :ne_cmod, # 1e # of entries in module reference tab.
18
+ :ne_cbnrestab, # 20 Length of nonresident-name table
19
+ :ne_segtab, # 22 Offset to segment table
20
+ :ne_rsrctab, # 24 Offset to resource table
21
+ :ne_restab, # 26 Offset to resident-name table
22
+ :ne_modtab, # 28 Offset to module reference table
23
+ :ne_imptab, # 2a Offset to imported name table
24
+ :ne_nrestab, # 2c Offset to nonresident-name table
25
+ :ne_cmovent, # 30 # of movable entry points
26
+ :ne_align, # 32 Logical sector alignment shift count
27
+ :ne_cres, # 34 # of resource segments
28
+ :ne_exetyp, # 36 Flags indicating target OS
29
+ :ne_flagsothers, # 37 Additional information flags
30
+ :ne_pretthunks, # 38 Offset to return thunks
31
+ :ne_psegrefbytes, # 3a Offset to segment ref. bytes
32
+ :ne_swaparea, # 3c Reserved by Microsoft
33
+ :ne_expver # 3e Expected Windows version number
34
+
35
+ attr_accessor :io, :offset
36
+
37
+ DEFAULT_CP = 1252
38
+
39
+ def self.cp
40
+ @@cp || DEFAULT_CP
41
+ end
42
+
43
+ def self.cp= cp
44
+ @@cp = cp
45
+ end
46
+
47
+ def self.read io, *args
48
+ self.cp = DEFAULT_CP
49
+ offset = io.tell
50
+ super.tap do |x|
51
+ x.io, x.offset = io, offset
52
+ end
53
+ end
54
+
55
+ class Segment < PEdump.create_struct 'v4',
56
+ :offset, :size, :flags, :min_alloc_size,
57
+ # manual:
58
+ :file_offset, :relocs
59
+
60
+ FLAG_RELOCINFO = 0x100
61
+
62
+ def data?
63
+ flags & 1 == 1
64
+ end
65
+
66
+ def code?
67
+ !data?
68
+ end
69
+
70
+ def flags_desc
71
+ r = code? ? 'CODE' : 'DATA'
72
+ r << ' ALLOC' if flags & 2 != 0
73
+ r << ' LOADED' if flags & 4 != 0
74
+ r << ((flags & 0x10 != 0) ? ' MOVABLE' : ' FIXED')
75
+ r << ((flags & 0x20 != 0) ? ' PURE' : '')
76
+ r << ((flags & 0x40 != 0) ? ' PRELOAD' : '')
77
+ if code?
78
+ r << ((flags & 0x80 != 0) ? ' EXECUTEONLY' : '')
79
+ else
80
+ r << ((flags & 0x80 != 0) ? ' READONLY' : '')
81
+ end
82
+ r << ((flags & FLAG_RELOCINFO != 0) ? ' RELOCINFO' : '')
83
+ r << ((flags & 0x200 != 0) ? ' DBGINFO' : '')
84
+ r << ((flags & 0x1000 != 0) ? ' DISCARD' : '')
85
+ r
86
+ end
87
+ end
88
+
89
+ class Reloc < PEdump.create_struct 'CCvvv',
90
+ :source, :type,
91
+ :offset, # offset of the relocation item within the segment
92
+
93
+ # If the relocation type is imported ordinal,
94
+ # the fifth and sixth bytes specify an index to a module's reference table and
95
+ # the seventh and eighth bytes specify a function ordinal value.
96
+
97
+ # If the relocation type is imported name,
98
+ # the fifth and sixth bytes specify an index to a module's reference table and
99
+ # the seventh and eighth bytes specify an offset to an imported-name table.
100
+
101
+ :module_idx,
102
+ :func_idx
103
+
104
+ TYPE_IMPORTORDINAL = 1
105
+ TYPE_IMPORTNAME = 2
106
+ end
107
+
108
+ def segments io=@io
109
+ @segments ||= io &&
110
+ begin
111
+ io.seek ne_segtab+@offset
112
+ ne_cseg.times.map{ Segment.read(io) }.each do |seg|
113
+ seg.file_offset = seg.offset << ne_align
114
+ seg.relocs = []
115
+ if (seg.flags & Segment::FLAG_RELOCINFO) != 0
116
+ io.seek seg.file_offset + seg.size
117
+ nRelocs = io.read(2).unpack('v').first
118
+ seg.relocs = nRelocs.times.map{ Reloc.read(io) }
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ class ResourceGroup < PEdump.create_struct 'vvV',
125
+ :type_id, :count, :reserved,
126
+ # manual:
127
+ :type, :children
128
+
129
+ def self.read io
130
+ super.tap do |g|
131
+ if g.type_id.to_i == 0
132
+ # type_id = 0 means end of resource groups
133
+ return nil
134
+ else
135
+ # read only if type_id is non-zero,
136
+ g.children = []
137
+ g.count.times do
138
+ break if io.eof?
139
+ g.children << ResourceInfo.read(io)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ class ResourceInfo < PEdump.create_struct 'v4V',
147
+ :offset, :size, :flags, :name_offset, :reserved,
148
+ # manual:
149
+ :name
150
+ end
151
+
152
+ class Resource < PEdump::Resource
153
+ # NE strings use 8-bit characters
154
+ def parse f, h={}
155
+ self.data = []
156
+ case type
157
+ when 'STRING'
158
+ f.seek file_offset
159
+ 16.times do
160
+ break if f.tell >= file_offset+self.size
161
+ nChars = f.getc.ord
162
+ t =
163
+ if nChars + 1 > self.size
164
+ # TODO: if it's not 1st string in table then truncated size must be less
165
+ PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
166
+ f.read(self.size-1)
167
+ else
168
+ f.read(nChars)
169
+ end
170
+ data <<
171
+ begin
172
+ t.force_encoding("CP#{h[:cp]}").encode!('UTF-8')
173
+ rescue
174
+ t.force_encoding('ASCII')
175
+ end
176
+ end
177
+ when 'VERSION'
178
+ f.seek file_offset
179
+ data << PEdump::NE::VS_VERSIONINFO.read(f)
180
+ else
181
+ super(f)
182
+ end
183
+ end
184
+ end
185
+
186
+ def _id2string id, io, res_base
187
+ if id & 0x8000 == 0
188
+ # offset to name
189
+ io.seek id + res_base
190
+ namesize = (io.getc || 0.chr).ord
191
+ io.read(namesize)
192
+ else
193
+ # numerical id
194
+ "##{id & 0x7fff}"
195
+ end
196
+ end
197
+
198
+ def resource_directory io=@io
199
+ @resource_directory ||=
200
+ begin
201
+ res_base = ne_rsrctab+@offset
202
+ io.seek res_base
203
+ res_shift = io.read(2).unpack('v').first
204
+ unless (0..16).include?(res_shift)
205
+ PEdump.logger.error "[!] invalid res_shift = %d" % res_shift
206
+ return []
207
+ end
208
+ PEdump.logger.info "[.] res_shift = %d" % res_shift
209
+ r = []
210
+ while !io.eof? && (g = ResourceGroup.read(io))
211
+ r << g
212
+ end
213
+ r.each do |g|
214
+ g.type = (g.type_id & 0x8000 != 0) && PEdump::ROOT_RES_NAMES[g.type_id & 0x7fff]
215
+ g.type ||= _id2string( g.type_id, io, res_base)
216
+ g.children.each do |res|
217
+ res.name = _id2string(res.name_offset, io, res_base)
218
+ res.offset ||= 0
219
+ res.offset <<= res_shift
220
+ res.size ||= 0
221
+ res.size <<= res_shift
222
+ end
223
+ end
224
+ r
225
+ end
226
+ end
227
+
228
+ def _detect_codepage a, io=@io
229
+ a.find_all{ |res| res.type == 'VERSION' }.each do |res|
230
+ res.parse(io)
231
+ res.data.each do |vi|
232
+ if vi.respond_to?(:Children) && vi.Children.respond_to?(:each)
233
+ # vi is PEdump::NE::VS_VERSIONINFO
234
+ vi.Children.each do |vfi|
235
+ if vfi.is_a?(PEdump::NE::VarFileInfo) && vfi.Children.is_a?(PEdump::NE::Var)
236
+ var = vfi.Children
237
+ # var is PEdump::NE::Var
238
+ if var.respond_to?(:Value) && var.Value.is_a?(Array) && var.Value.size == 2
239
+ return var.Value.last
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ nil
247
+ end
248
+
249
+ def resources io=@io
250
+ a = []
251
+ resource_directory(io).each do |grp|
252
+ grp.children.each do |res|
253
+ a << (r = Resource.new)
254
+ r.id = (res.name_offset & 0x7fff) if (res.name_offset & 0x8000) != 0
255
+ r.type = grp.type
256
+ r.size = res.size
257
+ r.name = res.name
258
+ r.file_offset = res.offset
259
+ r.reserved = res.reserved
260
+ end
261
+ end
262
+
263
+ # try to detect codepage
264
+ cp = _detect_codepage(a, io)
265
+ if cp
266
+ PEdump::NE.cp = cp # XXX HACK
267
+ PEdump.logger.info "[.] detect_codepage: #{cp.inspect}"
268
+ else
269
+ cp = DEFAULT_CP
270
+ PEdump.logger.info "[.] detect_codepage failed, using default #{cp}"
271
+ end
272
+
273
+ a.each{ |r| r.parse(io, :cp => cp) }
274
+ a
275
+ end
276
+
277
+ def imports io=@io
278
+ @imports ||=
279
+ begin
280
+ io.seek @offset+ne_modtab
281
+ modules = io.read(2*ne_cmod).unpack('v*')
282
+ modules.map! do |ofs|
283
+ io.seek @offset+ne_imptab+ofs
284
+ namelen = io.getc.ord
285
+ io.read(namelen)
286
+ end
287
+
288
+ r = []
289
+ segments(io).each do |seg|
290
+ seg.relocs.each do |rel|
291
+ if rel.type == Reloc::TYPE_IMPORTORDINAL
292
+ r << (f = PEdump::ImportedFunction.new)
293
+ f.module_name = modules[rel.module_idx-1]
294
+ f.ordinal = rel.func_idx
295
+ elsif rel.type == Reloc::TYPE_IMPORTNAME
296
+ r << (f = PEdump::ImportedFunction.new)
297
+ f.module_name = modules[rel.module_idx-1]
298
+ io.seek @offset+ne_imptab+rel.func_idx
299
+ namelen = io.getc.ord
300
+ f.name = io.read(namelen)
301
+ end
302
+ end
303
+ end
304
+ r
305
+ end
306
+ end
307
+
308
+ # first string with ordinal 0 is a module name
309
+ def exports io=@io
310
+ exp_dir = IMAGE_EXPORT_DIRECTORY.new
311
+ exp_dir.functions = []
312
+
313
+ io.seek @offset+ne_restab
314
+ while !io.eof && (namelen = io.getc.ord) > 0
315
+ exp_dir.functions << ExportedFunction.new( io.read(namelen), io.read(2).unpack('v').first, 0 )
316
+ end
317
+ exp_dir.name = exp_dir.functions.shift.name if exp_dir.functions.any?
318
+
319
+ a = []
320
+ io.seek ne_nrestab
321
+ while !io.eof && (namelen = io.getc.ord) > 0
322
+ a << ExportedFunction.new( io.read(namelen), io.read(2).unpack('v').first, 0 )
323
+ end
324
+ exp_dir.description = a.shift.name if a.any?
325
+ exp_dir.functions += a
326
+
327
+ exp_dir.functions.each do |f|
328
+ f.va = entrypoints[f.ord]
329
+ end
330
+
331
+ exp_dir
332
+ end
333
+
334
+ # The entry-table data is organized by bundle, each of which begins with a 2-byte header.
335
+ # The first byte of the header specifies the number of entries in the bundle ( 0 = end of the table).
336
+ # The second byte specifies whether the corresponding segment is movable or fixed.
337
+ # 0xFF = the segment is movable.
338
+ # 0xFE = the entry does not refer to a segment but refers to a constant defined within the module.
339
+ # else it is a segment index.
340
+
341
+ class Bundle < PEdump.create_struct 'CC', :num_entries, :seg_idx,
342
+ :entries # manual
343
+
344
+ FixedEntry = PEdump.create_struct 'Cv', :flag, :offset
345
+ MovableEntry = PEdump.create_struct 'CvCv', :flag, :int3F, :seg_idx, :offset
346
+
347
+ def movable?
348
+ seg_idx == 0xff
349
+ end
350
+
351
+ def self.read io
352
+ super.tap do |bundle|
353
+ return nil if bundle.num_entries == 0
354
+ if bundle.num_entries == 0
355
+ @@eob ||= 0
356
+ @@eob += 1
357
+ return nil if @@eob == 2
358
+ end
359
+ bundle.entries = bundle.seg_idx == 0 ? [] :
360
+ if bundle.movable?
361
+ bundle.num_entries.times.map{ MovableEntry.read(io) }
362
+ else
363
+ bundle.num_entries.times.map{ FixedEntry.read(io) }
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ def bundles io=@io
370
+ io.seek @offset+ne_enttab
371
+ bundles = []
372
+ while bundle = Bundle.read(io)
373
+ bundles << bundle
374
+ end
375
+ bundles
376
+ end
377
+
378
+ def entrypoints io=@io
379
+ @entrypoints ||=
380
+ begin
381
+ r = [0] # entrypoint indexes are 1-based
382
+ bundles(io).each do |b|
383
+ if b.entries.empty?
384
+ b.num_entries.times{ r<<0 }
385
+ else
386
+ b.entries.each do |e|
387
+ if e.is_a?(Bundle::MovableEntry)
388
+ r << (e.seg_idx<<16) + e.offset
389
+ elsif e.is_a?(Bundle::FixedEntry)
390
+ r << (b.seg_idx<<16) + e.offset
391
+ else
392
+ raise "invalid ep #{e.inspect}"
393
+ end
394
+ end
395
+ end
396
+ end
397
+ r
398
+ end
399
+ end
400
+ end
401
+
402
+ def ne f=@io
403
+ return @ne if defined?(@ne)
404
+ @ne ||=
405
+ begin
406
+ ne_offset = mz(f) && mz(f).lfanew
407
+ if ne_offset.nil?
408
+ logger.fatal "[!] NULL NE offset (e_lfanew)."
409
+ nil
410
+ elsif ne_offset > f.size
411
+ logger.fatal "[!] NE offset beyond EOF."
412
+ nil
413
+ else
414
+ f.seek ne_offset
415
+ if f.read(2) == 'NE'
416
+ f.seek ne_offset
417
+ NE.read f
418
+ else
419
+ nil
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ end