pedump 0.4.5 → 0.4.6

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.
@@ -3,6 +3,32 @@ require 'pedump/packer'
3
3
  require 'pedump/version_info'
4
4
  require 'optparse'
5
5
 
6
+ begin
7
+ require 'shellwords' # from ruby 1.9.3
8
+ rescue LoadError
9
+ unless ''.respond_to?(:shellescape)
10
+ class String
11
+ # File shellwords.rb, line 72
12
+ def shellescape
13
+ # An empty argument will be skipped, so return empty quotes.
14
+ return "''" if self.empty?
15
+
16
+ str = self.dup
17
+
18
+ # Process as a single byte sequence because not all shell
19
+ # implementations are multibyte aware.
20
+ str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")
21
+
22
+ # A LF cannot be escaped with a backslash because a backslash + LF
23
+ # combo is regarded as line continuation and simply ignored.
24
+ str.gsub!(/\n/, "'\n'")
25
+
26
+ str
27
+ end
28
+ end
29
+ end
30
+ end
31
+
6
32
  unless Object.instance_methods.include?(:try)
7
33
  class Object
8
34
  def try(*x)
@@ -71,6 +97,10 @@ class PEdump::CLI
71
97
  @actions << :packer_only
72
98
  end
73
99
 
100
+ opts.on '-r', "--recursive", "recurse dirs in packer detect" do
101
+ @options[:recursive] = true
102
+ end
103
+
74
104
  opts.on "--all", "Dump all but resource-directory (default)" do
75
105
  @actions = DEFAULT_ALL_ACTIONS
76
106
  end
@@ -146,12 +176,20 @@ class PEdump::CLI
146
176
  def dump_packer_only fnames
147
177
  max_fname_len = fnames.map(&:size).max
148
178
  fnames.each do |fname|
149
- File.open(fname,'rb') do |f|
150
- @pedump = create_pedump fname
151
- packers = @pedump.packers(f)
152
- pname = Array(packers).first.try(:packer).try(:name)
153
- pname ||= "unknown" if @options[:verbose] > 0
154
- printf("%-*s %s\n", max_fname_len+1, "#{fname}:", pname) if pname
179
+ if File.directory?(fname)
180
+ if @options[:recursive]
181
+ dump_packer_only(Dir[File.join(fname.shellescape,"*")])
182
+ else
183
+ STDERR.puts "[?] #{fname} is a directory, and recursive flag is not set"
184
+ end
185
+ else
186
+ File.open(fname,'rb') do |f|
187
+ @pedump = create_pedump fname
188
+ packers = @pedump.packers(f)
189
+ pname = Array(packers).first.try(:packer).try(:name)
190
+ pname ||= "unknown" if @options[:verbose] > 0
191
+ printf("%-*s %s\n", max_fname_len+1, "#{fname}:", pname) if pname
192
+ end
155
193
  end
156
194
  end
157
195
  end
@@ -267,9 +305,7 @@ class PEdump::CLI
267
305
  dump_opts = {:name => action}
268
306
  case action
269
307
  when :pe
270
- @pedump.pe.ifh.TimeDateStamp = @pedump.pe.ifh.TimeDateStamp.to_i
271
- data = @pedump.pe.signature + (@pedump.pe.ifh.try(:pack)||'') + (@pedump.pe.ioh.try(:pack)||'')
272
- @pedump.pe.ifh.TimeDateStamp = Time.at(@pedump.pe.ifh.TimeDateStamp).utc
308
+ data = @pedump.pe.pack
273
309
  when :resources
274
310
  return dump_resources(data)
275
311
  when :strings
@@ -420,12 +456,12 @@ class PEdump::CLI
420
456
  printf fmt.tr('x','s'), *%w'RAW_START RAW_END INDEX CALLBKS ZEROFILL FLAGS'
421
457
  data.each do |tls|
422
458
  printf fmt,
423
- tls.StartAddressOfRawData,
424
- tls.EndAddressOfRawData,
425
- tls.AddressOfIndex,
426
- tls.AddressOfCallBacks,
427
- tls.SizeOfZeroFill,
428
- tls.Characteristics
459
+ tls.StartAddressOfRawData.to_i,
460
+ tls.EndAddressOfRawData.to_i,
461
+ tls.AddressOfIndex.to_i,
462
+ tls.AddressOfCallBacks.to_i,
463
+ tls.SizeOfZeroFill.to_i,
464
+ tls.Characteristics.to_i
429
465
  end
430
466
  end
431
467
 
@@ -694,7 +730,11 @@ class PEdump::CLI
694
730
  end
695
731
  end
696
732
 
697
- def hexdump data, h = {}
733
+ def hexdump *args
734
+ self.class.hexdump(*args)
735
+ end
736
+
737
+ def self.hexdump data, h = {}
698
738
  offset = h[:offset] || 0
699
739
  add = h[:add] || 0
700
740
  size = h[:size] || (data.size-offset)
@@ -0,0 +1,147 @@
1
+ require 'pedump'
2
+ require 'pedump/loader'
3
+
4
+ ########################################################################
5
+ # comparing 2 binaries
6
+ ########################################################################
7
+
8
+ class PEdump::Comparer
9
+ attr_accessor :verbose
10
+ attr_accessor :ignored_data_dirs, :ignored_sections
11
+
12
+ METHODS = [:sections, :data_dirs, :imports, :resources, :pe_hdr]
13
+
14
+ def initialize ldr1, ldr2
15
+ @ldr1,@ldr2 = ldr1,ldr2
16
+ @ignored_data_dirs = []
17
+ @ignored_sections = []
18
+ end
19
+
20
+ def equal?
21
+ METHODS.map{ |m| send("cmp_#{m}") }.uniq == [true]
22
+ end
23
+
24
+ def diff
25
+ METHODS.map{ |m| send("cmp_#{m}") ? nil : m }.compact
26
+ end
27
+
28
+ def cmp_pe_hdr
29
+ @ldr1.pe.ioh.AddressOfEntryPoint == @ldr2.pe.ioh.AddressOfEntryPoint &&
30
+ @ldr1.pe.ioh.ImageBase == @ldr2.pe.ioh.ImageBase
31
+ end
32
+
33
+ def cmp_resources
34
+ PEdump.quiet do
35
+ #@ldr1.pedump.resources == @ldr2.pedump.resources
36
+ @ldr1.pedump.resources.each_with_index do |r1,idx|
37
+ r2 = @ldr2.pedump.resources[idx]
38
+ if (r1.to_a - [r1.file_offset]) != (r2.to_a - [r2.file_offset])
39
+ p r1
40
+ p r2
41
+ return false
42
+ end
43
+ end
44
+ end
45
+ true
46
+ end
47
+
48
+ def cmp_sections
49
+ r = true
50
+ @ldr1.sections.each_with_index do |s1,idx|
51
+ next if @ignored_sections.include?(s1.name)
52
+ s2 = @ldr2.sections[idx]
53
+
54
+ if !s2
55
+ r = false
56
+ printf "[!] extra section %-12s in %s\n".red, s1.name.inspect, f1
57
+ elsif s1.data == s2.data
58
+ printf "[.] section: %s == %s\n".green, s1.name, s2.name if @verbose
59
+ else
60
+ r = false
61
+ printf "[!] section: %s != %s\n".red, s1.name, s2.name
62
+ self.class.cmp_ios *[s1,s2].map{ |section| StringIO.new(section.data) }
63
+ end
64
+ end
65
+ r
66
+ end
67
+
68
+ def cmp_data_dirs
69
+ r = true
70
+ @ldr1.pe.ioh.DataDirectory.each_with_index do |d1,idx|
71
+ break if idx == 15
72
+ d2 = @ldr2.pe.ioh.DataDirectory[idx]
73
+
74
+ case idx
75
+ when PEdump::IMAGE_DATA_DIRECTORY::BASERELOC
76
+ # total 8-byte size relocs == no relocs at all
77
+ next if [d1.va, d2.va].min == 0 && [d1.size, d2.size].max == 8
78
+ end
79
+
80
+ next if @ignored_data_dirs.include?(idx)
81
+
82
+ if d1.va != d2.va && d1.size != d2.size
83
+ r = false
84
+ printf "[!] data_dir: %-12s: SIZE & VA: %6x %6x | %6x %6x\n".red, d1.type,
85
+ d1.va, d1.size, d2.va, d2.size
86
+ elsif d1.va != d2.va
87
+ r = false
88
+ printf "[!] data_dir: %-12s: VA : %x != %x\n".red, d1.type, d1.va, d2.va
89
+ elsif d1.size != d2.size
90
+ r = false
91
+ printf "[!] data_dir: %-12s: SIZE : %x != %x\n".red, d1.type, d1.size, d2.size
92
+ end
93
+ end
94
+ r
95
+ end
96
+
97
+ def cmp_imports
98
+ @ldr1.pedump.imports.each_with_index do |iid1,idx|
99
+ iid2 = @ldr2.pedump.imports[idx]
100
+ if iid1 != iid2
101
+ puts "[!] diff imports".red
102
+ return false
103
+ end
104
+ end
105
+ true
106
+ end
107
+
108
+ class << self
109
+ # arguments can be:
110
+ # a) filenames
111
+ # b) IO instances
112
+ # c) PEdump::Loader instances
113
+ def cmp *args
114
+ handles = []
115
+ if args.all?{|x| x.is_a?(String)}
116
+ handles = args.map{|x| File.open(x,"rb")}
117
+ _cmp(*handles.map{|h| PEdump::Loader.new(h)})
118
+ else
119
+ _cmp(*args)
120
+ end
121
+ ensure
122
+ handles.each(&:close)
123
+ end
124
+
125
+ # each arg is a PEdump::Loader
126
+ def _cmp ldr1, ldr2
127
+ new(ldr1, ldr2).equal?
128
+ end
129
+
130
+ def cmp_ios *ios
131
+ ndiff = 0
132
+ while !ios.any?(&:eof)
133
+ bytes = ios.map(&:readbyte)
134
+ if bytes.uniq.size > 1
135
+ ndiff += 1
136
+ printf ("\t%08x:"+" %02x"*ios.size).yellow+"\n", ios[0].pos-1, *bytes
137
+ if ndiff >= 5
138
+ puts "\t...".yellow
139
+ break
140
+ end
141
+ end
142
+ end
143
+ puts if ndiff > 0
144
+ end
145
+
146
+ end
147
+ end
@@ -1,5 +1,6 @@
1
1
  require 'logger'
2
2
  require 'pedump/version'
3
+ require 'pedump/logger'
3
4
 
4
5
  class String
5
6
  def xor x
@@ -30,26 +31,19 @@ class File
30
31
  end
31
32
 
32
33
  class PEdump
33
- class Logger < ::Logger
34
- def initialize *args
35
- super
36
- @formatter = proc do |severity,_,_,msg|
37
- # quick and dirty way to remove duplicate messages
38
- if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
39
- ''
40
- else
41
- @prevmsg = msg
42
- "#{msg}\n"
43
- end
44
- end
45
- @level = Logger::WARN
46
- end
47
- end
48
34
 
49
35
  module Readable
50
- def read file, size = nil
36
+ # src can be IO or String, or anything that responds to :read or :unpack
37
+ def read src, size = nil
51
38
  size ||= const_get 'SIZE'
52
- data = file.read(size).to_s
39
+ data =
40
+ if src.respond_to?(:read)
41
+ src.read(size).to_s
42
+ elsif src.respond_to?(:unpack)
43
+ src
44
+ else
45
+ raise "[?] don't know how to read from #{src.inspect}"
46
+ end
53
47
  if data.size < size && PEdump.logger
54
48
  PEdump.logger.error "[!] #{self.to_s} want #{size} bytes, got #{data.size}"
