pedump 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.md +56 -0
- data/README.md.tpl +35 -0
- data/Rakefile +152 -0
- data/VERSION +1 -0
- data/bin/pedump +7 -0
- data/data/sig.bin +0 -0
- data/data/sig.txt +14083 -0
- data/lib/pedump.rb +1044 -0
- data/lib/pedump/cli.rb +593 -0
- data/lib/pedump/packer.rb +124 -0
- data/lib/pedump/version.rb +10 -0
- data/pedump.gemspec +76 -0
- data/samples/calc.7z +0 -0
- data/spec/pedump_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +138 -0
data/lib/pedump/cli.rb
ADDED
@@ -0,0 +1,593 @@
|
|
1
|
+
require 'pedump'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
unless Object.instance_methods.include?(:try)
|
5
|
+
class Object
|
6
|
+
def try(*x)
|
7
|
+
send(*x) if respond_to?(x.first)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class PEdump::CLI
|
13
|
+
attr_accessor :data, :argv
|
14
|
+
|
15
|
+
KNOWN_ACTIONS = (
|
16
|
+
%w'mz dos_stub rich pe data_directory sections' +
|
17
|
+
%w'strings resources resource_directory imports exports packer web packer_only'
|
18
|
+
).map(&:to_sym)
|
19
|
+
|
20
|
+
DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only'.map(&:to_sym)
|
21
|
+
|
22
|
+
URL_BASE = "http://pedump.me"
|
23
|
+
|
24
|
+
def initialize argv = ARGV
|
25
|
+
@argv = argv
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
@actions = []
|
30
|
+
@options = { :format => :table }
|
31
|
+
optparser = OptionParser.new do |opts|
|
32
|
+
opts.banner = "Usage: pedump [options]"
|
33
|
+
|
34
|
+
opts.on "-V", "--version", "Print version information and exit" do
|
35
|
+
puts PEdump::VERSION
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
opts.on "-v", "--[no-]verbose", "Run verbosely" do |v|
|
39
|
+
@options[:verbose] ||= 0
|
40
|
+
@options[:verbose] += 1
|
41
|
+
end
|
42
|
+
opts.on "-F", "--force", "Try to dump by all means","(can cause exceptions & heavy wounds)" do |v|
|
43
|
+
@options[:force] ||= 0
|
44
|
+
@options[:force] += 1
|
45
|
+
end
|
46
|
+
opts.on "-f", "--format FORMAT", [:binary, :c, :dump, :hex, :inspect, :table],
|
47
|
+
"Output format: bin,c,dump,hex,inspect,table","(default: table)" do |v|
|
48
|
+
@options[:format] = v
|
49
|
+
end
|
50
|
+
KNOWN_ACTIONS.each do |t|
|
51
|
+
opts.on "--#{t.to_s.tr('_','-')}", eval("lambda{ |_| @actions << :#{t.to_s.tr('-','_')} }")
|
52
|
+
end
|
53
|
+
opts.on '-P', "--packer-only", "packer/compiler detect only,","mimics 'file' command output" do
|
54
|
+
@actions << :packer_only
|
55
|
+
end
|
56
|
+
opts.on "--all", "Dump all but resource-directory (default)" do
|
57
|
+
@actions = DEFAULT_ALL_ACTIONS
|
58
|
+
end
|
59
|
+
opts.on "--va2file VA", "Convert RVA to file offset" do |va|
|
60
|
+
@actions << [:va2file,va]
|
61
|
+
end
|
62
|
+
opts.on "-W", "--web", "Uploads files to a #{URL_BASE}","for a nice HTML tables with image previews,","candies & stuff" do
|
63
|
+
@actions << :web
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if (@argv = optparser.parse(@argv)).empty?
|
68
|
+
puts optparser.help
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
if (@actions-KNOWN_ACTIONS).any?{ |x| !x.is_a?(Array) }
|
73
|
+
puts "[?] unknown actions: #{@actions-KNOWN_ACTIONS}"
|
74
|
+
@actions.delete_if{ |x| !KNOWN_ACTIONS.include?(x) }
|
75
|
+
end
|
76
|
+
@actions = DEFAULT_ALL_ACTIONS if @actions.empty?
|
77
|
+
|
78
|
+
if @actions.include?(:packer_only)
|
79
|
+
raise "[!] can't mix --packer-only with other actions" if @actions.size > 1
|
80
|
+
dump_packer_only(argv)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
argv.each_with_index do |fname,idx|
|
85
|
+
@need_fname_header = (argv.size > 1)
|
86
|
+
@file_idx = idx
|
87
|
+
@file_name = fname
|
88
|
+
|
89
|
+
File.open(fname,'rb') do |f|
|
90
|
+
@pedump = PEdump.new(fname, :force => @options[:force]).tap do |x|
|
91
|
+
if @options[:verbose]
|
92
|
+
x.logger.level = @options[:verbose] > 1 ? Logger::INFO : Logger::DEBUG
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
next if !@options[:force] && !@pedump.mz(f)
|
97
|
+
|
98
|
+
@actions.each do |action|
|
99
|
+
if action == :web
|
100
|
+
upload f
|
101
|
+
else
|
102
|
+
dump_action action,f
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue Errno::EPIPE
|
108
|
+
# output interrupt, f.ex. when piping output to a 'head' command
|
109
|
+
# prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
|
110
|
+
end
|
111
|
+
|
112
|
+
def dump_packer_only fnames
|
113
|
+
max_fname_len = fnames.map(&:size).max
|
114
|
+
fnames.each do |fname|
|
115
|
+
File.open(fname,'rb') do |f|
|
116
|
+
@pedump = PEdump.new(fname, :force => @options[:force]).tap do |x|
|
117
|
+
if @options[:verbose]
|
118
|
+
x.logger.level = @options[:verbose] > 1 ? Logger::INFO : Logger::DEBUG
|
119
|
+
end
|
120
|
+
end
|
121
|
+
packers = @pedump.packers(f)
|
122
|
+
pname = Array(packers).first.try(:packer).try(:name)
|
123
|
+
pname ||= "unknown" if @options[:verbose]
|
124
|
+
printf("%-*s %s\n", max_fname_len+1, "#{fname}:", pname) if pname
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class ProgressProxy
|
130
|
+
attr_reader :pbar
|
131
|
+
|
132
|
+
def initialize file
|
133
|
+
@file = file
|
134
|
+
@pbar = ProgressBar.new("[.] uploading", file.size, STDOUT)
|
135
|
+
@pbar.try(:file_transfer_mode)
|
136
|
+
@pbar.bar_mark = '='
|
137
|
+
end
|
138
|
+
def read *args
|
139
|
+
@pbar.inc args.first
|
140
|
+
@file.read *args
|
141
|
+
end
|
142
|
+
def method_missing *args
|
143
|
+
@file.send *args
|
144
|
+
end
|
145
|
+
def respond_to? *args
|
146
|
+
@file.respond_to?(*args) || super(*args)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def upload f
|
151
|
+
if @pedump.mz(f).signature != 'MZ'
|
152
|
+
@pedump.logger.error "[!] refusing to upload a non-MZ file"
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
require 'digest/md5'
|
157
|
+
require 'open-uri'
|
158
|
+
require 'net/http/post/multipart'
|
159
|
+
require 'progressbar'
|
160
|
+
|
161
|
+
stdout_sync = STDOUT.sync
|
162
|
+
STDOUT.sync = true
|
163
|
+
|
164
|
+
md5 = Digest::MD5.file(f.path).hexdigest
|
165
|
+
@pedump.logger.info "[.] md5: #{md5}"
|
166
|
+
file_url = "#{URL_BASE}/#{md5}/"
|
167
|
+
|
168
|
+
@pedump.logger.info "[.] checking if file already uploaded.."
|
169
|
+
begin
|
170
|
+
if (r=open(file_url).read) == "OK"
|
171
|
+
@pedump.logger.warn "[.] file already uploaded: #{file_url}"
|
172
|
+
return
|
173
|
+
else
|
174
|
+
raise "invalid server response: #{r}"
|
175
|
+
end
|
176
|
+
rescue OpenURI::HTTPError
|
177
|
+
raise unless $!.to_s == "404 Not Found"
|
178
|
+
end
|
179
|
+
|
180
|
+
f.rewind
|
181
|
+
|
182
|
+
# upload with progressbar
|
183
|
+
post_url = URI.parse(URL_BASE+'/')
|
184
|
+
uio = UploadIO.new(f, "application/octet-stream", File.basename(f.path))
|
185
|
+
ppx = ProgressProxy.new(uio)
|
186
|
+
req = Net::HTTP::Post::Multipart.new post_url.path, "file" => ppx
|
187
|
+
res = Net::HTTP.start(post_url.host, post_url.port){ |http| http.request(req) }
|
188
|
+
ppx.pbar.finish
|
189
|
+
|
190
|
+
puts
|
191
|
+
puts "[.] analyzing..."
|
192
|
+
|
193
|
+
if (r=open(File.join(URL_BASE,md5,'analyze')).read) != "OK"
|
194
|
+
raise "invalid server response: #{r}"
|
195
|
+
end
|
196
|
+
|
197
|
+
puts "[.] uploaded: #{file_url}"
|
198
|
+
ensure
|
199
|
+
STDOUT.sync = stdout_sync
|
200
|
+
end
|
201
|
+
|
202
|
+
def action_title action
|
203
|
+
if @need_fname_header
|
204
|
+
@need_fname_header = false
|
205
|
+
puts if @file_idx > 0
|
206
|
+
puts "# -----------------------------------------------"
|
207
|
+
puts "# #@file_name"
|
208
|
+
puts "# -----------------------------------------------"
|
209
|
+
end
|
210
|
+
|
211
|
+
s = action.to_s.upcase.tr('_',' ')
|
212
|
+
s += " Header" if [:mz, :pe, :rich].include?(action)
|
213
|
+
s = "Packer / Compiler" if action == :packer
|
214
|
+
"\n=== %s ===\n\n" % s
|
215
|
+
end
|
216
|
+
|
217
|
+
def dump_action action, f
|
218
|
+
if action.is_a?(Array)
|
219
|
+
case action[0]
|
220
|
+
when :va2file
|
221
|
+
@pedump.sections(f)
|
222
|
+
va = action[1] =~ /(^0x)|(h$)/i ? action[1].to_i(16) : action[1].to_i
|
223
|
+
file_offset = @pedump.va2file(va)
|
224
|
+
printf "va2file(0x%x) = 0x%x (%d)\n", va, file_offset, file_offset
|
225
|
+
return
|
226
|
+
else raise "unknown action #{action.inspect}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
data = @pedump.send(action, f)
|
231
|
+
return if !data || (data.respond_to?(:empty?) && data.empty?)
|
232
|
+
|
233
|
+
puts action_title(action)
|
234
|
+
|
235
|
+
return dump(data) if [:inspect, :table].include?(@options[:format])
|
236
|
+
|
237
|
+
dump_opts = {:name => action}
|
238
|
+
case action
|
239
|
+
when :pe
|
240
|
+
@pedump.pe.ifh.TimeDateStamp = @pedump.pe.ifh.TimeDateStamp.to_i
|
241
|
+
data = @pedump.pe.signature + (@pedump.pe.ifh.try(:pack)||'') + (@pedump.pe.ioh.try(:pack)||'')
|
242
|
+
@pedump.pe.ifh.TimeDateStamp = Time.at(@pedump.pe.ifh.TimeDateStamp)
|
243
|
+
when :resources
|
244
|
+
return dump_resources(data)
|
245
|
+
when :strings
|
246
|
+
return dump_strings(data)
|
247
|
+
when :imports
|
248
|
+
return dump_imports(data)
|
249
|
+
when :exports
|
250
|
+
return dump_exports(data)
|
251
|
+
else
|
252
|
+
if data.is_a?(Struct) && data.respond_to?(:pack)
|
253
|
+
data = data.pack
|
254
|
+
elsif data.is_a?(Array) && data.all?{ |x| x.is_a?(Struct) && x.respond_to?(:pack)}
|
255
|
+
data = data.map(&:pack).join
|
256
|
+
end
|
257
|
+
end
|
258
|
+
dump data, dump_opts
|
259
|
+
end
|
260
|
+
|
261
|
+
def dump data, opts = {}
|
262
|
+
case opts[:format] || @options[:format] || :dump
|
263
|
+
when :dump, :hexdump
|
264
|
+
puts hexdump(data)
|
265
|
+
when :hex
|
266
|
+
puts data.each_byte.map{ |x| "%02x" % x }.join(' ')
|
267
|
+
when :binary
|
268
|
+
print data
|
269
|
+
when :c
|
270
|
+
name = opts[:name] || "foo"
|
271
|
+
puts "// #{data.size} bytes total"
|
272
|
+
puts "unsigned char #{name}[] = {"
|
273
|
+
data.unpack('C*').each_slice(12) do |row|
|
274
|
+
puts " " + row.map{ |c| "0x%02x," % c}.join(" ")
|
275
|
+
end
|
276
|
+
puts "};"
|
277
|
+
when :inspect
|
278
|
+
require 'pp'
|
279
|
+
pp data
|
280
|
+
when :table
|
281
|
+
dump_table data
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
COMMENTS = {
|
286
|
+
:Machine => {
|
287
|
+
0x014c => 'x86',
|
288
|
+
0x0200 => 'Intel Itanium',
|
289
|
+
0x8664 => 'x64',
|
290
|
+
'default' => '???'
|
291
|
+
},
|
292
|
+
:Magic => {
|
293
|
+
0x010b => '32-bit executable',
|
294
|
+
0x020b => '64-bit executable',
|
295
|
+
0x0107 => 'ROM image',
|
296
|
+
'default' => '???'
|
297
|
+
},
|
298
|
+
:Subsystem => PEdump::IMAGE_SUBSYSTEMS
|
299
|
+
}
|
300
|
+
|
301
|
+
def dump_generic_table data
|
302
|
+
data.each_pair do |k,v|
|
303
|
+
case v
|
304
|
+
when Numeric
|
305
|
+
case k
|
306
|
+
when /\AMajor.*Version\Z/
|
307
|
+
printf "%30s: %24s\n", k.to_s.sub('Major',''), "#{v}.#{data[k.to_s.sub('Major','Minor')]}"
|
308
|
+
when /\AMinor.*Version\Z/
|
309
|
+
when /TimeDateStamp/
|
310
|
+
printf "%30s: %24s\n", k, Time.at(v).strftime('"%Y-%m-%d %H:%M:%S"')
|
311
|
+
else
|
312
|
+
if COMMENTS[k]
|
313
|
+
printf "%30s: %10d %12s %s\n", k, v, v<10 ? v : ("0x"+v.to_s(16)),
|
314
|
+
COMMENTS[k][v] || (COMMENTS[k].is_a?(Hash) ? COMMENTS[k]['default'] : '') || ''
|
315
|
+
else
|
316
|
+
printf "%30s: %10d %12s\n", k, v, v<10 ? v : ("0x"+v.to_s(16))
|
317
|
+
end
|
318
|
+
end
|
319
|
+
when Struct
|
320
|
+
printf "\n# %s:\n", v.class.to_s.split('::').last
|
321
|
+
dump_table v
|
322
|
+
when Time
|
323
|
+
printf "%30s: %24s\n", k, v.strftime('"%Y-%m-%d %H:%M:%S"')
|
324
|
+
when Array
|
325
|
+
next if %w'DataDirectory section_table'.include?(k)
|
326
|
+
else
|
327
|
+
printf "%30s: %24s\n", k, v.to_s.inspect
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def dump_table data
|
333
|
+
if data.is_a?(Struct)
|
334
|
+
return dump_res_dir(data) if data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
335
|
+
return dump_exports(data) if data.is_a?(PEdump::IMAGE_EXPORT_DIRECTORY)
|
336
|
+
dump_generic_table data
|
337
|
+
elsif data.is_a?(Enumerable) && data.map(&:class).uniq.size == 1
|
338
|
+
case data.first
|
339
|
+
when PEdump::IMAGE_DATA_DIRECTORY
|
340
|
+
dump_data_dir data
|
341
|
+
when PEdump::IMAGE_SECTION_HEADER
|
342
|
+
dump_sections data
|
343
|
+
when PEdump::Resource
|
344
|
+
dump_resources data
|
345
|
+
when PEdump::STRING
|
346
|
+
dump_strings data
|
347
|
+
when PEdump::IMAGE_IMPORT_DESCRIPTOR
|
348
|
+
dump_imports data
|
349
|
+
when PEdump::Packer::Match
|
350
|
+
dump_packers data
|
351
|
+
else
|
352
|
+
puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
|
353
|
+
end
|
354
|
+
elsif data.is_a?(PEdump::DOSStub)
|
355
|
+
puts hexdump(data)
|
356
|
+
elsif data.is_a?(PEdump::RichHdr)
|
357
|
+
dump_rich_hdr data
|
358
|
+
else
|
359
|
+
puts "[?] Don't know how to display #{data.inspect[0,50]}... as a table"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def dump_packers data
|
364
|
+
if @options[:verbose]
|
365
|
+
data.each do |p|
|
366
|
+
printf "%8x %4d %s\n", p.offset, p.packer.size, p.packer.name
|
367
|
+
end
|
368
|
+
else
|
369
|
+
# show only largest detected unless verbose output requested
|
370
|
+
puts " #{data.first.packer.name}"
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def dump_exports data
|
375
|
+
printf "# module %s\n# flags=0x%x ts=%s version=%d.%d ord_base=%d\n",
|
376
|
+
data.name.inspect,
|
377
|
+
data.Characteristics.to_i,
|
378
|
+
Time.at(data.TimeDateStamp.to_i).strftime('"%Y-%m-%d %H:%M:%S"'),
|
379
|
+
data.MajorVersion, data.MinorVersion,
|
380
|
+
data.Base
|
381
|
+
|
382
|
+
if @options[:verbose]
|
383
|
+
[%w'Names', %w'EntryPoints Functions', %w'Ordinals NameOrdinals'].each do |x|
|
384
|
+
va = data["AddressOf"+x.last]
|
385
|
+
ofs = @pedump.va2file(va) || '?'
|
386
|
+
printf "# %-12s rva=0x%08x file_offset=%8s\n", x.first, va, ofs
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
printf "# nFuncs=%d nNames=%d\n",
|
391
|
+
data.NumberOfFunctions,
|
392
|
+
data.NumberOfNames
|
393
|
+
|
394
|
+
return unless data.name_ordinals.any? || data.entry_points.any? || data.names.any?
|
395
|
+
|
396
|
+
puts
|
397
|
+
|
398
|
+
ord2name = {}
|
399
|
+
data.NumberOfNames.times do |i|
|
400
|
+
ord2name[data.name_ordinals[i]] ||= []
|
401
|
+
ord2name[data.name_ordinals[i]] << data.names[i]
|
402
|
+
end
|
403
|
+
|
404
|
+
printf "%5s %8s %s\n", "ORD", "ENTRY_VA", "NAME"
|
405
|
+
data.NumberOfFunctions.times do |i|
|
406
|
+
ep = data.entry_points[i]
|
407
|
+
names = ord2name[i+data.Base].try(:join,', ')
|
408
|
+
next if ep.to_i == 0 && names.nil?
|
409
|
+
printf "%5d %8x %s\n", i + data.Base, ep, names
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def dump_imports data
|
414
|
+
fmt = "%-15s %5s %5s %s\n"
|
415
|
+
printf fmt, "MODULE_NAME", "HINT", "ORD", "FUNCTION_NAME"
|
416
|
+
data.each do |iid|
|
417
|
+
# image import descriptor
|
418
|
+
(Array(iid.original_first_thunk) + Array(iid.first_thunk)).uniq.each do |f|
|
419
|
+
next unless f
|
420
|
+
# imported function
|
421
|
+
printf fmt,
|
422
|
+
iid.module_name,
|
423
|
+
f.hint ? f.hint.to_s(16) : '',
|
424
|
+
f.ordinal ? f.ordinal.to_s(16) : '',
|
425
|
+
f.name
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def dump_strings data
|
431
|
+
printf "%5s %5s %4s %s\n", "ID", "ID", "LANG", "STRING"
|
432
|
+
prev_lang = nil
|
433
|
+
data.sort_by{|s| [s.lang, s.id] }.each do |s|
|
434
|
+
#puts if prev_lang && prev_lang != s.lang
|
435
|
+
printf "%5d %5x %4x %s\n", s.id, s.id, s.lang, s.value.inspect
|
436
|
+
prev_lang = s.lang
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def dump_res_dir entry, level = 0
|
441
|
+
if entry.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
442
|
+
# root entry
|
443
|
+
printf "dir? %8s %8s %5s %5s", "FLAGS", "TIMESTMP", "VERS", 'nEnt'
|
444
|
+
printf " | %-15s %8s | ", "NAME", "OFFSET"
|
445
|
+
printf "data? %8s %8s %5s %8s\n", 'DATA_OFS', 'DATA_SZ', 'CP', 'RESERVED'
|
446
|
+
end
|
447
|
+
|
448
|
+
dir =
|
449
|
+
case entry
|
450
|
+
when PEdump::IMAGE_RESOURCE_DIRECTORY
|
451
|
+
entry
|
452
|
+
when PEdump::IMAGE_RESOURCE_DIRECTORY_ENTRY
|
453
|
+
entry.data
|
454
|
+
end
|
455
|
+
|
456
|
+
fmt1 = "DIR: %8x %8x %5s %5d"
|
457
|
+
fmt1s = fmt1.tr("xd\nDIR:","ss ") % ['','','','']
|
458
|
+
|
459
|
+
if dir.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
460
|
+
printf fmt1,
|
461
|
+
dir.Characteristics, dir.TimeDateStamp,
|
462
|
+
[dir.MajorVersion,dir.MinorVersion].join('.'),
|
463
|
+
dir.NumberOfNamedEntries + dir.NumberOfIdEntries
|
464
|
+
else
|
465
|
+
print fmt1s
|
466
|
+
end
|
467
|
+
|
468
|
+
name =
|
469
|
+
case level
|
470
|
+
when 0 then "ROOT"
|
471
|
+
when 1 then PEdump::ROOT_RES_NAMES[entry.Name] || entry.name
|
472
|
+
else entry.name
|
473
|
+
end
|
474
|
+
|
475
|
+
printf " | %-15s", name
|
476
|
+
printf("\n%s %15s",fmt1s,'') if name.size > 15
|
477
|
+
printf " %8x | ", entry.respond_to?(:OffsetToData) ? entry.OffsetToData : 0
|
478
|
+
|
479
|
+
if dir.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
480
|
+
puts
|
481
|
+
dir.entries.each do |child|
|
482
|
+
dump_res_dir child, level+1
|
483
|
+
end
|
484
|
+
elsif dir
|
485
|
+
printf "DATA: %8x %8x %5s %8x\n", dir.OffsetToData, dir.Size, dir.CodePage, dir.Reserved
|
486
|
+
else
|
487
|
+
puts # null dir
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
# def dump_res_dir0 dir, level=0, dir_entry = nil
|
492
|
+
# dir_entry ||= PEdump::IMAGE_RESOURCE_DIRECTORY_ENTRY.new
|
493
|
+
# printf "%-10s %8x %8x %8x %5s %5d\n", dir_entry.name || "ROOT", dir_entry.OffsetToData.to_i,
|
494
|
+
# dir.Characteristics, dir.TimeDateStamp,
|
495
|
+
# [dir.MajorVersion,dir.MinorVersion].join('.'),
|
496
|
+
# dir.NumberOfNamedEntries + dir.NumberOfIdEntries
|
497
|
+
# dir.entries.each do |child|
|
498
|
+
# if child.data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
|
499
|
+
# dump_res_dir child.data, level+1, child
|
500
|
+
# else
|
501
|
+
# print " "*(level+1) + "CHILD"
|
502
|
+
# child.data.each_pair do |k,v|
|
503
|
+
# print " #{k[0,2]}=#{v}"
|
504
|
+
# end
|
505
|
+
# puts
|
506
|
+
# #p child
|
507
|
+
# end
|
508
|
+
# end
|
509
|
+
# end
|
510
|
+
|
511
|
+
def dump_resources data
|
512
|
+
keys = []; fmt = []
|
513
|
+
fmt << "%11x " ; keys << :file_offset
|
514
|
+
fmt << "%5d " ; keys << :cp
|
515
|
+
fmt << "%5x " ; keys << :lang
|
516
|
+
fmt << "%8d " ; keys << :size
|
517
|
+
fmt << "%-13s "; keys << :type
|
518
|
+
fmt << "%s\n" ; keys << :name
|
519
|
+
printf fmt.join.tr('dx','s'), *keys.map(&:to_s).map(&:upcase)
|
520
|
+
data.each do |res|
|
521
|
+
fmt.each_with_index do |f,i|
|
522
|
+
v = res.send(keys[i])
|
523
|
+
if f['x']
|
524
|
+
printf f.tr('x','s'), v.to_i < 10 ? v.to_s : "0x#{v.to_s(16)}"
|
525
|
+
else
|
526
|
+
printf f, v
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def dump_sections data
|
533
|
+
printf " %-8s %8s %8s %8s %8s %5s %8s %5s %8s %8s\n",
|
534
|
+
'NAME', 'RVA', 'VSZ','RAW_SZ','RAW_PTR','nREL','REL_PTR','nLINE','LINE_PTR','FLAGS'
|
535
|
+
data.each do |s|
|
536
|
+
name = s.Name[/[^a-z0-9_.]/i] ? s.Name.inspect : s.Name
|
537
|
+
name = "#{name}\n " if name.size > 8
|
538
|
+
printf " %-8s %8x %8x %8x %8x %5x %8x %5x %8x %8x %s\n", name.to_s,
|
539
|
+
s.VirtualAddress.to_i, s.VirtualSize.to_i,
|
540
|
+
s.SizeOfRawData.to_i, s.PointerToRawData.to_i,
|
541
|
+
s.NumberOfRelocations.to_i, s.PointerToRelocations.to_i,
|
542
|
+
s.NumberOfLinenumbers.to_i, s.PointerToLinenumbers.to_i,
|
543
|
+
s.flags.to_i, s.flags_desc
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
def dump_data_dir data
|
548
|
+
data.each do |row|
|
549
|
+
printf " %-12s rva:0x%8x size:0x %8x\n", row.type, row.va.to_i, row.size.to_i
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def dump_rich_hdr data
|
554
|
+
if decoded = data.decode
|
555
|
+
puts " LIB_ID VERSION TIMES_USED "
|
556
|
+
decoded.each do |row|
|
557
|
+
printf " %5d %2x %7d %4x %7d %3x\n",
|
558
|
+
row.id, row.id, row.version, row.version, row.times, row.times
|
559
|
+
end
|
560
|
+
else
|
561
|
+
puts "# raw:"
|
562
|
+
puts hexdump(data)
|
563
|
+
puts
|
564
|
+
puts "# dexored:"
|
565
|
+
puts hexdump(data.dexor)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
def hexdump data, h = {}
|
570
|
+
offset = h[:offset] || 0
|
571
|
+
add = h[:add] || 0
|
572
|
+
size = h[:size] || (data.size-offset)
|
573
|
+
tail = h[:tail] || "\n"
|
574
|
+
width = h[:width] || 0x10 # row width, in bytes
|
575
|
+
|
576
|
+
size = data.size-offset if size+offset > data.size
|
577
|
+
|
578
|
+
r = ''; s = ''
|
579
|
+
r << "%08x: " % (offset + add)
|
580
|
+
ascii = ''
|
581
|
+
size.times do |i|
|
582
|
+
if i%width==0 && i>0
|
583
|
+
r << "%s |%s|\n%08x: " % [s, ascii, offset + add + i]
|
584
|
+
ascii = ''; s = ''
|
585
|
+
end
|
586
|
+
s << " " if i%width%8==0
|
587
|
+
c = data[offset+i].ord
|
588
|
+
s << "%02x " % c
|
589
|
+
ascii << ((32..126).include?(c) ? c.chr : '.')
|
590
|
+
end
|
591
|
+
r << "%-*s |%-*s|%s" % [width*3+width/8+(width%8==0?0:1), s, width, ascii, tail]
|
592
|
+
end
|
593
|
+
end
|