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.
Files changed (5) hide show
  1. data/VERSION +1 -1
  2. data/lib/pedump.rb +139 -6
  3. data/lib/pedump/cli.rb +83 -28
  4. data/pedump.gemspec +2 -2
  5. metadata +11 -11
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
data/lib/pedump.rb CHANGED
@@ -126,14 +126,18 @@ class PEdump
126
126
  :lfanew
127
127
  )
128
128
 
129
- PE = Struct.new(
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
- class PE; alias :ifh :image_file_header; end
136
- class PE; alias :ioh :image_optional_header; end
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.ifh.Machine == 0x8664
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
- bitmap_hdr + f.read(@palette_size + @imgdata_size)
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
- data << BITMAPINFOHEADER.read(f)
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(method)
7
- send method if respond_to? method
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 = %w'mz dos_stub rich pe data_directory sections strings resources resource_directory'.map(&:to_sym)
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 dump_table data
162
- if data.is_a?(Struct)
163
- return dump_res_dir(data) if data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
164
- data.each_pair do |k,v|
165
- case v
166
- when Numeric
167
- case k
168
- when /\AMajor.*Version\Z/
169
- printf "%30s: %24s\n", k.to_s.sub('Major',''), "#{v}.#{data[k.to_s.sub('Major','Minor')]}"
170
- when /\AMinor.*Version\Z/
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
- if COMMENTS[k]
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.1.1"
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-09"
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.1.1
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-09 00:00:00.000000000Z
12
+ date: 2011-12-10 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70262192852620 !ruby/object:Gem::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: *70262192852620
24
+ version_requirements: *70291670384640
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: bundler
27
- requirement: &70262192851380 !ruby/object:Gem::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: *70262192851380
35
+ version_requirements: *70291670381860
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jeweler
38
- requirement: &70262192849440 !ruby/object:Gem::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: *70262192849440
46
+ version_requirements: *70291670379240
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rcov
49
- requirement: &70262192846320 !ruby/object:Gem::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: *70262192846320
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: -2508940562784118037
96
+ hash: 3280002266277389467
97
97
  required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  none: false
99
99
  requirements: