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