pedump 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: