def foGetAllocationInformationForProcessAndAddress( oProcess, uAllocationInformationStartAddress): # DPH_HEAP_BLOCK structures are stored sequentially in a virtual allocation. oAllocationInformationVirtualAllocation = cVirtualAllocation( oProcess.uId, uAllocationInformationStartAddress) assert oAllocationInformationVirtualAllocation, \ "Cannot find virtual allocation for page heap allocation information at 0x%X" % uAllocationInformationStartAddress if gbDebugOutput: print(",- oAllocationInformationVirtualAllocation ").ljust(80, "-") for sLine in oAllocationInformationVirtualAllocation.fasDump(): print "| %s" % sLine print "`".ljust(80, "-") if not oAllocationInformationVirtualAllocation.bAllocated: return None # Read the page heap allocation information DPH_HEAP_BLOCK = { 4: DPH_HEAP_BLOCK_32, 8: DPH_HEAP_BLOCK_64 }[oProcess.uPointerSize] oAllocationInformation = oAllocationInformationVirtualAllocation.foReadStructureForOffset( cStructure=DPH_HEAP_BLOCK, uOffset=uAllocationInformationStartAddress - oAllocationInformationVirtualAllocation.uStartAddress, ) if gbDebugOutput: print(",- oAllocationInformation ").ljust(80, "-") for sLine in oAllocationInformation.fasDump(): print "| %s" % sLine print "`".ljust(80, "-") return oAllocationInformation
def foGetVirtualAllocationForProcessAndAddress(oProcess, uAddressInVirtualAllocation): oVirtualAllocation = cVirtualAllocation(oProcess.uId, uAddressInVirtualAllocation) assert oVirtualAllocation, \ "Cannot find virtual allocation for page heap block allocation at 0x%X" % uAddressInVirtualAllocation if gbDebugOutput: print(",- oVirtualAllocation ").ljust(80, "-") for sLine in oVirtualAllocation.fasDump(): print "| %s" % sLine print "`".ljust(80, "-") return oVirtualAllocation
def fUpdateReportForProcessThreadTypeIdAndAddress(oCdbWrapper, oBugReport, oProcess, oThread, sViolationTypeId, uAccessViolationAddress): sViolationTypeDescription = { "R": "reading", "W": "writing", "E": "executing" }.get(sViolationTypeId, "accessing") oVirtualAllocation = cVirtualAllocation(oProcess.uId, uAccessViolationAddress) # Handling this AV as the appropriate type of pointer: for fbUpdateReportForSpecificPointerType in [ fbUpdateReportForNULLPointer, fbUpdateReportForCollateralPoisonPointer, fbUpdateReportForHeapManagerPointer, fbUpdateReportForStackPointer, fbUpdateReportForSpecialPointer, fbUpdateReportForAllocatedPointer, fbUpdateReportForReservedPointer, fbUpdateReportForUnallocatedPointer, fbUpdateReportForInvalidPointer, ]: if fbUpdateReportForSpecificPointerType(oCdbWrapper, oBugReport, oProcess, oThread, sViolationTypeId, uAccessViolationAddress, sViolationTypeDescription, oVirtualAllocation): # We handled the pointer as this type, and it can be only one type, so stop trying other types: return else: # This pointer is not of a known type: should never happend raise AssertionError("Could not handle AV%s@0x%08X" % (sViolationTypeId, uAccessViolationAddress))
def cBugReport_foAnalyzeException_STATUS_ACCESS_VIOLATION( oBugReport, oProcess, oException): oCdbWrapper = oProcess.oCdbWrapper # Parameter[0] = access type (0 = read, 1 = write, 8 = execute) # Parameter[1] = address assert len(oException.auParameters) == 2, \ "Unexpected number of access violation exception parameters (%d vs 2)" % len(oException.auParameters) # Access violation: add the type of operation and the location to the exception id. sViolationTypeId = { 0: "R", 1: "W", 8: "E" }.get(oException.auParameters[0], "_") sViolationTypeDescription = { 0: "reading", 1: "writing", 8: "executing" }.get(oException.auParameters[0], "accessing") sViolationTypeNotes = sViolationTypeId == "_" and " (the type-of-accesss code was 0x%X)" % oException.auParameters[ 0] or "" uAccessViolationAddress = oException.auParameters[1] if uAccessViolationAddress == 0xFFFFFFFFFFFFFFFF and sViolationTypeId == "R": # In x64 mode, current processors will thrown an exception when you use an address larger than 0x7FFFFFFFFFFF and # smaller than 0xFFFF800000000000. In such cases cdb reports incorrect information in the exception parameters, # e.g. the address is always reported as 0xFFFFFFFFFFFFFFFF and the access type is always "read". # A partial work-around is to get the address from the last instruction output, which can be retrieved by asking # cdb to output disassembly and address after each command. This may also tell us if the access type was "execute". oProcess.fasExecuteCdbCommand( sCommand=".prompt_allow +dis +ea;", sComment="Enable disassembly and address in cdb prompt", ) # Do this twice in case the first time requires loading symbols, which can output junk that makes parsing ouput difficult. oProcess.fasExecuteCdbCommand( \ sCommand = "~s;", sComment = "Show disassembly and optional symbol loading stuff", ) asLastInstructionAndAddress = oProcess.fasExecuteCdbCommand( sCommand="~s;", sComment="Show disassembly", bOutputIsInformative=True, ) # Revert to not showing disassembly and address: oProcess.fasExecuteCdbCommand( \ sCommand = ".prompt_allow -dis -ea;", sComment = "Revert to clean cdb prompt", ) # Sample output: # |00007ffd`420b213e 488b14c2 mov rdx,qword ptr [rdx+rax*8] ds:00007df5`ffb60000=???????????????? # or # |60053594 ff7008 push dword ptr [eax+8] ds:002b:00000008=???????? # or # |00007ff6`e7ab1204 ffe1 jmp rcx {c0c0c0c0`c0c0c0c0} # or # |00000000`7fffffff ?? ??? # or # |00007ff9`b6f1a904 488b8d500d0000 mov rcx,qword ptr [rbp+0D50h] ss:00000244`4124f590=0000024441210240 assert len(asLastInstructionAndAddress) == 1, \ "Unexpected last instruction output:\r\n%r" % "\r\n".join(asLastInstructionAndAddress) oEIPOutsideAllocatedMemoryMatch = re.match( "^%s$" % "".join([ r"([0-9`a-f]+)", r"\s+", r"\?\?", r"\s+", r"\?\?\?" # address spaces "??" spaces "???" ]), asLastInstructionAndAddress[0]) if oEIPOutsideAllocatedMemoryMatch: sAddress = oEIPOutsideAllocatedMemoryMatch.group(1) sViolationTypeId = "E" sViolationTypeDescription = "executing" else: oLastInstructionMatch = re.match( "^%s$" % "".join([ r"[0-9`a-f]+", r"\s+", # address spaces r"[0-9`a-f]+", r"\s+", # opcode spaces r"\w+", r"\s+", # instruction spaces r"(?:", # either{ r"([^\[,]+,.+)", # (destination operand that does not reference memory "," source operand ) r"|", # }or{ ".*" # any other combination of operands r")", # } r"(?:", # either{ r"\ws:", # segment register ":" r"(?:[0-9a-f`]{4}:)?", # optional { segment value ":" } r"([0-9a-f`]+)", # (sAddress1) r"=" r"(?:", # "=" either { r"\?+", # "???????" <cannot be read> "|", # } or { r"([0-9`a-f]+)", # (sValue) <memory at address can be read> "|", # } or { r"\{", # "{" r".+", # symbol r"\s+", # spaces r"\(([0-9`a-f]+)\)", # "(" (sAddress2) ")" r"\}", # "}" ")", r"|", # }or{ r"\{([0-9`a-f]+)\}", # "{" (sAddress3) "}" r")", # } ]), asLastInstructionAndAddress[0]) assert oLastInstructionMatch, \ "Unexpected last instruction output:\r\n%s" % "\r\n".join(asLastInstructionAndAddress) sDestinationOperandThatDoesNotReferenceMemory, sAddress1, sValue, sAddress2, sAddress3 = oLastInstructionMatch.groups( ) sAddress = sAddress1 or sAddress2 or sAddress3 if sAddress1: if sDestinationOperandThatDoesNotReferenceMemory: # The destination operand does not reference memory, so this must be a read AV sViolationTypeId = "R" sViolationTypeDescription = "reading" elif sAddress1 and sValue: # The adress referenced can be read, so it must be write AV sViolationTypeId = "W" sViolationTypeDescription = "writing" else: sViolationTypeId = "_" sViolationTypeDescription = "accessing" sViolationTypeNotes = " (the type of accesss must be read or write, but cannot be determined)" else: sViolationTypeId = "E" sViolationTypeDescription = "executing" uAccessViolationAddress = long(sAddress.replace("`", ""), 16) oBugReport.atxMemoryRemarks.append( ("Access violation", uAccessViolationAddress, None)) # TODO Find out size of access if sViolationTypeId == "E": # Hide the top stack frame if it is for the address at which the execute access violation happened: if oBugReport and oBugReport.oStack and oBugReport.oStack.aoFrames \ and oBugReport.oStack.aoFrames[0].uInstructionPointer == uAccessViolationAddress: oBugReport.oStack.aoFrames[0].sIsHiddenBecause = "called address" oVirtualAllocation = cVirtualAllocation(oProcess.uId, uAccessViolationAddress) # Try handling this as a NULL pointer, which is special cased because we do not always report these. if fbAccessViolationIsNULLPointer(oCdbWrapper, oBugReport, oProcess, sViolationTypeId, uAccessViolationAddress, sViolationTypeDescription, oVirtualAllocation): if dxConfig[ "bIgnoreFirstChanceNULLPointerAccessViolations"] and not oException.bApplicationCannotHandleException: # This is a first chance exception; let the application handle it first. return None else: # Try various ways of handling this AV: for fbAccessViolationHandled in [ fbAccessViolationIsCollateralPoisonPointer, fbAccessViolationIsHeapManagerPointer, fbAccessViolationIsStackPointer, fbAccessViolationIsSpecialPointer, fbAccessViolationIsAllocatedPointer, fbAccessViolationIsReservedPointer, fbAccessViolationIsFreePointer, fbAccessViolationIsInvalidPointer, ]: if fbAccessViolationHandled(oCdbWrapper, oBugReport, oProcess, sViolationTypeId, uAccessViolationAddress, sViolationTypeDescription, oVirtualAllocation): # Once it's handled, stop trying other handlers break else: # Unable to handle (should not be possible!) raise AssertionError("Could not handle AV%s@0x%08X" % (sViolationTypeId, uAccessViolationAddress)) oBugReport.sBugDescription += sViolationTypeNotes return oBugReport
def foCreate(oProcess, uStowedExceptionInformationAddress): # Read STOWED_EXCEPTION_INFORMATION_V1 or STOWED_EXCEPTION_INFORMATION_V2 structure. # (See https://msdn.microsoft.com/en-us/library/windows/desktop/dn600343(v=vs.85).aspx) # Both start with a STOWED_EXCEPTION_INFORMATION_HEADER structure. # (See https://msdn.microsoft.com/en-us/library/windows/desktop/dn600342(v=vs.85).aspx) # STOWED_EXCEPTION_INFORMATION_HEADER = { # ULONG Size # ULONG Signature // "SE01" (0x53453031), "SE02" (0x53453032) # } oStowedExceptionInformationHeader = oProcess.foReadStructureForAddress( cStructure=STOWED_EXCEPTION_INFORMATION_HEADER, uAddress=uStowedExceptionInformationAddress, ) if oStowedExceptionInformationHeader.Signature == STOWED_EXCEPTION_INFORMATION_V1_SIGNATURE: cStowedExceptionInformation = { "x86": STOWED_EXCEPTION_INFORMATION_V1_32, "x64": STOWED_EXCEPTION_INFORMATION_V1_64, }[oProcess.sISA] else: assert oStowedExceptionInformationHeader.Signature == STOWED_EXCEPTION_INFORMATION_V2_SIGNATURE, \ "Unexpected stowed exception signature 0x%X (expected 0x%X or 0x%X)" % \ (oStowedExceptionInformationHeader.Signature, STOWED_EXCEPTION_INFORMATION_V1_SIGNATURE, \ STOWED_EXCEPTION_INFORMATION_V2_SIGNATURE) cStowedExceptionInformation = { "x86": STOWED_EXCEPTION_INFORMATION_V2_32, "x64": STOWED_EXCEPTION_INFORMATION_V2_64, }[oProcess.sISA] assert oStowedExceptionInformationHeader.Size == SIZEOF(cStowedExceptionInformation), \ "STOWED_EXCEPTION_INFORMATION structure is 0x%X bytes, but 0x%X was expected!?" % \ (oStowedExceptionInformationHeader.Size, SIZEOF(cStowedExceptionInformation)) oStowedExceptionInformation = oProcess.foReadStructureForAddress( cStructure=cStowedExceptionInformation, uAddress=uStowedExceptionInformationAddress, ) uExceptionForm = oStowedExceptionInformation.ExceptionForm_ThreadId & 3 uThreadId = (oStowedExceptionInformation.ExceptionForm_ThreadId & 0xfffffffc) << 2 # Handle structure sNestedExceptionTypeId = None oNestedException = None sWRTLanguageExceptionIUnkownClassName = None if (oStowedExceptionInformationHeader.Signature == STOWED_EXCEPTION_INFORMATION_V2_SIGNATURE and oStowedExceptionInformation.NestedExceptionType != STOWED_EXCEPTION_NESTED_TYPE_NONE): uNestedExceptionAddress = POINTER_VALUE( oStowedExceptionInformation.NestedException) if oStowedExceptionInformation.NestedExceptionType == STOWED_EXCEPTION_NESTED_TYPE_WIN32: sNestedExceptionTypeId = "Win32" oNestedException = cException.foCreateFromMemory( oProcess=oProcess, uExceptionRecordAddress=uNestedExceptionAddress, ) elif oStowedExceptionInformation.NestedExceptionType == STOWED_EXCEPTION_NESTED_TYPE_STOWED: sNestedExceptionTypeId = "Stowed" oNestedException = cStowedException.foCreate( oProcess=oProcess, uStowedExceptionAddress=uNestedExceptionAddress, ) elif oStowedExceptionInformation.NestedExceptionType == STOWED_EXCEPTION_NESTED_TYPE_CLR: sNestedExceptionTypeId = "CLR" # TODO: find out how to trigger these so I can find out how to handle them. elif oStowedExceptionInformation.NestedExceptionType == STOWED_EXCEPTION_NESTED_TYPE_LEO: sNestedExceptionTypeId = "WRTLanguage" # These can be triggered using RoOriginateLanguageException. The "NestedException" contains a pointer to an # object that implements IUnknown. Apparently this object "contains all the information necessary recreate it # the exception a later point." (https://msdn.microsoft.com/en-us/library/dn302172(v=vs.85).aspx) # I have not been able to find more documentation for this, so this is based on reverse engineering. sWRTLanguageExceptionIUnkownClassName = fsGetCPPObjectClassNameFromVFTable( oProcess=oProcess, uCPPObjectAddress=uNestedExceptionAddress, ) # elif oStowedExceptionInformation.NestedExceptionType == STOWED_EXCEPTION_NESTED_TYPE_LMAX: else: oDataVirtualAllocation = cVirtualAllocation( oProcess.uId, uNestedExceptionAddress) uDataOffset = uNestedExceptionAddress - oDataVirtualAllocation.uStartAddress uDataSize = min(0x80, oDataVirtualAllocation.uSize - uDataOffset) sData = ",".join([ "%02X" % uByte for uByte in oDataVirtualAllocation. fauReadBytesForOffsetAndSize(uDataOffset, uDataSize) ]) sNestedExceptionTypeId = "Type=0x%08X,Data@0x%08X:[%s]" % \ (oStowedExceptionInformation.NestedExceptionType, uNestedExceptionAddress, sData) # Handle the two different forms: if uExceptionForm == 1: oStowedException = cStowedException( iCode=oStowedExceptionInformation.ResultCode, uAddress=oStowedExceptionInformation.ExceptionAddress, pStackTrace=oStowedExceptionInformation.StackTrace, uStackTraceSize=oStowedExceptionInformation.StackTraceWords * oStowedExceptionInformation.StackTraceWordSize, sNestedExceptionTypeId=sNestedExceptionTypeId, oNestedException=oNestedException, sWRTLanguageExceptionIUnkownClassName= sWRTLanguageExceptionIUnkownClassName, ) else: assert uExceptionForm == 2, \ "Unexpected exception form %d" % uExceptionForm sErrorText = oProcess.fsReadNullTerminatedStringForAddress( uAddress=oStowedExceptionInformation.ErrorText, bUnicode=True, ) oStowedException = cStowedException( iCode=oStowedExceptionInformation.ResultCode, sErrorText=sErrorText, sNestedExceptionTypeId=sNestedExceptionTypeId, oNestedException=oNestedException, sWRTLanguageExceptionIUnkownClassName= sWRTLanguageExceptionIUnkownClassName, ) return oStowedException
def cBugReport_foAnalyzeException_Cpp(oBugReport, oProcess, oThread, oException): # Based on https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273/ # Attempt to get the symbol of the virtual function table of the object that was thrown and add that the the type id: if oProcess.sISA == "x64": assert len(oException.auParameters) == 4, \ "Expected a C++ Exception to have 4 parameters, got %d" % len(oException.auParameters) # On 64-bit systems, the exception information uses 32-bit offsets from a 64-bit base address. uBaseAddress = oException.auParameters[3] else: assert len(oException.auParameters) == 3, \ "Expected a C++ Exception to have 3 parameters, got %d" % len(oException.auParameters) # On 32-bit systems, the exception information uses 32-bit addresses (== offsets from 0). uBaseAddress = 0 # +-------+ # | DW/QW | uUnknown1 # +-------+ # | DW/QW | uExceptionObjectAddress* (may not be actual address) # +-------+ # | DW/QW | uExceptionObjectDescriptionAddress # +-------+ | # | DW/QW | uBaseAddress* | (optional, 64-bit only!) # +-------+ | | # | ,----- -' # | V # | +----+ EXCEPTION_OBJECT_DESCRIPTION_1 # | | DW | uUnknown1 # | +----+ # | | DW | uUnknown2 # | +----+ # | | DW | uUnknown3 # | +----+ # | | DW | uOffsetOfPart2 # | +----+ | # | : : | # | | # |'--------.| # | V # | +----+ EXCEPTION_OBJECT_DESCRIPTION_2 # | | DW | uUnknown1 # | +----+ # | | DW | uOffsetOfPart3 # | +----+ | # | : : | # | | # |'--------------.| # | V # | +----+ EXCEPTION_OBJECT_DESCRIPTION_3 # | | DW | uUnknown1 # | +----+ # | | DW | uOffsetOfPart4 # | +----+ | # | : : | # | | # '--------------------.| # V # +-------+ EXCEPTION_OBJECT_DESCRIPTION_4 # | DW/QW | uAddressOfVFTable # +-------+ # | DW/QW | uUnknown1 # +-------+ # | CHARS | szClassName # : : # PART 1 uExceptionObjectDescriptionAddress = oException.auParameters[2] oExceptionObjectDescriptionVirtualAllocation = cVirtualAllocation( oProcess.uId, uExceptionObjectDescriptionAddress) oExceptionObjectDescription = oExceptionObjectDescriptionVirtualAllocation.foReadStructureForOffset( EXCEPTION_OBJECT_DESCRIPTION_1, uExceptionObjectDescriptionAddress - oExceptionObjectDescriptionVirtualAllocation.uStartAddress, ) # PART 2 uAddressOfPart2 = uBaseAddress + oExceptionObjectDescription.uOffsetOfPart2 oPart2VirtualAllocation = cVirtualAllocation(oProcess.uId, uAddressOfPart2) oPart2 = oPart2VirtualAllocation.foReadStructureForOffset( EXCEPTION_OBJECT_DESCRIPTION_2, uAddressOfPart2 - oPart2VirtualAllocation.uStartAddress, ) # PART 3 uAddressOfPart3 = uBaseAddress + oPart2.uOffsetOfPart3 oPart3VirtualAllocation = cVirtualAllocation(oProcess.uId, uAddressOfPart3) oPart3 = oPart3VirtualAllocation.foReadStructureForOffset( EXCEPTION_OBJECT_DESCRIPTION_3, uAddressOfPart3 - oPart3VirtualAllocation.uStartAddress, ) # PART 4 uAddressOfPart4 = uBaseAddress + oPart3.uOffsetOfPart4 oPart4VirtualAllocation = cVirtualAllocation(oProcess.uId, uAddressOfPart4) cStructureOfPart4 = { "x86": EXCEPTION_OBJECT_DESCRIPTION_4_32, "x64": EXCEPTION_OBJECT_DESCRIPTION_4_64, }[oProcess.sISA] oPart4 = oPart4VirtualAllocation.foReadStructureForOffset( cStructureOfPart4, uAddressOfPart4 - oPart4VirtualAllocation.uStartAddress, ) # Extract decorated symbol name of class from part 4 uAddressOfDecoratedClassName = uAddressOfPart4 + oPart4.fuOffsetOf( "szDecoratedClassName") sDecoratedClassName = oPart4VirtualAllocation.fsReadNullTerminatedStringForOffset( uAddressOfDecoratedClassName - oPart4VirtualAllocation.uStartAddress) sClassName = mDbgHelp.fsUndecorateSymbolName(sDecoratedClassName, bNameOnly=True) # Get undecorated symbol name of class and add it to the exception: oBugReport.sBugTypeId += ":%s" % (sClassName or sDecoratedClassName) return oBugReport