55
49
  end
@@ -67,7 +61,7 @@ class PEdump
67
61
  case f
68
62
  when /[aAC]/ then 1
69
63
  when 'v' then 2
70
- when 'V' then 4
64
+ when 'V','l' then 4
71
65
  when 'Q' then 8
72
66
  else raise "unknown fmt #{f.inspect}"
73
67
  end
@@ -0,0 +1,131 @@
1
+ require 'pedump'
2
+ require 'stringio'
3
+ require 'pedump/loader/section'
4
+
5
+ class PEdump::Loader
6
+ attr_accessor :mz_hdr, :dos_stub, :pe_hdr, :sections, :pedump
7
+
8
+ # shortcuts
9
+ alias :pe :pe_hdr
10
+ def ep; @pe_hdr.ioh.AddressOfEntryPoint; end
11
+ def ep= v; @pe_hdr.ioh.AddressOfEntryPoint=v; end
12
+
13
+ ########################################################################
14
+ # constructors
15
+ ########################################################################
16
+
17
+ def initialize io = nil, pedump_params = {}
18
+ @pedump = PEdump.new(io, pedump_params)
19
+ if io
20
+ @mz_hdr = @pedump.mz
21
+ @dos_stub = @pedump.dos_stub
22
+ @pe_hdr = @pedump.pe
23
+ load_sections @pedump.sections, io
24
+ end
25
+ end
26
+
27
+ def load_sections section_hdrs, f = nil
28
+ if section_hdrs.is_a?(Array) && section_hdrs.map(&:class).uniq == [PEdump::IMAGE_SECTION_HEADER]
29
+ @sections = section_hdrs.map{ |x| Section.new(x, :deferred_load_io => f) }
30
+ if f.respond_to?(:seek) && f.respond_to?(:read)
31
+ #
32
+ # converted to deferred loading
33
+ #
34
+ # section_hdrs.each_with_index do |sect_hdr, idx|
35
+ # f.seek sect_hdr.PointerToRawData
36
+ # @sections[idx].data = f.read(sect_hdr.SizeOfRawData)
37
+ # end
38
+ elsif f
39
+ raise "invalid 2nd arg: #{f.inspect}"
40
+ end
41
+ else
42
+ raise "invalid arg: #{section_hdrs.inspect}"
43
+ end
44
+ end
45
+
46
+ ########################################################################
47
+ # VA conversion
48
+ ########################################################################
49
+
50
+ def va2section va
51
+ @sections.find{ |x| x.range.include?(va) }
52
+ end
53
+
54
+ def va2stream va
55
+ return nil unless section = va2section(va)
56
+ StringIO.new(section.data).tap do |io|
57
+ io.seek va-section.va
58
+ end
59
+ end
60
+
61
+ ########################################################################
62
+ # virtual memory read/write
63
+ ########################################################################
64
+
65
+ def [] va, size
66
+ section = va2section(va)
67
+ raise "no section for va=0x#{va.to_s 16}" unless section
68
+ offset = va - section.va
69
+ raise "negative offset #{offset}" if offset < 0
70
+ r = section.data[offset,size]
71
+ if r.size < size
72
+ # append some empty data
73
+ r << ("\x00".force_encoding('binary')) * (size - r.size)
74
+ end
75
+ r
76
+ end
77
+
78
+ def []= va, size, data
79
+ raise "data.size != size" if data.size != size
80
+ section = va2section(va)
81
+ raise "no section for va=0x#{va.to_s 16}" unless section
82
+ offset = va - section.va
83
+ raise "negative offset #{offset}" if offset < 0
84
+ if section.data.size < offset
85
+ # append some empty data
86
+ section.data << ("\x00".force_encoding('binary') * (offset-section.data.size))
87
+ end
88
+ section.data[offset, data.size] = data
89
+ end
90
+
91
+ ########################################################################
92
+ # generating PE binary
93
+ ########################################################################
94
+
95
+ def section_table
96
+ @sections.map do |section|
97
+ section.hdr.SizeOfRawData = section.data.size
98
+ section.hdr.pack
99
+ end.join
100
+ end
101
+
102
+ def dump f
103
+ align = @pe_hdr.ioh.FileAlignment
104
+
105
+ mz_size = @mz_hdr.pack.size
106
+ raise "odd mz_size #{mz_size}" if mz_size % 0x10 != 0
107
+ @mz_hdr.header_paragraphs = mz_size / 0x10 # offset of dos_stub
108
+ @mz_hdr.lfanew = mz_size + @dos_stub.size # offset of PE hdr
109
+ f.write @mz_hdr.pack
110
+ f.write @dos_stub
111
+ f.write @pe_hdr.pack
112
+ f.write @pe_hdr.ioh.DataDirectory.map(&:pack).join
113
+
114
+ section_tbl_offset = f.tell # store offset for 2nd write of section table
115
+ f.write section_table
116
+
117
+ @sections.each do |section|
118
+ f.seek(align - (f.tell % align), IO::SEEK_CUR) if f.tell % align != 0
119
+ section.hdr.PointerToRawData = f.tell # fix raw_ptr
120
+ f.write(section.data)
121
+ end
122
+
123
+ eof = f.tell
124
+
125
+ # 2nd write of section table with correct raw_ptr's
126
+ f.seek section_tbl_offset
127
+ f.write section_table
128
+
129
+ f.seek eof
130
+ end
131
+ end
@@ -0,0 +1,51 @@
1
+ class PEdump::Loader
2
+ class Section
3
+ attr_accessor :hdr
4
+ attr_writer :data
5
+
6
+ EMPTY_DATA = ''.force_encoding('binary')
7
+
8
+ def initialize x = nil, args = {}
9
+ if x.is_a?(PEdump::IMAGE_SECTION_HEADER)
10
+ @hdr = x.dup
11
+ end
12
+ @data = EMPTY_DATA.dup
13
+ @deferred_load_io = args[:deferred_load_io]
14
+ @deferred_load_pos = args[:deferred_load_pos] || (@hdr && @hdr.PointerToRawData)
15
+ @deferred_load_size = args[:deferred_load_size] || (@hdr && @hdr.SizeOfRawData)
16
+ end
17
+
18
+ def name; @hdr.Name; end
19
+ def va ; @hdr.VirtualAddress; end
20
+ def vsize; @hdr.VirtualSize; end
21
+ def flags; @hdr.Characteristics; end
22
+ def flags= f; @hdr.Characteristics= f; end
23
+
24
+ def data
25
+ if @data.empty? && @deferred_load_io && @deferred_load_pos && @deferred_load_size.to_i > 0
26
+ begin
27
+ old_pos = @deferred_load_io.tell
28
+ @deferred_load_io.seek @deferred_load_pos
29
+ @data = @deferred_load_io.binmode.read(@deferred_load_size) || EMPTY_DATA.dup
30
+ ensure
31
+ if @deferred_load_io && old_pos
32
+ @deferred_load_io.seek old_pos
33
+ @deferred_load_io = nil # prevent read only on 1st access to data
34
+ end
35
+ end
36
+ end
37
+ @data
38
+ end
39
+
40
+ def range
41
+ va...(va+vsize)
42
+ end
43
+
44
+ def inspect
45
+ "#<Section name=%-10s va=%8x vsize=%8x rawsize=%8s>" % [
46
+ name.inspect, va, vsize,
47
+ @data.size > 0 ? @data.size.to_s(16) : (@deferred_load_io ? "<defer>" : 0)
48
+ ]
49
+ end
50
+ end
51
+ end