pedump 0.3.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.
- 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
|