1#!/usr/bin/env python3
2#
3# Author:
4#  Tamas Jos (@skelsec)
5#
6# TODO: implement this better, the ExceptionInformation definition is missing on msdn :(
7
8import io
9import enum
10from minidump.common_structs import *
11
12# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680368(v=vs.85).aspx
13class MINIDUMP_EXCEPTION_STREAM:
14	def __init__(self):
15		self.ThreadId = None
16		self.alignment = None
17		self.ExceptionRecord = None
18		self.ThreadContext = None
19
20	@staticmethod
21	def parse(buff):
22		mes = MINIDUMP_EXCEPTION_STREAM()
23		mes.ThreadId = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
24		mes.alignment = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
25		mes.ExceptionRecord = MINIDUMP_EXCEPTION.parse(buff)
26		mes.ThreadContext = MINIDUMP_LOCATION_DESCRIPTOR.parse(buff)
27		return mes
28
29	def __str__(self):
30		t  = '== MINIDUMP_EXCEPTION_STREAM ==\n'
31		t += 'ThreadId: %s\n' % self.ThreadId
32		# t += 'alignment: %s\n' % self.alignment
33		t += 'ExceptionRecord:\n %s\n' % str(self.ExceptionRecord)
34		t += 'ThreadContext: %s\n' % str(self.ThreadContext)
35		return t
36
37	@staticmethod
38	def get_header():
39		return [
40			'ThreadId',
41			*MINIDUMP_EXCEPTION.get_header()
42		]
43
44
45	def to_row(self):
46		return [
47			'0x%08x' % self.ThreadId,
48			*self.ExceptionRecord.to_row()
49		]
50
51
52class ExceptionCode(enum.Enum):
53	# Not a real exception code, it's just a placeholder to prevent the parser from raising an error
54	EXCEPTION_UNKNOWN               = 'EXCEPTION_UNKNOWN_CHECK_RAW'
55	EXCEPTION_NONE 					= 0x00
56
57	# Linux SIG values (for crashpad generated dumps)
58	EXCEPTION_SIGHUP    			= 0x00000001    # Hangup (POSIX)
59	EXCEPTION_SIGINT    			= 0x00000002    # Terminal interrupt (ANSI)
60	EXCEPTION_SIGQUIT   			= 0x00000003    # Terminal quit (POSIX)
61	EXCEPTION_SIGILL    			= 0x00000004    # Illegal instruction (ANSI)
62	EXCEPTION_SIGTRAP   			= 0x00000005    # Trace trap (POSIX)
63	EXCEPTION_SIGIOT    			= 0x00000006    # IOT Trap (4.2 BSD)
64	EXCEPTION_SIGBUS    			= 0x00000007    # BUS error (4.2 BSD)
65	EXCEPTION_SIGFPE    			= 0x00000008    # Floating point exception (ANSI)
66	EXCEPTION_SIGKILL   			= 0x00000009    # Kill(can't be caught or ignored) (POSIX)
67	EXCEPTION_SIGUSR1   			= 0x0000000A   # User defined signal 1 (POSIX)
68	EXCEPTION_SIGSEGV   			= 0x0000000B   # Invalid memory segment access (ANSI)
69	EXCEPTION_SIGUSR2   			= 0x0000000C   # User defined signal 2 (POSIX)
70	EXCEPTION_SIGPIPE   			= 0x0000000D   # Write on a pipe with no reader, Broken pipe (POSIX)
71	EXCEPTION_SIGALRM   			= 0x0000000E   # Alarm clock (POSIX)
72	EXCEPTION_SIGTERM   			= 0x0000000F   # Termination (ANSI)
73	EXCEPTION_SIGSTKFLT 			= 0x00000010   # Stack fault
74	EXCEPTION_SIGCHLD   			= 0x00000011   # Child process has stopped or exited, changed (POSIX)
75	EXCEPTION_SIGCONTV  			= 0x00000012   # Continue executing, if stopped (POSIX)
76	EXCEPTION_SIGSTOP   			= 0x00000013   # Stop executing(can't be caught or ignored) (POSIX)
77	EXCEPTION_SIGTSTP   			= 0x00000014   # Terminal stop signal (POSIX)
78	EXCEPTION_SIGTTIN   			= 0x00000015   # Background process trying to read, from TTY (POSIX)
79	EXCEPTION_SIGTTOU   			= 0x00000016   # Background process trying to write, to TTY (POSIX)
80	EXCEPTION_SIGURG    			= 0x00000017   # Urgent condition on socket (4.2 BSD)
81	EXCEPTION_SIGXCPU   			= 0x00000018   # CPU limit exceeded (4.2 BSD)
82	EXCEPTION_SIGXFSZ   			= 0x00000019   # File size limit exceeded (4.2 BSD)
83	EXCEPTION_SIGVTALRM 			= 0x0000001A   # Virtual alarm clock (4.2 BSD)
84	EXCEPTION_SIGPROF   			= 0x0000001B   # Profiling alarm clock (4.2 BSD)
85	EXCEPTION_SIGWINCH  			= 0x0000001C   # Window size change (4.3 BSD, Sun)
86	EXCEPTION_SIGIO     			= 0x0000001D   # I/O now possible (4.2 BSD)
87	EXCEPTION_SIGPWR    			= 0x0000001E   # Power failure restart (System V)
88
89	# Standard Windows exception values
90	EXCEPTION_ACCESS_VIOLATION 		= 0xC0000005 	# The thread tried to read from or write to a virtual address for which it does not have the appropriate access.
91	EXCEPTION_ARRAY_BOUNDS_EXCEEDED = 0xC000008C 	# The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking.
92	EXCEPTION_BREAKPOINT 			= 0x80000003 	# A breakpoint was encountered.
93	EXCEPTION_DATATYPE_MISALIGNMENT = 0x80000002 	# The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on.
94	EXCEPTION_FLT_DENORMAL_OPERAND 	= 0xC000008D 	# One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value.
95	EXCEPTION_FLT_DIVIDE_BY_ZERO 	= 0xC000008E	# The thread tried to divide a floating-point value by a floating-point divisor of zero.
96	EXCEPTION_FLT_INEXACT_RESULT 	= 0xC000008F	# The result of a floating-point operation cannot be represented exactly as a decimal fraction.
97	EXCEPTION_FLT_INVALID_OPERATION = 0xC0000090	# This exception represents any floating-point exception not included in this list.
98	EXCEPTION_FLT_OVERFLOW 			= 0xC0000091 	# The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type.
99	EXCEPTION_FLT_STACK_CHECK 		= 0xC0000092	# The stack overflowed or underflowed as the result of a floating-point operation.
100	EXCEPTION_FLT_UNDERFLOW 		= 0xC0000093	# The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type.
101	EXCEPTION_ILLEGAL_INSTRUCTION 	= 0xC000001D	# The thread tried to execute an invalid instruction.
102	EXCEPTION_IN_PAGE_ERROR 		= 0xC0000006	# The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network.
103	EXCEPTION_INT_DIVIDE_BY_ZERO 	= 0xC0000094	# The thread tried to divide an integer value by an integer divisor of zero.
104	EXCEPTION_INT_OVERFLOW 			= 0xC0000095	# The result of an integer operation caused a carry out of the most significant bit of the result.
105	EXCEPTION_INVALID_DISPOSITION 	= 0xC0000026	# An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception.
106	EXCEPTION_NONCONTINUABLE_EXCEPTION =0xC0000025  # The thread tried to continue execution after a noncontinuable exception occurred.
107	EXCEPTION_PRIV_INSTRUCTION 		= 0xC0000096	# The thread tried to execute an instruction whose operation is not allowed in the current machine mode.
108	EXCEPTION_SINGLE_STEP 			= 0x80000004	# A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
109	EXCEPTION_STACK_OVERFLOW 		= 0xC00000FD	# The thread used up its stack.
110
111#https://msdn.microsoft.com/en-us/library/windows/desktop/ms680367(v=vs.85).aspx
112class MINIDUMP_EXCEPTION:
113	def __init__(self):
114		self.ExceptionCode = None
115		self.ExceptionFlags = None
116		self.ExceptionRecord = None
117		self.ExceptionAddress = None
118		self.NumberParameters = None
119		self.__unusedAlignment = None
120		self.ExceptionInformation = []
121		self.ExceptionCode_raw = None
122
123	@staticmethod
124	def parse(buff):
125		me = MINIDUMP_EXCEPTION()
126		me.ExceptionCode_raw = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
127		try:
128			me.ExceptionCode = ExceptionCode(me.ExceptionCode_raw)
129		except:
130			me.ExceptionCode = ExceptionCode.EXCEPTION_UNKNOWN
131
132		me.ExceptionFlags = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
133		me.ExceptionRecord = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
134		me.ExceptionAddress = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
135		me.NumberParameters = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
136		me.__unusedAlignment = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
137		for _ in range(me.NumberParameters):
138			me.ExceptionInformation.append(int.from_bytes(buff.read(8), byteorder = 'little', signed = False))
139
140		return me
141
142	def __str__(self):
143		t  = '== MINIDUExceptionInformationMP_EXCEPTION ==\n'
144		t += "ExceptionCode : %s\n" % self.ExceptionCode
145		t += "ExceptionFlags : %s\n" % self.ExceptionFlags
146		t += "ExceptionRecord : %s\n" % self.ExceptionRecord
147		t += "ExceptionAddress : 0x%x\n" % self.ExceptionAddress
148		t += "NumberParameters : %s\n" % self.NumberParameters
149		# t += "__unusedAlignment : %s\n" % self.__unusedAlignment
150		t += "ExceptionInformation : %s\n" % ";".join("0x%x" % info for info in self.ExceptionInformation)
151		return t
152
153	@staticmethod
154	def get_header():
155		return [
156			'ExceptionCode',
157			'ExceptionFlags',
158			'ExceptionRecord',
159			'ExceptionAddress',
160			'ExceptionInformation'
161		]
162
163
164	def to_row(self):
165		return [
166			str(self.ExceptionCode),
167			'0x%08x' % self.ExceptionFlags,
168			'0x%08x' % self.ExceptionRecord,
169			'0x%08x' % self.ExceptionAddress,
170			str(self.ExceptionInformation)
171		]
172
173
174class ExceptionList:
175	def __init__(self):
176		self.exception_records = []
177
178	@staticmethod
179	def parse(dir, buff):
180		t = ExceptionList()
181
182		buff.seek(dir.Location.Rva)
183		chunk = io.BytesIO(buff.read(dir.Location.DataSize))
184
185		# Unfortunately, we don't have a certain way to figure out how many exception records
186		# there is in the stream, so we have to fallback on heuristics (EOF or bad data read)
187		#
188		# NB : 	some tool only read one exception record : https://github.com/GregTheDev/MinidumpExplorer/blob/a6dd974757c16142eefcfff7d99be10b14f87eaf/MinidumpExplorer/MinidumpExplorer/MainForm.cs#L257
189		#		but it's incorrect since we can have an exception chain (double fault, exception catched and re-raised, etc.)
190		while chunk.tell() < dir.Location.DataSize:
191			mes = MINIDUMP_EXCEPTION_STREAM.parse(chunk)
192
193			# a minidump exception stream is usally padded with zeroes
194			# so whenever we parse an exception record with the code EXCEPTION_NONE
195			# we can stop.
196			if mes.ExceptionRecord.ExceptionCode == ExceptionCode.EXCEPTION_NONE:
197				break
198
199			t.exception_records.append(mes)
200
201		return t
202
203	@staticmethod
204	async def aparse(dir, buff):
205		t = ExceptionList()
206
207		await buff.seek(dir.Location.Rva)
208		chunk_data = await buff.read(dir.Location.DataSize)
209		chunk = io.BytesIO(chunk_data)
210
211		# Unfortunately, we don't have a certain way to figure out how many exception records
212		# there is in the stream, so we have to fallback on heuristics (EOF or bad data read)
213		#
214		# NB : 	some tool only read one exception record : https://github.com/GregTheDev/MinidumpExplorer/blob/a6dd974757c16142eefcfff7d99be10b14f87eaf/MinidumpExplorer/MinidumpExplorer/MainForm.cs#L257
215		#		but it's incorrect since we can have an exception chain (double fault, exception catched and re-raised, etc.)
216		while chunk.tell() < dir.Location.DataSize:
217			mes = MINIDUMP_EXCEPTION_STREAM.parse(chunk)
218
219			# a minidump exception stream is usally padded with zeroes
220			# so whenever we parse an exception record with the code EXCEPTION_NONE
221			# we can stop.
222			if mes.ExceptionRecord.ExceptionCode == ExceptionCode.EXCEPTION_NONE:
223				break
224
225			t.exception_records.append(mes)
226
227		return t
228
229	def to_table(self):
230		t = []
231		t.append(MINIDUMP_EXCEPTION_STREAM.get_header())
232		for ex_record in self.exception_records:
233			t.append(ex_record.to_row())
234		return t
235
236	def __str__(self):
237		return '== ExceptionList ==\n' + construct_table(self.to_table())
238