pedump 0.1.1 → 0.2.0
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/VERSION +1 -1
- data/lib/pedump.rb +139 -6
- data/lib/pedump/cli.rb +83 -28
- data/pedump.gemspec +2 -2
- metadata +11 -11
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/pedump.rb
CHANGED
@@ -126,14 +126,18 @@ class PEdump
|
|
126
126
|
:lfanew
|
127
127
|
)
|
128
128
|
|
129
|
-
PE
|
129
|
+
class PE < Struct.new(
|
130
130
|
:signature, # "PE\x00\x00"
|
131
131
|
:image_file_header,
|
132
132
|
:image_optional_header,
|
133
133
|
:section_table
|
134
134
|
)
|
135
|
-
|
136
|
-
|
135
|
+
alias :ifh :image_file_header
|
136
|
+
alias :ioh :image_optional_header
|
137
|
+
def x64?
|
138
|
+
ifh && ifh.Machine == 0x8664
|
139
|
+
end
|
140
|
+
end
|
137
141
|
|
138
142
|
# http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
139
143
|
IMAGE_FILE_HEADER = create_struct( 'v2V3v2',
|
@@ -401,7 +405,7 @@ class PEdump
|
|
401
405
|
PE.new(pe_sig).tap do |pe|
|
402
406
|
pe.image_file_header = IMAGE_FILE_HEADER.read(f)
|
403
407
|
if pe.ifh.SizeOfOptionalHeader > 0
|
404
|
-
if pe.
|
408
|
+
if pe.x64?
|
405
409
|
pe.image_optional_header = IMAGE_OPTIONAL_HEADER64.read(f, pe.ifh.SizeOfOptionalHeader)
|
406
410
|
else
|
407
411
|
pe.image_optional_header = IMAGE_OPTIONAL_HEADER.read(f, pe.ifh.SizeOfOptionalHeader)
|
@@ -448,6 +452,126 @@ class PEdump
|
|
448
452
|
end
|
449
453
|
alias :section_table :sections
|
450
454
|
|
455
|
+
##############################################################################
|
456
|
+
# imports
|
457
|
+
##############################################################################
|
458
|
+
|
459
|
+
# http://sandsprite.com/CodeStuff/Understanding_imports.html
|
460
|
+
# http://stackoverflow.com/questions/5631317/import-table-it-vs-import-address-table-iat
|
461
|
+
IMAGE_IMPORT_DESCRIPTOR = create_struct 'V5',
|
462
|
+
:OriginalFirstThunk,
|
463
|
+
:TimeDateStamp,
|
464
|
+
:ForwarderChain,
|
465
|
+
:Name,
|
466
|
+
:FirstThunk,
|
467
|
+
# manual:
|
468
|
+
:module_name,
|
469
|
+
:original_first_thunk,
|
470
|
+
:first_thunk
|
471
|
+
|
472
|
+
ImportedFunction = Struct.new(:hint, :name, :ordinal)
|
473
|
+
|
474
|
+
def imports f=nil
|
475
|
+
return nil unless pe(f) && pe(f).ioh && f
|
476
|
+
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
|
477
|
+
return [] if !dir || (dir.va == 0 && dir.size == 0)
|
478
|
+
va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT].va
|
479
|
+
f.seek va2file(va)
|
480
|
+
r = []
|
481
|
+
until (t=IMAGE_IMPORT_DESCRIPTOR.read(f)).empty?
|
482
|
+
r << t
|
483
|
+
end
|
484
|
+
r.each do |x|
|
485
|
+
if x.Name.to_i != 0 && (va = va2file(x.Name))
|
486
|
+
f.seek va
|
487
|
+
x.module_name = f.gets("\x00").chop
|
488
|
+
end
|
489
|
+
[:original_first_thunk, :first_thunk].each do |tbl|
|
490
|
+
camel = tbl.capitalize.to_s.gsub(/_./){ |char| char[1..-1].upcase}
|
491
|
+
if x[camel].to_i != 0 && (va = va2file(x[camel]))
|
492
|
+
f.seek va
|
493
|
+
x[tbl] ||= []
|
494
|
+
if pe.x64?
|
495
|
+
x[tbl] << t while (t = f.read(8).unpack('Q').first) != 0
|
496
|
+
else
|
497
|
+
x[tbl] << t while (t = f.read(4).unpack('V').first) != 0
|
498
|
+
end
|
499
|
+
end
|
500
|
+
cache = {}
|
501
|
+
bits = pe.x64? ? 64 : 32
|
502
|
+
x[tbl] && x[tbl].map! do |t|
|
503
|
+
cache[t] ||=
|
504
|
+
if t & (2**(bits-1)) > 0 # 0x8000_0000(_0000_0000)
|
505
|
+
ImportedFunction.new(nil,nil,t & (2**(bits-1)-1)) # 0x7fff_ffff(_ffff_ffff)
|
506
|
+
elsif va=va2file(t)
|
507
|
+
f.seek va
|
508
|
+
ImportedFunction.new(f.read(2).unpack('v').first, f.gets("\x00").chop)
|
509
|
+
else
|
510
|
+
nil
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
if x.original_first_thunk != x.first_thunk
|
515
|
+
logger.warn "[?] import table: #{x.module_name}: original_first_thunk != first_thunk"
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
##############################################################################
|
521
|
+
# exports
|
522
|
+
##############################################################################
|
523
|
+
|
524
|
+
#http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
525
|
+
IMAGE_EXPORT_DIRECTORY = create_struct 'V2v2V7',
|
526
|
+
:Characteristics,
|
527
|
+
:TimeDateStamp,
|
528
|
+
:MajorVersion, # These fields appear to be unused and are set to 0.
|
529
|
+
:MinorVersion, # These fields appear to be unused and are set to 0.
|
530
|
+
:Name,
|
531
|
+
:Base, # The starting ordinal number for exported functions
|
532
|
+
:NumberOfFunctions,
|
533
|
+
:NumberOfNames,
|
534
|
+
:AddressOfFunctions,
|
535
|
+
:AddressOfNames,
|
536
|
+
:AddressOfNameOrdinals,
|
537
|
+
# manual:
|
538
|
+
:name, :entry_points, :names, :ordinals
|
539
|
+
|
540
|
+
def exports f=nil
|
541
|
+
return nil unless pe(f) && pe(f).ioh && f
|
542
|
+
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT]
|
543
|
+
return [] if !dir || (dir.va == 0 && dir.size == 0)
|
544
|
+
va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT].va
|
545
|
+
f.seek va2file(va)
|
546
|
+
IMAGE_EXPORT_DIRECTORY.read(f).tap do |x|
|
547
|
+
if x.Name.to_i != 0 && (va = va2file(x.Name))
|
548
|
+
f.seek va
|
549
|
+
x.name = f.gets("\x00").chop
|
550
|
+
end
|
551
|
+
if x.NumberOfFunctions.to_i != 0
|
552
|
+
if x.AddressOfFunctions.to_i !=0 && (va = va2file(x.AddressOfFunctions))
|
553
|
+
f.seek va
|
554
|
+
x.entry_points = f.read(x.NumberOfFunctions*4).unpack('V*')
|
555
|
+
end
|
556
|
+
if x.AddressOfNameOrdinals.to_i !=0 && (va = va2file(x.AddressOfNameOrdinals))
|
557
|
+
f.seek va
|
558
|
+
x.ordinals = f.read(x.NumberOfFunctions*2).unpack('v*').map{ |o| o+x.Base }
|
559
|
+
end
|
560
|
+
end
|
561
|
+
if x.NumberOfNames.to_i != 0 && x.AddressOfNames.to_i !=0 && (va = va2file(x.AddressOfNames))
|
562
|
+
f.seek va
|
563
|
+
x.names = f.read(x.NumberOfNames*4).unpack('V*').map do |va|
|
564
|
+
f.seek va2file(va)
|
565
|
+
f.gets("\x00").chop
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
##############################################################################
|
572
|
+
# resources
|
573
|
+
##############################################################################
|
574
|
+
|
451
575
|
IMAGE_RESOURCE_DIRECTORY = create_struct 'V2v4',
|
452
576
|
:Characteristics, :TimeDateStamp, # 2dw
|
453
577
|
:MajorVersion, :MinorVersion, :NumberOfNamedEntries, :NumberOfIdEntries, # 4w
|
@@ -581,7 +705,11 @@ class PEdump
|
|
581
705
|
def restore_bitmap src_fname
|
582
706
|
File.open(src_fname, "rb") do |f|
|
583
707
|
parse f
|
584
|
-
|
708
|
+
if data.first == "PNG"
|
709
|
+
"\x89PNG" +f.read(self.size-4)
|
710
|
+
else
|
711
|
+
bitmap_hdr + f.read(@palette_size + @imgdata_size)
|
712
|
+
end
|
585
713
|
end
|
586
714
|
end
|
587
715
|
|
@@ -620,7 +748,12 @@ class PEdump
|
|
620
748
|
case type
|
621
749
|
when 'BITMAP','ICON'
|
622
750
|
f.seek file_offset
|
623
|
-
|
751
|
+
if f.read(4) == "\x89PNG"
|
752
|
+
data << 'PNG'
|
753
|
+
else
|
754
|
+
f.seek file_offset
|
755
|
+
data << BITMAPINFOHEADER.read(f)
|
756
|
+
end
|
624
757
|
when 'CURSOR'
|
625
758
|
f.seek file_offset
|
626
759
|
data << CURSOR_HOTSPOT.read(f)
|
data/lib/pedump/cli.rb
CHANGED
@@ -3,8 +3,8 @@ require 'optparse'
|
|
3
3
|
|
4
4
|
unless Object.instance_methods.include?(:try)
|
5
5
|
class Object
|
6
|
-
def try(
|
7
|
-
send
|
6
|
+
def try(*x)
|
7
|
+
send(*x) if respond_to?(x.first)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -12,7 +12,11 @@ end
|
|
12
12
|
class PEdump::CLI
|
13
13
|
attr_accessor :data, :argv
|
14
14
|
|
15
|
-
KNOWN_ACTIONS =
|
15
|
+
KNOWN_ACTIONS = (
|
16
|
+
%w'mz dos_stub rich pe data_directory sections' +
|
17
|
+
%w'strings resources resource_directory imports exports'
|
18
|
+
).map(&:to_sym)
|
19
|
+
|
16
20
|
DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory'.map(&:to_sym)
|
17
21
|
|
18
22
|
def initialize argv = ARGV
|
@@ -108,6 +112,10 @@ class PEdump::CLI
|
|
108
112
|
return dump_resources(data)
|
109
113
|
when :strings
|
110
114
|
return dump_strings(data)
|
115
|
+
when :imports
|
116
|
+
return dump_imports(data)
|
117
|
+
when :exports
|
118
|
+
return dump_exports(data)
|
111
119
|
else
|
112
120
|
if data.is_a?(Struct) && data.respond_to?(:pack)
|
113
121
|
data = data.pack
|
@@ -158,35 +166,42 @@ class PEdump::CLI
|
|
158
166
|
:Subsystem => PEdump::IMAGE_SUBSYSTEMS
|
159
167
|
}
|
160
168
|
|
161
|
-
def
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
case
|
166
|
-
when
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
169
|
+
def dump_generic_table data
|
170
|
+
data.each_pair do |k,v|
|
171
|
+
case v
|
172
|
+
when Numeric
|
173
|
+
case k
|
174
|
+
when /\AMajor.*Version\Z/
|
175
|
+
printf "%30s: %24s\n", k.to_s.sub('Major',''), "#{v}.#{data[k.to_s.sub('Major','Minor')]}"
|
176
|
+
when /\AMinor.*Version\Z/
|
177
|
+
when /TimeDateStamp/
|
178
|
+
printf "%30s: %24s\n", k, Time.at(v).strftime('"%Y-%m-%d %H:%M:%S"')
|
179
|
+
else
|
180
|
+
if COMMENTS[k]
|
181
|
+
printf "%30s: %10d %12s %s\n", k, v, v<10 ? v : ("0x"+v.to_s(16)),
|
182
|
+
COMMENTS[k][v] || (COMMENTS[k].is_a?(Hash) ? COMMENTS[k]['default'] : '') || ''
|
171
183
|
else
|
172
|
-
|
173
|
-
printf "%30s: %10d %12s %s\n", k, v, v<10 ? v : ("0x"+v.to_s(16)),
|
174
|
-
COMMENTS[k][v] || (COMMENTS[k].is_a?(Hash) ? COMMENTS[k]['default'] : '') || ''
|
175
|
-
else
|
176
|
-
printf "%30s: %10d %12s\n", k, v, v<10 ? v : ("0x"+v.to_s(16))
|
177
|
-
end
|
184
|
+
printf "%30s: %10d %12s\n", k, v, v<10 ? v : ("0x"+v.to_s(16))
|
178
185
|
end
|
179
|
-
when Struct
|
180
|
-
printf "\n# %s:\n", v.class.to_s.split('::').last
|
181
|
-
dump_table v
|
182
|
-
when Time
|
183
|
-
printf "%30s: %24s\n", k, v.strftime('"%Y-%m-%d %H:%M:%S"')
|
184
|
-
when Array
|
185
|
-
next if %w'DataDirectory section_table'.include?(k)
|
186
|
-
else
|
187
|
-
printf "%30s: %24s\n", k, v.to_s.inspect
|
188
186
|
end
|
187
|
+
when Struct
|
188
|
+
printf "\n# %s:\n", v.class.to_s.split('::').last
|
189
|
+
dump_table v
|
190
|
+
when Time
|
191
|
+
printf "%30s: %24s\n", k, v.strftime('"%Y-%m-%d %H:%M:%S"')
|
192
|
+
when Array
|
193
|
+
next if %w'DataDirectory section_table'.include?(k)
|
194
|
+
else
|
195
|
+
printf "%30s: %24s\n", k, v.to_s.inspect
|
189
196
|
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def dump_table data
|
201
|
+
if data.is_a?(Struct)
|
202
|
+
return dump_res_dir(data) if data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
203
|
+
return dump_exports(data) if data.is_a?(PEdump::IMAGE_EXPORT_DIRECTORY)
|
204
|
+
dump_generic_table data
|
190
205
|
elsif data.is_a?(Enumerable) && data.map(&:class).uniq.size == 1
|
191
206
|
case data.first
|
192
207
|
when PEdump::IMAGE_DATA_DIRECTORY
|
@@ -197,6 +212,8 @@ class PEdump::CLI
|
|
197
212
|
dump_resources data
|
198
213
|
when PEdump::STRING
|
199
214
|
dump_strings data
|
215
|
+
when PEdump::IMAGE_IMPORT_DESCRIPTOR
|
216
|
+
dump_imports data
|
200
217
|
else
|
201
218
|
puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
|
202
219
|
end
|
@@ -209,6 +226,44 @@ class PEdump::CLI
|
|
209
226
|
end
|
210
227
|
end
|
211
228
|
|
229
|
+
def dump_exports data
|
230
|
+
printf "# module_name=%s flags=0x%x ts=%s version=%d.%d\n",
|
231
|
+
data.name.inspect,
|
232
|
+
data.Characteristics,
|
233
|
+
Time.at(data.TimeDateStamp.to_i).strftime('"%Y-%m-%d %H:%M:%S"'),
|
234
|
+
data.MajorVersion, data.MinorVersion
|
235
|
+
|
236
|
+
printf "# n_funcs=%d n_names=%d\n",
|
237
|
+
data.NumberOfFunctions,
|
238
|
+
data.NumberOfNames
|
239
|
+
|
240
|
+
puts
|
241
|
+
|
242
|
+
printf "%5s %8s %s\n", "ORD", "ENTRY_VA", "NAME"
|
243
|
+
data.NumberOfFunctions.times do |i|
|
244
|
+
printf "%5s %8x %s\n",
|
245
|
+
data.ordinals[i].try(:to_s,16),
|
246
|
+
data.entry_points[i],
|
247
|
+
data.names[i]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def dump_imports data
|
252
|
+
fmt = "%-15s %5s %5s %s\n"
|
253
|
+
printf fmt, "MODULE_NAME", "HINT", "ORD", "FUNCTION_NAME"
|
254
|
+
data.each do |iid|
|
255
|
+
# image import descriptor
|
256
|
+
(Array(iid.original_first_thunk) + Array(iid.first_thunk)).uniq.each do |f|
|
257
|
+
# imported function
|
258
|
+
printf fmt,
|
259
|
+
iid.module_name,
|
260
|
+
f.hint ? f.hint.to_s(16) : '',
|
261
|
+
f.ordinal ? f.ordinal.to_s(16) : '',
|
262
|
+
f.name
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
212
267
|
def dump_strings data
|
213
268
|
printf "%5s %5s %4s %s\n", "ID", "ID", "LANG", "STRING"
|
214
269
|
prev_lang = nil
|
data/pedump.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "pedump"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Andrey \"Zed\" Zaikin"]
|
12
|
-
s.date = "2011-12-
|
12
|
+
s.date = "2011-12-10"
|
13
13
|
s.description = "dump headers, sections, extract resources of win32 PE exe,dll,etc"
|
14
14
|
s.email = "zed.0xff@gmail.com"
|
15
15
|
s.executables = ["pedump"]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pedump
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-10 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70291670384640 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 2.3.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70291670384640
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bundler
|
27
|
-
requirement: &
|
27
|
+
requirement: &70291670381860 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.0.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70291670381860
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: jeweler
|
38
|
-
requirement: &
|
38
|
+
requirement: &70291670379240 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.6.4
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70291670379240
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rcov
|
49
|
-
requirement: &
|
49
|
+
requirement: &70291670377460 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70291670377460
|
58
58
|
description: dump headers, sections, extract resources of win32 PE exe,dll,etc
|
59
59
|
email: zed.0xff@gmail.com
|
60
60
|
executables:
|
@@ -93,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
93
|
version: '0'
|
94
94
|
segments:
|
95
95
|
- 0
|
96
|
-
hash:
|
96
|
+
hash: 3280002266277389467
|
97
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
98
|
none: false
|
99
99
|
requirements:
|