pedump 0.6.0 → 0.6.4
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 +4 -4
- data/Gemfile +4 -4
- data/Gemfile.lock +75 -43
- data/README.md +96 -10
- data/Rakefile +44 -9
- data/VERSION +1 -1
- data/data/comp_id.txt +776 -0
- data/lib/pedump/cli.rb +122 -20
- data/lib/pedump/loader/section.rb +5 -3
- data/lib/pedump/loader.rb +28 -6
- data/lib/pedump/rich.rb +562 -0
- data/lib/pedump/te.rb +15 -4
- data/lib/pedump.rb +30 -6
- data/pedump.gemspec +23 -33
- metadata +25 -26
- data/.github/FUNDING.yml +0 -2
- data/.github/dependabot.yml +0 -8
data/lib/pedump/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'pedump'
|
2
2
|
require 'pedump/packer'
|
3
|
+
require 'pedump/rich'
|
3
4
|
require 'pedump/version_info'
|
4
5
|
require 'optparse'
|
5
6
|
|
@@ -34,12 +35,13 @@ class PEdump::CLI
|
|
34
35
|
|
35
36
|
KNOWN_ACTIONS = (
|
36
37
|
%w'mz dos_stub rich pe ne te data_directory sections tls security' +
|
37
|
-
%w'strings resources resource_directory imports exports version_info packer web console packer_only'
|
38
|
+
%w'strings resources resource_directory imports exports version_info packer web console packer_only' +
|
39
|
+
%w'extract' # 'disasm'
|
38
40
|
).map(&:to_sym)
|
39
41
|
|
40
|
-
DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console'.map(&:to_sym)
|
42
|
+
DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console extract disasm'.map(&:to_sym)
|
41
43
|
|
42
|
-
URL_BASE = "
|
44
|
+
URL_BASE = "https://pedump.me"
|
43
45
|
|
44
46
|
def initialize argv = ARGV
|
45
47
|
@argv = argv
|
@@ -96,8 +98,24 @@ class PEdump::CLI
|
|
96
98
|
opts.on "--all", "Dump all but resource-directory (default)" do
|
97
99
|
@actions = DEFAULT_ALL_ACTIONS
|
98
100
|
end
|
101
|
+
|
102
|
+
opts.separator ''
|
103
|
+
|
104
|
+
# opts.on "--disasm [X]", "Disassemble a symbol/VA" do |v|
|
105
|
+
# @actions << [:disasm, v]
|
106
|
+
# end
|
107
|
+
opts.on("--extract ID", "Extract a resource/section/data_dir",
|
108
|
+
"ID: datadir:EXPORT - datadir by type",
|
109
|
+
"ID: resource:0x98478 - resource by offset",
|
110
|
+
"ID: resource:ICON/#1 - resource by type & name",
|
111
|
+
"ID: section:.text - section by name",
|
112
|
+
"ID: section:rva/0x1000 - section by RVA",
|
113
|
+
"ID: section:raw/0x400 - section by RAW_PTR",
|
114
|
+
) do |v|
|
115
|
+
@actions << [:extract, v]
|
116
|
+
end
|
99
117
|
opts.on "--va2file VA", "Convert RVA to file offset" do |va|
|
100
|
-
@actions << [:va2file,va]
|
118
|
+
@actions << [:va2file, va]
|
101
119
|
end
|
102
120
|
|
103
121
|
opts.separator ''
|
@@ -133,7 +151,7 @@ class PEdump::CLI
|
|
133
151
|
@file_name = fname
|
134
152
|
|
135
153
|
File.open(fname,'rb') do |f|
|
136
|
-
@pedump = create_pedump
|
154
|
+
@pedump = create_pedump f
|
137
155
|
|
138
156
|
next if !@options[:force] && !@pedump.supported_file?(f)
|
139
157
|
|
@@ -152,8 +170,8 @@ class PEdump::CLI
|
|
152
170
|
# prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
|
153
171
|
end
|
154
172
|
|
155
|
-
def create_pedump
|
156
|
-
PEdump.new(
|
173
|
+
def create_pedump io
|
174
|
+
PEdump.new(io, :force => @options[:force]).tap do |x|
|
157
175
|
x.logger.level =
|
158
176
|
case @options[:verbose]
|
159
177
|
when -100..-3
|
@@ -194,7 +212,7 @@ class PEdump::CLI
|
|
194
212
|
end
|
195
213
|
|
196
214
|
class ProgressProxy
|
197
|
-
def initialize file, prefix = "[.] uploading: ", io =
|
215
|
+
def initialize file, prefix = "[.] uploading: ", io = $stdout
|
198
216
|
@file = file
|
199
217
|
@io = io
|
200
218
|
@prefix = prefix
|
@@ -227,15 +245,16 @@ class PEdump::CLI
|
|
227
245
|
require 'net/http'
|
228
246
|
require 'net/http/post/multipart'
|
229
247
|
|
230
|
-
stdout_sync =
|
231
|
-
|
248
|
+
stdout_sync = $stdout.sync
|
249
|
+
$stdout.sync = true
|
232
250
|
|
233
251
|
md5 = Digest::MD5.file(f.path).hexdigest
|
234
252
|
@pedump.logger.info "[.] md5: #{md5}"
|
235
253
|
file_url = "#{URL_BASE}/#{md5}/"
|
236
254
|
|
237
255
|
@pedump.logger.warn "[.] checking if file already uploaded.."
|
238
|
-
|
256
|
+
uri = URI.parse URL_BASE
|
257
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme == 'https')) do |http|
|
239
258
|
http.open_timeout = 10
|
240
259
|
http.read_timeout = 10
|
241
260
|
# doing HTTP HEAD is a lot faster than open-uri
|
@@ -257,18 +276,20 @@ class PEdump::CLI
|
|
257
276
|
uio = UploadIO.new(f, "application/octet-stream", File.basename(f.path))
|
258
277
|
ppx = ProgressProxy.new(uio)
|
259
278
|
req = Net::HTTP::Post::Multipart.new post_url.path, "file" => ppx
|
260
|
-
res = Net::HTTP.start(post_url.host, post_url.port)
|
279
|
+
res = Net::HTTP.start(post_url.host, post_url.port, use_ssl: (post_url.scheme == 'https')) do |http|
|
280
|
+
http.request(req)
|
281
|
+
end
|
261
282
|
ppx.finish!
|
262
283
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
284
|
+
unless [200, 302, 303].include?(res.code.to_i)
|
285
|
+
@pedump.logger.fatal "[!] invalid server response: #{res.code} #{res.msg}"
|
286
|
+
@pedump.logger.fatal res.body unless res.body.empty?
|
287
|
+
exit(1)
|
267
288
|
end
|
268
289
|
|
269
290
|
puts "[.] uploaded: #{file_url}"
|
270
291
|
ensure
|
271
|
-
|
292
|
+
$stdout.sync = stdout_sync
|
272
293
|
end
|
273
294
|
|
274
295
|
def console f
|
@@ -312,6 +333,10 @@ class PEdump::CLI
|
|
312
333
|
def dump_action action, f
|
313
334
|
if action.is_a?(Array)
|
314
335
|
case action[0]
|
336
|
+
when :disasm
|
337
|
+
return
|
338
|
+
when :extract
|
339
|
+
return extract action[1]
|
315
340
|
when :va2file
|
316
341
|
@pedump.sections(f)
|
317
342
|
va = action[1] =~ /(^0x)|(h$)/i ? action[1].to_i(16) : action[1].to_i
|
@@ -798,10 +823,12 @@ class PEdump::CLI
|
|
798
823
|
|
799
824
|
def dump_rich_hdr data
|
800
825
|
if decoded = data.decode
|
801
|
-
puts "
|
826
|
+
puts " ID VER COUNT DESCRIPTION"
|
802
827
|
decoded.each do |row|
|
803
|
-
printf " %5d %
|
804
|
-
row.id, row.id, row.version, row.version, row.times, row.times
|
828
|
+
# printf " %5d %4x %7d %4x %12d %8x\n",
|
829
|
+
# row.id, row.id, row.version, row.version, row.times, row.times
|
830
|
+
printf " %4x %4x %12d %s\n",
|
831
|
+
row.id, row.version, row.times, ::PEdump::RICH_IDS[(row.id<<16) + row.version]
|
805
832
|
end
|
806
833
|
else
|
807
834
|
puts "# raw:"
|
@@ -812,4 +839,79 @@ class PEdump::CLI
|
|
812
839
|
end
|
813
840
|
end
|
814
841
|
|
842
|
+
def disasm x
|
843
|
+
puts "TODO"
|
844
|
+
end
|
845
|
+
|
846
|
+
def extract x
|
847
|
+
a = x.split(':',2)
|
848
|
+
case a[0]
|
849
|
+
when 'datadir'
|
850
|
+
extract_datadir a[1]
|
851
|
+
when 'resource'
|
852
|
+
extract_resource a[1]
|
853
|
+
when 'section'
|
854
|
+
extract_section a[1]
|
855
|
+
else
|
856
|
+
raise "invalid #{x.inspect}"
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
def extract_datadir id
|
861
|
+
entry = @pedump.data_directory.find{ |x| x.type == id }
|
862
|
+
unless entry
|
863
|
+
@pedump.logger.fatal "[!] entry #{id.inspect} not found"
|
864
|
+
exit(1)
|
865
|
+
end
|
866
|
+
if entry.size != 0
|
867
|
+
IO::copy_stream @pedump.io, $stdout, entry.size, @pedump.va2file(entry.va)
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
def extract_resource id
|
872
|
+
res = nil
|
873
|
+
if id =~ /\A0x\h+\Z/
|
874
|
+
needle = id.to_i(16)
|
875
|
+
res = @pedump.resources.find{ |r| r.file_offset == needle }
|
876
|
+
elsif id =~ /\A\d+\Z/
|
877
|
+
needle = id.to_i(10)
|
878
|
+
res = @pedump.resources.find{ |r| r.file_offset == needle }
|
879
|
+
elsif id['/']
|
880
|
+
type, name = id.split('/', 2)
|
881
|
+
res = @pedump.resources.find{ |r| r.type == type && r.name == name }
|
882
|
+
else
|
883
|
+
@pedump.logger.fatal "[!] invalid resource id #{id.inspect}"
|
884
|
+
exit(1)
|
885
|
+
end
|
886
|
+
unless res
|
887
|
+
@pedump.logger.fatal "[!] resource #{id.inspect} not found"
|
888
|
+
exit(1)
|
889
|
+
end
|
890
|
+
IO::copy_stream @pedump.io, $stdout, res.size, res.file_offset
|
891
|
+
end
|
892
|
+
|
893
|
+
def extract_section id
|
894
|
+
section = nil
|
895
|
+
if id['/']
|
896
|
+
a = id.split('/',2)
|
897
|
+
if a[0] == 'rva'
|
898
|
+
value = a[1].to_i(0) # auto hex/dec, but also can parse oct and bin
|
899
|
+
section = @pedump.sections.find{ |s| s.VirtualAddress == value }
|
900
|
+
elsif a[0] == 'raw'
|
901
|
+
value = a[1].to_i(0) # auto hex/dec, but also can parse oct and bin
|
902
|
+
section = @pedump.sections.find{ |s| s.PointerToRawData == value }
|
903
|
+
else
|
904
|
+
@pedump.logger.fatal "[!] invalid section id #{id.inspect}"
|
905
|
+
exit(1)
|
906
|
+
end
|
907
|
+
else
|
908
|
+
section = @pedump.sections.find{ |s| s.Name == id }
|
909
|
+
end
|
910
|
+
unless section
|
911
|
+
@pedump.logger.fatal "[!] section #{id.inspect} not found"
|
912
|
+
exit(1)
|
913
|
+
end
|
914
|
+
IO::copy_stream @pedump.io, $stdout, section.SizeOfRawData, section.PointerToRawData
|
915
|
+
end
|
916
|
+
|
815
917
|
end # class PEdump::CLI
|
@@ -10,15 +10,16 @@ class PEdump::Loader
|
|
10
10
|
@hdr = x.dup
|
11
11
|
end
|
12
12
|
@data = EMPTY_DATA.dup
|
13
|
+
@delta = args[:delta] || 0
|
13
14
|
@deferred_load_io = args[:deferred_load_io]
|
14
|
-
@deferred_load_pos = args[:deferred_load_pos] || (@hdr && @hdr.PointerToRawData)
|
15
|
+
@deferred_load_pos = args[:deferred_load_pos] || (@hdr && (@hdr.PointerToRawData - @delta))
|
15
16
|
@deferred_load_size = args[:deferred_load_size] || (@hdr && @hdr.SizeOfRawData)
|
16
17
|
@image_base = args[:image_base] || 0
|
17
18
|
end
|
18
19
|
|
19
20
|
def name; @hdr.Name; end
|
20
|
-
def va ; @hdr.VirtualAddress + @image_base; end
|
21
|
-
def rva ; @hdr.VirtualAddress; end
|
21
|
+
def va ; @hdr.VirtualAddress + @image_base - @delta; end
|
22
|
+
def rva ; @hdr.VirtualAddress - @delta; end
|
22
23
|
def vsize; @hdr.VirtualSize; end
|
23
24
|
def flags; @hdr.Characteristics; end
|
24
25
|
def flags= f; @hdr.Characteristics= f; end
|
@@ -51,6 +52,7 @@ class PEdump::Loader
|
|
51
52
|
@data.size > 0 ? @data.size.to_s(16) : (@deferred_load_io ? "<defer>" : 0)
|
52
53
|
]
|
53
54
|
r << (" dlpos=%8x" % @deferred_load_pos) if @deferred_load_pos
|
55
|
+
r << (" delta=%3x" % @delta) if @delta != 0
|
54
56
|
r << ">"
|
55
57
|
end
|
56
58
|
end
|
data/lib/pedump/loader.rb
CHANGED
@@ -15,7 +15,15 @@ class PEdump::Loader
|
|
15
15
|
|
16
16
|
# shortcuts
|
17
17
|
alias :pe :pe_hdr
|
18
|
-
|
18
|
+
|
19
|
+
def ep
|
20
|
+
if @pe_hdr
|
21
|
+
@pe_hdr.try(:ioh).try(:AddressOfEntryPoint)
|
22
|
+
elsif @te_hdr
|
23
|
+
@te_hdr.AddressOfEntryPoint - @delta
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
19
27
|
def ep= v; @pe_hdr.ioh.AddressOfEntryPoint=v; end
|
20
28
|
|
21
29
|
########################################################################
|
@@ -23,12 +31,24 @@ class PEdump::Loader
|
|
23
31
|
########################################################################
|
24
32
|
|
25
33
|
def initialize io = nil, params = {}
|
34
|
+
# @delta is for EFI TE loading, based on https://github.com/gdbinit/TELoader/blob/master/teloader.cpp
|
35
|
+
@delta = 0
|
26
36
|
@pedump = PEdump.new(io, params)
|
27
37
|
if io
|
28
38
|
@mz_hdr = @pedump.mz
|
29
39
|
@dos_stub = @pedump.dos_stub
|
30
40
|
@pe_hdr = @pedump.pe
|
31
|
-
@
|
41
|
+
@te_hdr = @pedump.te
|
42
|
+
|
43
|
+
@image_base = params[:image_base]
|
44
|
+
if @pe_hdr
|
45
|
+
@image_base ||= @pe_hdr.try(:ioh).try(:ImageBase)
|
46
|
+
elsif @te_hdr
|
47
|
+
@image_base ||= @te_hdr.ImageBase
|
48
|
+
@delta = @pedump.te_shift
|
49
|
+
end
|
50
|
+
@image_base ||= 0
|
51
|
+
|
32
52
|
load_sections @pedump.sections, io
|
33
53
|
end
|
34
54
|
@find_limit = params[:find_limit] || DEFAULT_FIND_LIMIT
|
@@ -38,7 +58,7 @@ class PEdump::Loader
|
|
38
58
|
if section_hdrs.is_a?(Array)
|
39
59
|
@sections = section_hdrs.map do |x|
|
40
60
|
raise "unknown section hdr: #{x.inspect}" unless x.is_a?(PEdump::IMAGE_SECTION_HEADER)
|
41
|
-
Section.new(x, :deferred_load_io => f, :image_base => @image_base )
|
61
|
+
Section.new(x, :deferred_load_io => f, :image_base => @image_base, :delta => @delta )
|
42
62
|
end
|
43
63
|
if f.respond_to?(:seek) && f.respond_to?(:read)
|
44
64
|
#
|
@@ -249,10 +269,12 @@ class PEdump::Loader
|
|
249
269
|
def names
|
250
270
|
return @names if @names
|
251
271
|
@names = {}
|
252
|
-
|
253
|
-
|
254
|
-
|
272
|
+
|
273
|
+
oep = ep()
|
274
|
+
if oep
|
275
|
+
@names[oep + @image_base] = 'start'
|
255
276
|
end
|
277
|
+
|
256
278
|
_parse_imports
|
257
279
|
_parse_exports
|
258
280
|
#TODO: debug info
|