pedump 0.4.8 → 0.4.9

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