pedump 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'iostruct'
4
+
5
+ class PEdump
6
+
7
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680378(v=vs.85).aspx
8
+ class MINIDUMP_HEADER < IOStruct.new 'a4LLLLLQ',
9
+ :Signature,
10
+ :Version,
11
+ :NumberOfStreams,
12
+ :StreamDirectoryRva,
13
+ :CheckSum,
14
+ :TimeDateStamp,
15
+ :Flags
16
+
17
+ def valid?
18
+ self.Signature == 'MDMP'
19
+ end
20
+ end
21
+
22
+ MINIDUMP_LOCATION_DESCRIPTOR = IOStruct.new 'LL', :DataSize, :Rva
23
+
24
+ class MINIDUMP_DIRECTORY < IOStruct.new 'L', :StreamType, :Location
25
+ def self.read io
26
+ r = super
27
+ r.Location = MINIDUMP_LOCATION_DESCRIPTOR.read(io)
28
+ r
29
+ end
30
+ end
31
+
32
+ MINIDUMP_MEMORY_INFO = IOStruct.new 'QQLLQLLLL',
33
+ :BaseAddress,
34
+ :AllocationBase,
35
+ :AllocationProtect,
36
+ :__alignment1,
37
+ :RegionSize,
38
+ :State,
39
+ :Protect,
40
+ :Type,
41
+ :__alignment2
42
+
43
+ class MINIDUMP_MEMORY_INFO_LIST < IOStruct.new 'LLQ',
44
+ :SizeOfHeader,
45
+ :SizeOfEntry,
46
+ :NumberOfEntries,
47
+ :entries
48
+
49
+ def self.read io
50
+ r = super
51
+ r.entries = r.NumberOfEntries.times.map{ MINIDUMP_MEMORY_INFO.read(io) }
52
+ r
53
+ end
54
+ end
55
+
56
+ MINIDUMP_MEMORY_DESCRIPTOR = IOStruct.new 'QLL',
57
+ :StartOfMemoryRange,
58
+ :DataSize,
59
+ :Rva
60
+
61
+ class MINIDUMP_MEMORY_LIST < IOStruct.new 'L',
62
+ :NumberOfMemoryRanges,
63
+ :MemoryRanges
64
+
65
+ def self.read io
66
+ r = super
67
+ r.MemoryRanges = r.NumberOfMemoryRanges.times.map{ MINIDUMP_MEMORY_DESCRIPTOR.read(io) }
68
+ r
69
+ end
70
+
71
+ def entries; self.MemoryRanges; end
72
+ end
73
+
74
+ MINIDUMP_MEMORY_DESCRIPTOR64 = IOStruct.new 'QQ',
75
+ :StartOfMemoryRange,
76
+ :DataSize
77
+
78
+ class MINIDUMP_MEMORY64_LIST < IOStruct.new 'QQ',
79
+ :NumberOfMemoryRanges,
80
+ :BaseRva,
81
+ :MemoryRanges
82
+
83
+ def self.read io
84
+ r = super
85
+ r.MemoryRanges = r.NumberOfMemoryRanges.times.map{ MINIDUMP_MEMORY_DESCRIPTOR64.read(io) }
86
+ r
87
+ end
88
+
89
+ def entries; self.MemoryRanges; end
90
+ end
91
+
92
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680394(v=vs.85).aspx
93
+ MINIDUMP_STREAM_TYPE = {
94
+ 0 => :UnusedStream,
95
+ 1 => :ReservedStream0,
96
+ 2 => :ReservedStream1,
97
+ 3 => :ThreadListStream,
98
+ 4 => :ModuleListStream,
99
+ 5 => :MemoryListStream, # MINIDUMP_MEMORY_LIST
100
+ 6 => :ExceptionStream,
101
+ 7 => :SystemInfoStream,
102
+ 8 => :ThreadExListStream,
103
+ 9 => :Memory64ListStream, # MINIDUMP_MEMORY64_LIST
104
+ 10 => :CommentStreamA,
105
+ 11 => :CommentStreamW,
106
+ 12 => :HandleDataStream,
107
+ 13 => :FunctionTableStream,
108
+ 14 => :UnloadedModuleListStream,
109
+ 15 => :MiscInfoStream,
110
+ 16 => :MemoryInfoListStream, # MINIDUMP_MEMORY_INFO_LIST
111
+ 17 => :ThreadInfoListStream,
112
+ 18 => :HandleOperationListStream,
113
+ 0xffff => :LastReservedStream,
114
+
115
+ # Special types saved by google breakpad
116
+ # https://chromium.googlesource.com/breakpad/breakpad/+/846b6335c5b0ba46dfa2ed96fccfa3f7a02fa2f1/src/google_breakpad/common/minidump_format.h#311
117
+ 0x47670001 => :BreakpadInfoStream,
118
+ 0x47670002 => :BreakpadAssertionInfoStream,
119
+ 0x47670003 => :BreakpadLinuxCpuInfo,
120
+ 0x47670004 => :BreakpadLinuxProcStatus,
121
+ 0x47670005 => :BreakpadLinuxLsbRelease,
122
+ 0x47670006 => :BreakpadLinuxCmdLine,
123
+ 0x47670007 => :BreakpadLinuxEnviron,
124
+ 0x47670008 => :BreakpadLinuxAuxv,
125
+ 0x47670009 => :BreakpadLinuxMaps,
126
+ 0x4767000A => :BreakpadLinuxDsoDebug,
127
+
128
+ # Saved by crashpad
129
+ # https://chromium.googlesource.com/crashpad/crashpad/+/doc/minidump/minidump_extensions.h#95
130
+ 0x43500001 => :CrashpadInfo,
131
+
132
+ # Saved by Syzyasan
133
+ # https://github.com/google/syzygy/blob/c8bb4927f07fec0de8834c4774ddaafef0bc099f/syzygy/kasko/api/client.h#L28
134
+ # https://github.com/google/syzygy/blob/master/syzygy/crashdata/crashdata.proto
135
+ 0x4B6B0001 => :SyzyasanCrashdata,
136
+
137
+ # Saved by Chromium
138
+ 0x4B6B0002 => :ChromiumStabilityReport,
139
+ 0x4B6B0003 => :ChromiumSystemProfile,
140
+ 0x4B6B0004 => :ChromiumGwpAsanData,
141
+ }
142
+
143
+ class Loader
144
+ class Minidump
145
+ attr_accessor :hdr, :streams, :io
146
+
147
+ def initialize io
148
+ @io = io
149
+ @hdr = MINIDUMP_HEADER.read(@io)
150
+ raise "invalid minidump" unless @hdr.valid?
151
+ end
152
+
153
+ def streams
154
+ @streams ||=
155
+ begin
156
+ @io.seek(@hdr.StreamDirectoryRva)
157
+ @hdr.NumberOfStreams.times.map do
158
+ dir = MINIDUMP_DIRECTORY.read(io)
159
+ dir.Location.empty? ? nil : dir
160
+ end.compact
161
+ end
162
+ end
163
+
164
+ def stream_by_name(name)
165
+ type = MINIDUMP_STREAM_TYPE.invert[name]
166
+ raise "Unknown type symbol #{name}!" if !type
167
+
168
+ streams.find { |s| s.StreamType == type }
169
+ end
170
+
171
+ def memory_info_list
172
+ # MINIDUMP_MEMORY_INFO_LIST
173
+ stream = stream_by_name(:MemoryInfoListStream)
174
+ return nil unless stream
175
+ io.seek stream.Location.Rva
176
+ MINIDUMP_MEMORY_INFO_LIST.read io
177
+ end
178
+
179
+ def memory_list
180
+ # MINIDUMP_MEMORY_LIST
181
+ stream = stream_by_name(:MemoryListStream)
182
+ return nil unless stream
183
+ io.seek stream.Location.Rva
184
+ MINIDUMP_MEMORY_LIST.read io
185
+ end
186
+
187
+ def memory64_list
188
+ # MINIDUMP_MEMORY64_LIST
189
+ stream = stream_by_name(:Memory64ListStream)
190
+ return nil unless stream
191
+ io.seek stream.Location.Rva
192
+ MINIDUMP_MEMORY64_LIST.read io
193
+ end
194
+
195
+ MemoryRange = Struct.new :file_offset, :va, :size
196
+
197
+ # set options[:merge] = true to merge adjacent memory ranges
198
+ def memory_ranges options = {}
199
+ if memory64_list
200
+ ml = memory64_list
201
+ file_offset = ml.BaseRva
202
+ r = []
203
+ if options[:merge]
204
+ ml.entries.each do |x|
205
+ if r.last && r.last.va + r.last.size == x.StartOfMemoryRange
206
+ # if section VA == prev_section.VA + prev_section.SIZE
207
+ # then just increase the size of previous section
208
+ r.last.size += x.DataSize
209
+ else
210
+ r << MemoryRange.new( file_offset, x.StartOfMemoryRange, x.DataSize )
211
+ end
212
+ file_offset += x.DataSize
213
+ end
214
+ else
215
+ ml.entries.each do |x|
216
+ r << MemoryRange.new( file_offset, x.StartOfMemoryRange, x.DataSize )
217
+ file_offset += x.DataSize
218
+ end
219
+ end
220
+ return r
221
+ elsif memory_list
222
+ ml = memory_list
223
+ r = []
224
+ if options[:merge]
225
+ ml.entries.each do |x|
226
+ if r.last && r.last.va + r.last.size == x.StartOfMemoryRange
227
+ # if section VA == prev_section.VA + prev_section.SIZE
228
+ # then just increase the size of previous section
229
+ r.last.size += x.DataSize
230
+ else
231
+ r << MemoryRange.new( x.Rva, x.StartOfMemoryRange, x.DataSize )
232
+ end
233
+ end
234
+ else
235
+ ml.entries.each do |x|
236
+ r << MemoryRange.new( x.Rva, x.StartOfMemoryRange, x.DataSize )
237
+ end
238
+ end
239
+ return r
240
+ else
241
+ raise "Could not find memory ranges"
242
+ end
243
+ end
244
+
245
+ end # class Minidump
246
+ end # class Loader
247
+ end # module PEdump
248
+
249
+ ##############################################
250
+
251
+ if $0 == __FILE__
252
+ require 'pp'
253
+ require 'optparse'
254
+
255
+ options = {}
256
+ opt_parse = OptionParser.new do |opts|
257
+ opts.banner = "Usage: #{$0} [options] <minidump>"
258
+
259
+ opts.on("--all", "Print all of the following sections") do
260
+ options[:all] = true
261
+ end
262
+ opts.on("--header", "Print minidump header") do
263
+ options[:header] = true
264
+ end
265
+ opts.on("--streams", "Print out the streams present") do
266
+ options[:streams] = true
267
+ end
268
+ opts.on("--memory-ranges", "Print out memory ranges included in the minidump") do
269
+ options[:memory_ranges] = true
270
+ end
271
+ opts.on("--breakpad", "Print out breakpad text sections if present") do
272
+ options[:breakpad] = true
273
+ end
274
+ opts.separator ''
275
+
276
+ opts.on("--memory <address>", "Print the memory range beginning at address") do |m|
277
+ options[:memory] = m.hex
278
+ end
279
+ opts.separator ''
280
+
281
+ opts.on("-h", "--help", "Help") do
282
+ puts opts
283
+ exit 0
284
+ end
285
+ end
286
+
287
+ opt_parse.parse!
288
+
289
+ if ARGV.empty?
290
+ $stderr.puts opt_parse.help
291
+ exit 1
292
+ end
293
+
294
+ io = open(ARGV.first, "rb")
295
+ md = PEdump::Loader::Minidump.new io
296
+
297
+ if options[:all] || options[:header]
298
+ pp md.hdr
299
+ puts
300
+ end
301
+
302
+ if options[:all] || options[:streams]
303
+ puts "[.] Streams present in the minidump:"
304
+ md.streams.each do |s|
305
+ if PEdump::MINIDUMP_STREAM_TYPE[s.StreamType]
306
+ puts "[.] #{PEdump::MINIDUMP_STREAM_TYPE[s.StreamType]}"
307
+ else
308
+ puts "[.] Unknown stream type #{s.StreamType}"
309
+ end
310
+ end
311
+ puts
312
+ end
313
+
314
+ if options[:all] || options[:breakpad]
315
+ [ :BreakpadLinuxCpuInfo, :BreakpadLinuxProcStatus, :BreakpadLinuxMaps,
316
+ :BreakpadLinuxCmdLine, :BreakpadLinuxEnviron ].each { |name|
317
+ stream = md.stream_by_name(name)
318
+ next if !stream
319
+
320
+ io.seek stream.Location.Rva
321
+ contents = io.read(stream.Location.DataSize)
322
+
323
+ if contents !~ /[^[:print:][:space:]]/
324
+ puts "[.] Section #{name}:"
325
+ puts contents
326
+ else
327
+ puts "[.] Section #{name}: #{contents.inspect}"
328
+ end
329
+ puts
330
+ }
331
+ end
332
+
333
+ if options[:all] || options[:memory_ranges]
334
+ puts "[.] #{md.memory_ranges.size} memory ranges"
335
+ puts "[.] #{md.memory_ranges(:merge => true).size} merged memory ranges"
336
+ puts
337
+
338
+ printf "[.] %16s %8s\n", "addr", "size"
339
+ md.memory_ranges(:merge => true).sort_by { |mr| mr.va }.each do |mr|
340
+ printf "[.] %16x %8x\n", mr.va, mr.size
341
+ end
342
+ end
343
+
344
+ if options[:memory]
345
+ mr = md.memory_ranges(:merge => true).find { |r| r.va == options[:memory] }
346
+ raise "Could not find the specified region" if !mr
347
+
348
+ io.seek(mr.file_offset)
349
+ print io.read(mr.size)
350
+ end
351
+ end
@@ -0,0 +1,57 @@
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
+ @image_base = args[:image_base] || 0
17
+ end
18
+
19
+ def name; @hdr.Name; end
20
+ def va ; @hdr.VirtualAddress + @image_base; end
21
+ def rva ; @hdr.VirtualAddress; end
22
+ def vsize; @hdr.VirtualSize; end
23
+ def flags; @hdr.Characteristics; end
24
+ def flags= f; @hdr.Characteristics= f; end
25
+
26
+ def data
27
+ if @data.empty? && @deferred_load_io && @deferred_load_pos && @deferred_load_size.to_i > 0
28
+ begin
29
+ old_pos = @deferred_load_io.tell
30
+ @deferred_load_io.seek @deferred_load_pos
31
+ @data = @deferred_load_io.binmode.read(@deferred_load_size) || EMPTY_DATA.dup
32
+ ensure
33
+ if @deferred_load_io && old_pos
34
+ @deferred_load_io.seek old_pos
35
+ @deferred_load_io = nil # prevent read only on 1st access to data
36
+ end
37
+ end
38
+ end
39
+ @data
40
+ end
41
+
42
+ def range
43
+ va...(va+vsize)
44
+ end
45
+
46
+ def inspect
47
+ r = "#<Section"
48
+ r << (" name=%-10s" % name.inspect) if name
49
+ r << " va=%8x vsize=%8x rawsize=%8s" % [
50
+ va, vsize,
51
+ @data.size > 0 ? @data.size.to_s(16) : (@deferred_load_io ? "<defer>" : 0)
52
+ ]
53
+ r << (" dlpos=%8x" % @deferred_load_pos) if @deferred_load_pos
54
+ r << ">"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,67 @@
1
+ require 'awesome_print' # for colored tty logging
2
+
3
+ class PEdump
4
+ class Logger < ::Logger
5
+ def initialize *args
6
+ super
7
+ @formatter = proc do |severity,_,_,msg|
8
+ # quick and dirty way to remove duplicate messages
9
+ if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
10
+ ''
11
+ else
12
+ @prevmsg = msg
13
+ "#{msg}\n"
14
+ end
15
+ end
16
+ @level = WARN
17
+ end
18
+ end
19
+
20
+ def Logger.create params
21
+ logger =
22
+ if params[:logger]
23
+ params[:logger]
24
+ else
25
+ logdev = params[:logdev] || STDERR
26
+ logger_class =
27
+ if params.key?(:color)
28
+ # forced color or not
29
+ params[:color] ? ColoredLogger : Logger
30
+ else
31
+ # set color if logdev is TTY
32
+ (logdev.respond_to?(:tty?) && logdev.tty?) ? ColoredLogger : Logger
33
+ end
34
+ logger_class.new(logdev)
35
+ end
36
+
37
+ logger.level = params[:log_level] if params[:log_level]
38
+ logger
39
+ end
40
+
41
+ class ColoredLogger < ::Logger
42
+ def initialize *args
43
+ super
44
+ @formatter = proc do |severity,_,_,msg|
45
+ # quick and dirty way to remove duplicate messages
46
+ if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
47
+ ''
48
+ else
49
+ @prevmsg = msg
50
+ color =
51
+ case severity
52
+ when 'FATAL'
53
+ :redish
54
+ when 'ERROR'
55
+ :red
56
+ when 'WARN'
57
+ :yellowish
58
+ when 'DEBUG'
59
+ :gray
60
+ end
61
+ "#{color ? msg.send(color) : msg}\n"
62
+ end
63
+ end
64
+ @level = WARN
65
+ end
66
+ end
67
+ end