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 +14 -23
- data/VERSION +1 -1
- data/lib/pedump.rb +57 -21
- data/lib/pedump/cli.rb +81 -55
- data/lib/pedump/ne.rb +425 -0
- data/lib/pedump/ne/version_info.rb +167 -0
- data/lib/pedump/pe.rb +20 -2
- data/lib/pedump/resources.rb +9 -5
- data/lib/pedump/version.rb +1 -1
- data/misc/nedump.c +751 -0
- data/pedump.gemspec +6 -2
- data/spec/ne_spec.rb +125 -0
- data/spec/spec_helper.rb +5 -1
- metadata +25 -21
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
|
6
|
+
A pure ruby implementation of win32 PE binary files dumper.
|
7
7
|
|
8
|
-
|
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
|
-
|
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.
|
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)
|
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 ||=
|
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
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
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
|
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
|
-
|
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
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
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 |
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
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
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
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 %
|
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
|
-
|
704
|
-
|
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
|
-
|
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
|