pedump 0.5.3
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.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +20 -0
- data/README.md +410 -0
- data/Rakefile +179 -0
- data/VERSION +1 -0
- data/bin/pedump +7 -0
- data/data/fs.txt +224 -0
- data/data/jc-userdb.txt +14371 -0
- data/data/sig.bin +0 -0
- data/data/signatures.txt +678 -0
- data/data/userdb.txt +14083 -0
- data/lib/pedump.rb +868 -0
- data/lib/pedump/cli.rb +804 -0
- data/lib/pedump/comparer.rb +147 -0
- data/lib/pedump/composite_io.rb +56 -0
- data/lib/pedump/core.rb +38 -0
- data/lib/pedump/core_ext/try.rb +57 -0
- data/lib/pedump/loader.rb +393 -0
- data/lib/pedump/loader/minidump.rb +351 -0
- data/lib/pedump/loader/section.rb +57 -0
- data/lib/pedump/logger.rb +67 -0
- data/lib/pedump/ne.rb +425 -0
- data/lib/pedump/ne/version_info.rb +171 -0
- data/lib/pedump/packer.rb +173 -0
- data/lib/pedump/pe.rb +121 -0
- data/lib/pedump/resources.rb +436 -0
- data/lib/pedump/security.rb +58 -0
- data/lib/pedump/sig_parser.rb +507 -0
- data/lib/pedump/tls.rb +17 -0
- data/lib/pedump/unpacker.rb +26 -0
- data/lib/pedump/unpacker/aspack.rb +858 -0
- data/lib/pedump/unpacker/upx.rb +13 -0
- data/lib/pedump/version.rb +10 -0
- data/lib/pedump/version_info.rb +171 -0
- data/misc/aspack/Makefile +3 -0
- data/misc/aspack/aspack_unlzx.c +92 -0
- data/misc/aspack/lzxdec.c +479 -0
- data/misc/aspack/lzxdec.h +56 -0
- data/misc/nedump.c +751 -0
- data/pedump.gemspec +109 -0
- metadata +227 -0
data/lib/pedump/cli.rb
ADDED
@@ -0,0 +1,804 @@
|
|
1
|
+
require 'pedump'
|
2
|
+
require 'pedump/packer'
|
3
|
+
require 'pedump/version_info'
|
4
|
+
require 'optparse'
|
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
|
+
|
32
|
+
class PEdump::CLI
|
33
|
+
attr_accessor :data, :argv
|
34
|
+
|
35
|
+
KNOWN_ACTIONS = (
|
36
|
+
%w'mz dos_stub rich pe ne data_directory sections tls security' +
|
37
|
+
%w'strings resources resource_directory imports exports version_info packer web console packer_only'
|
38
|
+
).map(&:to_sym)
|
39
|
+
|
40
|
+
DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console'.map(&:to_sym)
|
41
|
+
|
42
|
+
URL_BASE = "http://pedump.me"
|
43
|
+
|
44
|
+
def initialize argv = ARGV
|
45
|
+
@argv = argv
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
@actions = []
|
50
|
+
@options = { :format => :table, :verbose => 0 }
|
51
|
+
optparser = OptionParser.new do |opts|
|
52
|
+
opts.banner = "Usage: pedump [options]"
|
53
|
+
|
54
|
+
opts.on "--version", "Print version information and exit" do
|
55
|
+
puts PEdump::VERSION
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
opts.on "-v", "--verbose", "Run verbosely","(can be used multiple times)" do |v|
|
59
|
+
@options[:verbose] += 1
|
60
|
+
end
|
61
|
+
opts.on "-q", "--quiet", "Silent any warnings","(can be used multiple times)" do |v|
|
62
|
+
@options[:verbose] -= 1
|
63
|
+
end
|
64
|
+
opts.on "-F", "--force", "Try to dump by all means","(can cause exceptions & heavy wounds)" do |v|
|
65
|
+
@options[:force] ||= 0
|
66
|
+
@options[:force] += 1
|
67
|
+
end
|
68
|
+
opts.on "-f", "--format FORMAT", [:binary, :c, :dump, :hex, :inspect, :table, :yaml],
|
69
|
+
"Output format: bin,c,dump,hex,inspect,table,yaml","(default: table)" do |v|
|
70
|
+
@options[:format] = v
|
71
|
+
end
|
72
|
+
KNOWN_ACTIONS.each do |t|
|
73
|
+
a = [
|
74
|
+
"--#{t.to_s.tr('_','-')}",
|
75
|
+
eval("lambda{ |_| @actions << :#{t.to_s.tr('-','_')} }")
|
76
|
+
]
|
77
|
+
a.unshift(a[0][1,2].upcase) if a[0] =~ /--(((ex|im)port|section|resource)s|version-info)/
|
78
|
+
a.unshift(a[0][1,2]) if a[0] =~ /--strings/
|
79
|
+
opts.on *a
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on "--deep", "packer deep scan, significantly slower" do
|
83
|
+
@options[:deep] ||= 0
|
84
|
+
@options[:deep] += 1
|
85
|
+
PEdump::Packer.default_deep = @options[:deep]
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on '-P', "--packer-only", "packer/compiler detect only,","mimics 'file' command output" do
|
89
|
+
@actions << :packer_only
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on '-r', "--recursive", "recurse dirs in packer detect" do
|
93
|
+
@options[:recursive] = true
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on "--all", "Dump all but resource-directory (default)" do
|
97
|
+
@actions = DEFAULT_ALL_ACTIONS
|
98
|
+
end
|
99
|
+
opts.on "--va2file VA", "Convert RVA to file offset" do |va|
|
100
|
+
@actions << [:va2file,va]
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.separator ''
|
104
|
+
|
105
|
+
opts.on "-W", "--web", "Uploads files to a #{URL_BASE}","for a nice HTML tables with image previews,","candies & stuff" do
|
106
|
+
@actions << :web
|
107
|
+
end
|
108
|
+
opts.on "-C", "--console", "opens IRB console with specified file loaded" do
|
109
|
+
@actions << :console
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if (@argv = optparser.parse(@argv)).empty?
|
114
|
+
puts optparser.help
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
if (@actions-KNOWN_ACTIONS).any?{ |x| !x.is_a?(Array) }
|
119
|
+
puts "[?] unknown actions: #{@actions-KNOWN_ACTIONS}"
|
120
|
+
@actions.delete_if{ |x| !KNOWN_ACTIONS.include?(x) }
|
121
|
+
end
|
122
|
+
@actions = DEFAULT_ALL_ACTIONS if @actions.empty?
|
123
|
+
|
124
|
+
if @actions.include?(:packer_only)
|
125
|
+
raise "[!] can't mix --packer-only with other actions" if @actions.size > 1
|
126
|
+
dump_packer_only(argv)
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
argv.each_with_index do |fname,idx|
|
131
|
+
@need_fname_header = (argv.size > 1)
|
132
|
+
@file_idx = idx
|
133
|
+
@file_name = fname
|
134
|
+
|
135
|
+
File.open(fname,'rb') do |f|
|
136
|
+
@pedump = create_pedump fname
|
137
|
+
|
138
|
+
next if !@options[:force] && !@pedump.mz(f)
|
139
|
+
|
140
|
+
@actions.each do |action|
|
141
|
+
case action
|
142
|
+
when :web; upload f
|
143
|
+
when :console; console f
|
144
|
+
else
|
145
|
+
dump_action action,f
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
rescue Errno::EPIPE
|
151
|
+
# output interrupt, f.ex. when piping output to a 'head' command
|
152
|
+
# prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
|
153
|
+
end
|
154
|
+
|
155
|
+
def create_pedump fname
|
156
|
+
PEdump.new(fname, :force => @options[:force]).tap do |x|
|
157
|
+
x.logger.level =
|
158
|
+
case @options[:verbose]
|
159
|
+
when -100..-3
|
160
|
+
Logger::FATAL + 1
|
161
|
+
when -2
|
162
|
+
Logger::FATAL
|
163
|
+
when -1
|
164
|
+
Logger::ERROR
|
165
|
+
when 0
|
166
|
+
Logger::WARN # default
|
167
|
+
when 1
|
168
|
+
Logger::INFO
|
169
|
+
when 2..100
|
170
|
+
Logger::DEBUG
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def dump_packer_only fnames
|
176
|
+
max_fname_len = fnames.map(&:size).max
|
177
|
+
fnames.each do |fname|
|
178
|
+
if File.directory?(fname)
|
179
|
+
if @options[:recursive]
|
180
|
+
dump_packer_only(Dir[File.join(fname.shellescape,"*")])
|
181
|
+
else
|
182
|
+
STDERR.puts "[?] #{fname} is a directory, and recursive flag is not set"
|
183
|
+
end
|
184
|
+
else
|
185
|
+
File.open(fname,'rb') do |f|
|
186
|
+
@pedump = create_pedump fname
|
187
|
+
packers = @pedump.packers(f)
|
188
|
+
pname = Array(packers).first.try(:packer).try(:name)
|
189
|
+
pname ||= "unknown" if @options[:verbose] > 0
|
190
|
+
printf("%-*s %s\n", max_fname_len+1, "#{fname}:", pname) if pname
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class ProgressProxy
|
197
|
+
attr_reader :pbar
|
198
|
+
|
199
|
+
def initialize file
|
200
|
+
@file = file
|
201
|
+
@pbar = ProgressBar.new("[.] uploading", file.size, STDOUT)
|
202
|
+
@pbar.try(:file_transfer_mode)
|
203
|
+
@pbar.bar_mark = '='
|
204
|
+
end
|
205
|
+
def read *args
|
206
|
+
@pbar.inc args.first
|
207
|
+
@file.read *args
|
208
|
+
end
|
209
|
+
def method_missing *args
|
210
|
+
@file.send *args
|
211
|
+
end
|
212
|
+
def respond_to? *args
|
213
|
+
@file.respond_to?(args.first) || super(*args)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def upload f
|
218
|
+
if @pedump.mz(f).signature != 'MZ'
|
219
|
+
@pedump.logger.error "[!] refusing to upload a non-MZ file"
|
220
|
+
return
|
221
|
+
end
|
222
|
+
|
223
|
+
require 'digest/md5'
|
224
|
+
require 'open-uri'
|
225
|
+
require 'net/http'
|
226
|
+
require 'net/http/post/multipart'
|
227
|
+
require 'progressbar'
|
228
|
+
|
229
|
+
stdout_sync = STDOUT.sync
|
230
|
+
STDOUT.sync = true
|
231
|
+
|
232
|
+
md5 = Digest::MD5.file(f.path).hexdigest
|
233
|
+
@pedump.logger.info "[.] md5: #{md5}"
|
234
|
+
file_url = "#{URL_BASE}/#{md5}/"
|
235
|
+
|
236
|
+
@pedump.logger.warn "[.] checking if file already uploaded.."
|
237
|
+
Net::HTTP.start('pedump.me') do |http|
|
238
|
+
http.open_timeout = 10
|
239
|
+
http.read_timeout = 10
|
240
|
+
# doing HTTP HEAD is a lot faster than open-uri
|
241
|
+
h = http.head("/#{md5}/")
|
242
|
+
if h.code.to_i == 200 && h.content_type.to_s.strip.downcase == "text/html"
|
243
|
+
@pedump.logger.warn "[.] file already uploaded: #{file_url}"
|
244
|
+
return
|
245
|
+
elsif h.code.to_i != 404 # 404 means that there's no such file and we're OK to upload
|
246
|
+
@pedump.logger.fatal "[!] invalid server response: \"#{h.code} #{h.msg}\" (#{h.content_type})"
|
247
|
+
exit(1)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
f.rewind
|
252
|
+
|
253
|
+
# upload with progressbar
|
254
|
+
post_url = URI.parse(URL_BASE+'/')
|
255
|
+
uio = UploadIO.new(f, "application/octet-stream", File.basename(f.path))
|
256
|
+
ppx = ProgressProxy.new(uio)
|
257
|
+
req = Net::HTTP::Post::Multipart.new post_url.path, "file" => ppx
|
258
|
+
res = Net::HTTP.start(post_url.host, post_url.port){ |http| http.request(req) }
|
259
|
+
ppx.pbar.finish
|
260
|
+
|
261
|
+
puts
|
262
|
+
puts "[.] analyzing..."
|
263
|
+
|
264
|
+
if (r=open(File.join(URL_BASE,md5,'analyze')).read) != "OK"
|
265
|
+
raise "invalid server response: #{r}"
|
266
|
+
end
|
267
|
+
|
268
|
+
puts "[.] uploaded: #{file_url}"
|
269
|
+
ensure
|
270
|
+
STDOUT.sync = stdout_sync
|
271
|
+
end
|
272
|
+
|
273
|
+
def console f
|
274
|
+
require 'pedump/loader'
|
275
|
+
require 'pp'
|
276
|
+
|
277
|
+
ARGV.clear # clear ARGV so IRB is not confused
|
278
|
+
require 'irb'
|
279
|
+
f.rewind
|
280
|
+
ldr = @ldr = PEdump::Loader.new(f)
|
281
|
+
|
282
|
+
# override IRB.setup, called from IRB.start
|
283
|
+
m0 = IRB.method(:setup)
|
284
|
+
IRB.define_singleton_method :setup do |*args|
|
285
|
+
m0.call *args
|
286
|
+
conf[:IRB_RC] = Proc.new do |context|
|
287
|
+
context.main.instance_variable_set '@ldr', ldr
|
288
|
+
context.main.define_singleton_method(:ldr){ @ldr }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
puts "[.] ldr = PEdump::Loader.new(open(#{f.path.inspect}))".gray
|
293
|
+
IRB.start
|
294
|
+
end
|
295
|
+
|
296
|
+
def action_title action
|
297
|
+
if @need_fname_header
|
298
|
+
@need_fname_header = false
|
299
|
+
puts if @file_idx > 0
|
300
|
+
puts "# -----------------------------------------------"
|
301
|
+
puts "# #@file_name"
|
302
|
+
puts "# -----------------------------------------------"
|
303
|
+
end
|
304
|
+
|
305
|
+
s = action.to_s.upcase.tr('_',' ')
|
306
|
+
s += " Header" if [:mz, :pe, :rich].include?(action)
|
307
|
+
s = "Packer / Compiler" if action == :packer
|
308
|
+
"\n=== %s ===\n\n" % s
|
309
|
+
end
|
310
|
+
|
311
|
+
def dump_action action, f
|
312
|
+
if action.is_a?(Array)
|
313
|
+
case action[0]
|
314
|
+
when :va2file
|
315
|
+
@pedump.sections(f)
|
316
|
+
va = action[1] =~ /(^0x)|(h$)/i ? action[1].to_i(16) : action[1].to_i
|
317
|
+
file_offset = @pedump.va2file(va)
|
318
|
+
printf "va2file(0x%x) = 0x%x (%d)\n", va, file_offset, file_offset
|
319
|
+
return
|
320
|
+
else raise "unknown action #{action.inspect}"
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
data = @pedump.send(action, f)
|
325
|
+
return if !data || (data.respond_to?(:empty?) && data.empty?)
|
326
|
+
|
327
|
+
puts action_title(action) unless @options[:format] == :binary
|
328
|
+
|
329
|
+
return dump(data) if [:inspect, :table, :yaml].include?(@options[:format])
|
330
|
+
|
331
|
+
dump_opts = {:name => action}
|
332
|
+
case action
|
333
|
+
when :pe
|
334
|
+
data = @pedump.pe.pack
|
335
|
+
when :resources
|
336
|
+
return dump_resources(data)
|
337
|
+
when :strings
|
338
|
+
return dump_strings(data)
|
339
|
+
when :imports
|
340
|
+
return dump_imports(data)
|
341
|
+
when :exports
|
342
|
+
return dump_exports(data)
|
343
|
+
when :version_info
|
344
|
+
return dump_version_info(data)
|
345
|
+
else
|
346
|
+
if data.is_a?(Struct) && data.respond_to?(:pack)
|
347
|
+
data = data.pack
|
348
|
+
elsif data.is_a?(Array) && data.all?{ |x| x.is_a?(Struct) && x.respond_to?(:pack)}
|
349
|
+
data = data.map(&:pack).join
|
350
|
+
end
|
351
|
+
end
|
352
|
+
dump data, dump_opts
|
353
|
+
end
|
354
|
+
|
355
|
+
def dump data, opts = {}
|
356
|
+
case opts[:format] || @options[:format] || :dump
|
357
|
+
when :dump, :hexdump
|
358
|
+
data.hexdump
|
359
|
+
when :hex
|
360
|
+
puts data.each_byte.map{ |x| "%02x" % x }.join(' ')
|
361
|
+
when :binary
|
362
|
+
print data
|
363
|
+
when :c
|
364
|
+
name = opts[:name] || "foo"
|
365
|
+
puts "// #{data.size} bytes total"
|
366
|
+
puts "unsigned char #{name}[] = {"
|
367
|
+
data.unpack('C*').each_slice(12) do |row|
|
368
|
+
puts " " + row.map{ |c| "0x%02x," % c}.join(" ")
|
369
|
+
end
|
370
|
+
puts "};"
|
371
|
+
when :inspect
|
372
|
+
require 'pp'
|
373
|
+
pp data
|
374
|
+
when :table
|
375
|
+
dump_table data
|
376
|
+
when :yaml
|
377
|
+
require 'yaml'
|
378
|
+
puts data.to_yaml
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
COMMENTS = {
|
383
|
+
:Machine => {
|
384
|
+
0x014c => 'x86',
|
385
|
+
0x0200 => 'Intel Itanium',
|
386
|
+
0x8664 => 'x64',
|
387
|
+
'default' => '???'
|
388
|
+
},
|
389
|
+
:Magic => {
|
390
|
+
0x010b => '32-bit executable',
|
391
|
+
0x020b => '64-bit executable',
|
392
|
+
0x0107 => 'ROM image',
|
393
|
+
'default' => '???'
|
394
|
+
},
|
395
|
+
:Subsystem => PEdump::IMAGE_SUBSYSTEMS
|
396
|
+
}
|
397
|
+
|
398
|
+
def _flags2string flags
|
399
|
+
return '' if !flags || flags.empty?
|
400
|
+
a = [flags.shift.dup]
|
401
|
+
flags.each do |f|
|
402
|
+
if (a.last.size + f.size) < 40
|
403
|
+
a.last << ", " << f
|
404
|
+
else
|
405
|
+
a << f.dup
|
406
|
+
end
|
407
|
+
end
|
408
|
+
a.join("\n"+ ' '*58)
|
409
|
+
end
|
410
|
+
|
411
|
+
def dump_generic_table data
|
412
|
+
data.each_pair do |k,v|
|
413
|
+
next if [:DataDirectory, :section_table].include?(k)
|
414
|
+
case v
|
415
|
+
when Numeric
|
416
|
+
case k
|
417
|
+
when /\AMajor.*Version\Z/
|
418
|
+
printf "%30s: %24s\n", k.to_s.sub('Major',''), "#{v}.#{data[k.to_s.sub('Major','Minor')]}"
|
419
|
+
when /\AMinor.*Version\Z/
|
420
|
+
when /TimeDateStamp/
|
421
|
+
printf "%30s: %24s\n", k, Time.at(v).utc.strftime('"%Y-%m-%d %H:%M:%S"')
|
422
|
+
else
|
423
|
+
comment = ''
|
424
|
+
if COMMENTS[k]
|
425
|
+
comment = COMMENTS[k][v] || (COMMENTS[k].is_a?(Hash) ? COMMENTS[k]['default'] : '') || ''
|
426
|
+
elsif data.is_a?(PEdump::IMAGE_FILE_HEADER) && k == :Characteristics
|
427
|
+
comment = _flags2string(data.flags)
|
428
|
+
elsif k == :DllCharacteristics
|
429
|
+
comment = _flags2string(data.flags)
|
430
|
+
end
|
431
|
+
comment.strip!
|
432
|
+
comment = " #{comment}" unless comment.empty?
|
433
|
+
printf "%30s: %10d %12s%s\n", k, v, v<10 ? v : ("0x"+v.to_s(16)), comment
|
434
|
+
end
|
435
|
+
when Struct
|
436
|
+
# IMAGE_FILE_HEADER:
|
437
|
+
# IMAGE_OPTIONAL_HEADER:
|
438
|
+
printf "\n# %s:\n", v.class.to_s.split('::').last
|
439
|
+
dump_table v
|
440
|
+
when Time
|
441
|
+
printf "%30s: %24s\n", k, v.strftime('"%Y-%m-%d %H:%M:%S"')
|
442
|
+
else
|
443
|
+
printf "%30s: %24s\n", k, v.to_s.inspect
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def dump_table data
|
449
|
+
if data.is_a?(Struct)
|
450
|
+
return dump_res_dir(data) if data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
451
|
+
return dump_exports(data) if data.is_a?(PEdump::IMAGE_EXPORT_DIRECTORY)
|
452
|
+
dump_generic_table data
|
453
|
+
elsif data.is_a?(Enumerable) && data.map(&:class).uniq.size == 1
|
454
|
+
case data.first
|
455
|
+
when PEdump::IMAGE_DATA_DIRECTORY
|
456
|
+
dump_data_dir data
|
457
|
+
when PEdump::IMAGE_SECTION_HEADER
|
458
|
+
dump_sections data
|
459
|
+
when PEdump::Resource
|
460
|
+
dump_resources data
|
461
|
+
when PEdump::STRING
|
462
|
+
dump_strings data
|
463
|
+
when PEdump::IMAGE_IMPORT_DESCRIPTOR, PEdump::ImportedFunction
|
464
|
+
dump_imports data
|
465
|
+
when PEdump::Packer::Match
|
466
|
+
dump_packers data
|
467
|
+
when PEdump::VS_VERSIONINFO, PEdump::NE::VS_VERSIONINFO
|
468
|
+
dump_version_info data
|
469
|
+
when PEdump::IMAGE_TLS_DIRECTORY32, PEdump::IMAGE_TLS_DIRECTORY64
|
470
|
+
dump_tls data
|
471
|
+
when PEdump::WIN_CERTIFICATE
|
472
|
+
dump_security data
|
473
|
+
when PEdump::NE::Segment
|
474
|
+
dump_ne_segments data
|
475
|
+
else
|
476
|
+
puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
|
477
|
+
end
|
478
|
+
elsif data.is_a?(PEdump::DOSStub)
|
479
|
+
data.hexdump
|
480
|
+
elsif data.is_a?(PEdump::RichHdr)
|
481
|
+
dump_rich_hdr data
|
482
|
+
else
|
483
|
+
puts "[?] Don't know how to display #{data.inspect[0,50]}... as a table"
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def dump_security data
|
488
|
+
return unless data
|
489
|
+
data.each do |win_cert|
|
490
|
+
if win_cert.data.respond_to?(:certificates)
|
491
|
+
win_cert.data.certificates.each do |cert|
|
492
|
+
puts cert.to_text
|
493
|
+
puts
|
494
|
+
end
|
495
|
+
else
|
496
|
+
@pedump.logger.error "[?] no certificates in #{win_cert.class}"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def dump_tls data
|
502
|
+
fmt = "%10x %10x %8x %8x %8x %8x\n"
|
503
|
+
printf fmt.tr('x','s'), *%w'RAW_START RAW_END INDEX CALLBKS ZEROFILL FLAGS'
|
504
|
+
data.each do |tls|
|
505
|
+
printf fmt,
|
506
|
+
tls.StartAddressOfRawData.to_i,
|
507
|
+
tls.EndAddressOfRawData.to_i,
|
508
|
+
tls.AddressOfIndex.to_i,
|
509
|
+
tls.AddressOfCallBacks.to_i,
|
510
|
+
tls.SizeOfZeroFill.to_i,
|
511
|
+
tls.Characteristics.to_i
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
def dump_version_info data
|
516
|
+
if @options[:format] != :table
|
517
|
+
File.open(@file_name,'rb') do |f|
|
518
|
+
@pedump.resources.find_all{ |r| r.type == 'VERSION'}.each do |res|
|
519
|
+
f.seek res.file_offset
|
520
|
+
data = f.read(res.size)
|
521
|
+
dump data
|
522
|
+
end
|
523
|
+
end
|
524
|
+
return
|
525
|
+
end
|
526
|
+
|
527
|
+
fmt = " %-20s: %s\n"
|
528
|
+
data.each do |vi|
|
529
|
+
puts "# VS_FIXEDFILEINFO:"
|
530
|
+
|
531
|
+
if @options[:verbose] > 0 || vi.Value.dwSignature != 0xfeef04bd
|
532
|
+
printf(fmt, "Signature", "0x#{vi.Value.dwSignature.to_i.to_s(16)}")
|
533
|
+
end
|
534
|
+
|
535
|
+
printf fmt, 'FileVersion', [
|
536
|
+
vi.Value.dwFileVersionMS.to_i >> 16,
|
537
|
+
vi.Value.dwFileVersionMS.to_i & 0xffff,
|
538
|
+
vi.Value.dwFileVersionLS.to_i >> 16,
|
539
|
+
vi.Value.dwFileVersionLS.to_i & 0xffff
|
540
|
+
].join('.')
|
541
|
+
|
542
|
+
printf fmt, 'ProductVersion', [
|
543
|
+
vi.Value.dwProductVersionMS.to_i >> 16,
|
544
|
+
vi.Value.dwProductVersionMS.to_i & 0xffff,
|
545
|
+
vi.Value.dwProductVersionLS.to_i >> 16,
|
546
|
+
vi.Value.dwProductVersionLS.to_i & 0xffff
|
547
|
+
].join('.')
|
548
|
+
|
549
|
+
vi.Value.each_pair do |k,v|
|
550
|
+
next if k[/[ML]S$/] || k == :valid || k == :dwSignature
|
551
|
+
printf fmt, k.to_s.sub(/^dw/,''), v.to_i > 9 ? "0x#{v.to_s(16)}" : v
|
552
|
+
end
|
553
|
+
|
554
|
+
vi.Children.each do |file_info|
|
555
|
+
case file_info
|
556
|
+
when PEdump::StringFileInfo, PEdump::NE::StringFileInfo
|
557
|
+
file_info.Children.each do |string_table|
|
558
|
+
puts "\n# StringTable #{string_table.szKey}:"
|
559
|
+
string_table.Children.each do |string|
|
560
|
+
printf fmt, string.szKey, string.Value.inspect
|
561
|
+
end
|
562
|
+
end
|
563
|
+
when PEdump::VarFileInfo, PEdump::NE::VarFileInfo
|
564
|
+
puts
|
565
|
+
printf fmt, "VarFileInfo", '[ 0x' + file_info.Children.Value.map{|v| v.to_s(16)}.join(", 0x") + ' ]'
|
566
|
+
else
|
567
|
+
puts "[?] unknown child type: #{file_info.inspect}, use -fi to inspect"
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def dump_packers data
|
574
|
+
if @options[:verbose] > 0
|
575
|
+
data.each do |p|
|
576
|
+
printf "%8x %4d %s\n", p.offset, p.packer.size, p.packer.name
|
577
|
+
end
|
578
|
+
else
|
579
|
+
# show only largest detected unless verbose output requested
|
580
|
+
puts " #{data.first.packer.name}"
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def dump_exports data
|
585
|
+
printf "# module %s\n", data.name.inspect
|
586
|
+
printf "# description %s\n", data.description.inspect if data.description
|
587
|
+
|
588
|
+
if data.Characteristics || data.TimeDateStamp || data.MajorVersion || data.MinorVersion || data.Base
|
589
|
+
printf "# flags=0x%x ts=%s version=%d.%d ord_base=%d\n",
|
590
|
+
data.Characteristics.to_i,
|
591
|
+
Time.at(data.TimeDateStamp.to_i).utc.strftime('"%Y-%m-%d %H:%M:%S"'),
|
592
|
+
data.MajorVersion.to_i, data.MinorVersion.to_i,
|
593
|
+
data.Base.to_i
|
594
|
+
end
|
595
|
+
|
596
|
+
if @options[:verbose] > 0
|
597
|
+
[%w'Names', %w'EntryPoints Functions', %w'Ordinals NameOrdinals'].each do |x|
|
598
|
+
va = data["AddressOf"+x.last]
|
599
|
+
ofs = @pedump.va2file(va) || '?'
|
600
|
+
printf("# %-12s rva=0x%08x file_offset=%8s\n", x.first, va, ofs) if va
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
if data.NumberOfFunctions || data.NumberOfNames
|
605
|
+
printf "# nFuncs=%d nNames=%d\n", data.NumberOfFunctions.to_i, data.NumberOfNames.to_i
|
606
|
+
end
|
607
|
+
|
608
|
+
if data.functions && data.functions.any?
|
609
|
+
puts
|
610
|
+
if @pedump.ne?
|
611
|
+
printf "%5s %9s %s\n", "ORD", "SEG:OFFS", "NAME"
|
612
|
+
data.functions.each do |f|
|
613
|
+
printf "%5x %4x:%04x %s\n", f.ord, f.va>>16, f.va&0xffff, f.name
|
614
|
+
end
|
615
|
+
else
|
616
|
+
printf "%5s %8s %s\n", "ORD", "ENTRY_VA", "NAME"
|
617
|
+
data.functions.each do |f|
|
618
|
+
printf "%5x %8x %s\n", f.ord, f.va, f.name
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
def dump_imports data
|
625
|
+
fmt = "%-15s %5s %5s %s\n"
|
626
|
+
printf fmt, "MODULE_NAME", "HINT", "ORD", "FUNCTION_NAME"
|
627
|
+
data.each do |x|
|
628
|
+
case x
|
629
|
+
when PEdump::IMAGE_IMPORT_DESCRIPTOR
|
630
|
+
(Array(x.original_first_thunk) + Array(x.first_thunk)).uniq.each do |f|
|
631
|
+
next unless f
|
632
|
+
# imported function
|
633
|
+
printf fmt,
|
634
|
+
x.module_name,
|
635
|
+
f.hint ? f.hint.to_s(16) : '',
|
636
|
+
f.ordinal ? f.ordinal.to_s(16) : '',
|
637
|
+
f.name
|
638
|
+
end
|
639
|
+
when PEdump::ImportedFunction
|
640
|
+
printf fmt,
|
641
|
+
x.module_name,
|
642
|
+
x.hint ? x.hint.to_s(16) : '',
|
643
|
+
x.ordinal ? x.ordinal.to_s(16) : '',
|
644
|
+
x.name
|
645
|
+
else
|
646
|
+
raise "invalid #{x.inspect}"
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
def dump_strings data
|
652
|
+
printf "%5s %5s %4s %s\n", "ID", "ID", "LANG", "STRING"
|
653
|
+
prev_lang = nil
|
654
|
+
data.sort_by{|s| [s.lang, s.id] }.each do |s|
|
655
|
+
#puts if prev_lang && prev_lang != s.lang
|
656
|
+
printf "%5d %5x %4s %s\n", s.id, s.id, s.lang && s.lang.to_s(16), s.value.inspect
|
657
|
+
prev_lang = s.lang
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
def dump_res_dir entry, level = 0
|
662
|
+
if entry.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
663
|
+
# root entry
|
664
|
+
printf "dir? %8s %8s %5s %5s", "FLAGS", "TIMESTMP", "VERS", 'nEnt'
|
665
|
+
printf " | %-15s %8s | ", "NAME", "OFFSET"
|
666
|
+
printf "data? %8s %8s %5s %8s\n", 'DATA_OFS', 'DATA_SZ', 'CP', 'RESERVED'
|
667
|
+
end
|
668
|
+
|
669
|
+
dir =
|
670
|
+
case entry
|
671
|
+
when PEdump::IMAGE_RESOURCE_DIRECTORY
|
672
|
+
entry
|
673
|
+
when PEdump::IMAGE_RESOURCE_DIRECTORY_ENTRY
|
674
|
+
entry.data
|
675
|
+
end
|
676
|
+
|
677
|
+
fmt1 = "DIR: %8x %8x %5s %5d"
|
678
|
+
fmt1s = fmt1.tr("xd\nDIR:","ss ") % ['','','','']
|
679
|
+
|
680
|
+
if dir.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
681
|
+
printf fmt1,
|
682
|
+
dir.Characteristics, dir.TimeDateStamp,
|
683
|
+
[dir.MajorVersion,dir.MinorVersion].join('.'),
|
684
|
+
dir.NumberOfNamedEntries + dir.NumberOfIdEntries
|
685
|
+
else
|
686
|
+
print fmt1s
|
687
|
+
end
|
688
|
+
|
689
|
+
name =
|
690
|
+
case level
|
691
|
+
when 0 then "ROOT"
|
692
|
+
when 1 then PEdump::ROOT_RES_NAMES[entry.Name] || entry.name
|
693
|
+
else entry.name
|
694
|
+
end
|
695
|
+
|
696
|
+
printf " | %-15s", name
|
697
|
+
printf("\n%s %15s",fmt1s,'') if name.size > 15
|
698
|
+
printf " %8x | ", entry.respond_to?(:OffsetToData) ? entry.OffsetToData : 0
|
699
|
+
|
700
|
+
if dir.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
701
|
+
puts
|
702
|
+
dir.entries.each do |child|
|
703
|
+
dump_res_dir child, level+1
|
704
|
+
end
|
705
|
+
elsif dir
|
706
|
+
printf "DATA: %8x %8x %5s %8x\n", dir.OffsetToData, dir.Size, dir.CodePage, dir.Reserved
|
707
|
+
else
|
708
|
+
puts # null dir
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
# def dump_res_dir0 dir, level=0, dir_entry = nil
|
713
|
+
# dir_entry ||= PEdump::IMAGE_RESOURCE_DIRECTORY_ENTRY.new
|
714
|
+
# printf "%-10s %8x %8x %8x %5s %5d\n", dir_entry.name || "ROOT", dir_entry.OffsetToData.to_i,
|
715
|
+
# dir.Characteristics, dir.TimeDateStamp,
|
716
|
+
# [dir.MajorVersion,dir.MinorVersion].join('.'),
|
717
|
+
# dir.NumberOfNamedEntries + dir.NumberOfIdEntries
|
718
|
+
# dir.entries.each do |child|
|
719
|
+
# if child.data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
720
|
+
# dump_res_dir child.data, level+1, child
|
721
|
+
# else
|
722
|
+
# print " "*(level+1) + "CHILD"
|
723
|
+
# child.data.each_pair do |k,v|
|
724
|
+
# print " #{k[0,2]}=#{v}"
|
725
|
+
# end
|
726
|
+
# puts
|
727
|
+
# #p child
|
728
|
+
# end
|
729
|
+
# end
|
730
|
+
# end
|
731
|
+
|
732
|
+
def dump_resources data
|
733
|
+
keys = []; fmt = []
|
734
|
+
fmt << "%11x " ; keys << :file_offset
|
735
|
+
fmt << "%5d " ; keys << :cp
|
736
|
+
fmt << "%5x " ; keys << :lang
|
737
|
+
fmt << "%8d " ; keys << :size
|
738
|
+
fmt << "%-13s "; keys << :type
|
739
|
+
fmt << "%s\n" ; keys << :name
|
740
|
+
printf fmt.join.tr('dx','s'), *keys.map(&:to_s).map(&:upcase)
|
741
|
+
data.each do |res|
|
742
|
+
fmt.each_with_index do |f,i|
|
743
|
+
if v = res.send(keys[i])
|
744
|
+
if f['x']
|
745
|
+
printf f.tr('x','s'), v.to_i < 10 ? v.to_s : "0x#{v.to_s(16)}"
|
746
|
+
else
|
747
|
+
printf f, v
|
748
|
+
end
|
749
|
+
else
|
750
|
+
# NULL value
|
751
|
+
printf f.tr('xd','s'), ''
|
752
|
+
end
|
753
|
+
end
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
def dump_sections data
|
758
|
+
printf " %-8s %8s %8s %8s %8s %5s %8s %5s %8s %8s\n",
|
759
|
+
'NAME', 'RVA', 'VSZ','RAW_SZ','RAW_PTR','nREL','REL_PTR','nLINE','LINE_PTR','FLAGS'
|
760
|
+
data.each do |s|
|
761
|
+
name = s.Name[/[^a-z0-9_.]/i] ? s.Name.inspect : s.Name
|
762
|
+
name = "#{name}\n " if name.size > 8
|
763
|
+
printf " %-8s %8x %8x %8x %8x %5x %8x %5x %8x %8x %s\n", name.to_s,
|
764
|
+
s.VirtualAddress.to_i, s.VirtualSize.to_i,
|
765
|
+
s.SizeOfRawData.to_i, s.PointerToRawData.to_i,
|
766
|
+
s.NumberOfRelocations.to_i, s.PointerToRelocations.to_i,
|
767
|
+
s.NumberOfLinenumbers.to_i, s.PointerToLinenumbers.to_i,
|
768
|
+
s.flags.to_i, s.flags_desc
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
def dump_ne_segments data
|
773
|
+
fmt = "%2x %6x %6x %9x %9x %6x %s\n"
|
774
|
+
printf fmt.tr('x','s'), *%w'# OFFSET SIZE MIN_ALLOC FILE_OFFS FLAGS', ''
|
775
|
+
data.each_with_index do |seg,idx|
|
776
|
+
printf fmt, idx+1, seg.offset, seg.size, seg.min_alloc_size, seg.file_offset, seg.flags,
|
777
|
+
seg.flags_desc
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
|
782
|
+
def dump_data_dir data
|
783
|
+
data.each do |row|
|
784
|
+
printf " %-12s rva:0x%8x size:0x %8x\n", row.type, row.va.to_i, row.size.to_i
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
def dump_rich_hdr data
|
789
|
+
if decoded = data.decode
|
790
|
+
puts " LIB_ID VERSION TIMES_USED "
|
791
|
+
decoded.each do |row|
|
792
|
+
printf " %5d %2x %7d %4x %7d %3x\n",
|
793
|
+
row.id, row.id, row.version, row.version, row.times, row.times
|
794
|
+
end
|
795
|
+
else
|
796
|
+
puts "# raw:"
|
797
|
+
data.hexdump
|
798
|
+
puts
|
799
|
+
puts "# dexored:"
|
800
|
+
data.dexor.hexdump
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
end # class PEdump::CLI
|