def cBugReport_foAnalyzeException_STATUS_ACCESS_VIOLATION(oBugReport, oCdbWrapper, oException):
  # 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 "";
  uAddress = oException.auParameters[1];
  if uAddress == 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".
    oCdbWrapper.fasSendCommandAndReadOutput( \
        ".prompt_allow +dis +ea; $$ 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.
    if not oCdbWrapper.bCdbRunning: return None;
    oCdbWrapper.fasSendCommandAndReadOutput( \
        "~s; $$ Show disassembly and optional symbol loading stuff");
    if not oCdbWrapper.bCdbRunning: return None;
    asLastInstructionAndAddress = oCdbWrapper.fasSendCommandAndReadOutput(
      "~s; $$ Show disassembly",
      bOutputIsInformative = True,
    );
    if not oCdbWrapper.bCdbRunning: return None;
    # Revert to not showing disassembly and address:
    oCdbWrapper.fasSendCommandAndReadOutput( \
        ".prompt_allow -dis -ea; $$ Revert to clean cdb prompt");
    if not oCdbWrapper.bCdbRunning: return None;
    # 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-9a-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-9a-f`]+", r"\s+",      # address   spaces
        r"[0-9a-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`]+)",          #   (address)
          r"=(\?+|[0-9a-f`]+)",     #   "=" (either{ "???????" }or{ value })
        r"|",                       # }or{
          r"\{([0-9a-f`]+)\}",      #   "{" (address) "}"
        r")",                       # }
      ]), asLastInstructionAndAddress[0]);
      assert oLastInstructionMatch, \
          "Unexpected last instruction output:\r\n%s" % "\r\n".join(asLastInstructionAndAddress);
      sDestinationOperandThatDoesNotReferenceMemory, sAddress1, sValue, sAddress2 = oLastInstructionMatch.groups();
      sAddress = sAddress1 or sAddress2;
      if sAddress1:
        if sDestinationOperandThatDoesNotReferenceMemory:
          # The destination operand does not reference memory, so this must be a read AV
          sViolationTypeId = "R";
          sViolationTypeDescription = "reading";
        elif sValue[0] != "?":
          # 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";
    uAddress = long(sAddress.replace("`", ""), 16);
  
  if sViolationTypeId == "E":
    # Hide the stack frame for the address at which the execute access violation happened: (e.g. 0x0 for a NULL pointer).
    asHiddenTopFrames = ["0x%X" % uAddress];
  else:
    # Hide common 
    asHiddenTopFrames = asHiddenTopFramesForReadAndWriteAVs;
  
  dtsDetails_uSpecialAddress = ddtsDetails_uSpecialAddress_sISA[oCdbWrapper.sCurrentISA];
  for (uSpecialAddress, (sAddressId, sAddressDescription, sSecurityImpact)) in dtsDetails_uSpecialAddress.items():
    sBugDescription = "Access violation while %s memory at 0x%X using %s." % \
        (sViolationTypeDescription, uAddress, sAddressDescription);
    iOffset = uAddress - uSpecialAddress;
    if iOffset != 0:
      uOverflow = {"x86": 1 << 32, "x64": 1 << 64}[oCdbWrapper.sCurrentISA];
      if iOffset > dxBugIdConfig["uMaxAddressOffset"]: # Maybe this is wrapping:
        iOffset -= uOverflow;
      elif iOffset < -dxBugIdConfig["uMaxAddressOffset"]: # Maybe this is wrapping:
        iOffset += uOverflow;
    uOffset = abs(iOffset);
    if uOffset <= dxBugIdConfig["uMaxAddressOffset"]:
      oBugReport.sBugTypeId = "AV%s:%s%s" % (sViolationTypeId, sAddressId, fsGetOffsetDescription(iOffset));
      oBugReport.atxMemoryRemarks.append(("Access violation", uAddress, None)); # TODO Find out size of access
      break;
  else:
    if uAddress >= 0x800000000000:
      oBugReport.sBugTypeId = "AV%s:Invalid" % sViolationTypeId;
      sBugDescription = "Access violation while %s memory at the invalid address 0x%X." % (sViolationTypeDescription, uAddress);
      sSecurityImpact = "Potentially exploitable security issue.";
    else:
      # This is not a special marker or NULL, so it must be an invalid pointer
      # See is page heap has more details on the address at which the access violation happened:
      asPageHeapReport = oCdbWrapper.fasSendCommandAndReadOutput(
        "!heap -p -a 0x%X; $$ Get page heap information" % uAddress,
        bOutputIsInformative = True,
      );
      if not oCdbWrapper.bCdbRunning: return None;
      # Sample output:
      # |    address 0e948ffc found in
      # |    _DPH_HEAP_ROOT @ 48b1000
      # |    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
      # |                                    e9f08bc:          e948000             2000
      # |    6d009cd2 verifier!AVrfDebugPageHeapFree+0x000000c2
      # |    77d42e20 ntdll!RtlDebugFreeHeap+0x0000003c
      # |    77cfe0da ntdll!RtlpFreeHeap+0x0006c97a
      # |    77cf5d2c ntdll!RtlpFreeHeapInternal+0x0000027e
      # |    77c90a3c ntdll!RtlFreeHeap+0x0000002c
      # <<<snip>>> no 0-day information for you!
      # |    address 07fd1000 found in
      # |    _DPH_HEAP_ROOT @ 4fd1000
      # |    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
      # |                                 7f51d9c:          7fd0fc0               40 -          7fd0000             2000
      # |    6c469abc verifier!AVrfDebugPageHeapAllocate+0x0000023c
      # <<<snip>>> no 0-day information for you!
      # There may be errors, sample output:
      # |ReadMemory error for address 5b59c3d0
      # |Use `!address 5b59c3d0' to check validity of the address.
      # <<<snip>>>
      # |*************************************************************************
      # |***                                                                   ***
      # |***                                                                   ***
      # |***    Either you specified an unqualified symbol, or your debugger   ***
      # |***    doesn't have full symbol information.  Unqualified symbol      ***
      # |***    resolution is turned off by default. Please either specify a   ***
      # |***    fully qualified symbol module!symbolname, or enable resolution ***
      # <<<snip>>>
      # unable to resolve ntdll!RtlpStackTraceDataBase
      asPageHeapReport = [
        x for x in asPageHeapReport
        if not re.match(r"^(%s)\s*$" % "|".join([
          "ReadMemory error for address [0-9`a-f]+",
          "Use `!address [0-9`a-f]+' to check validity of the address.",
          "\*\*\*.*\*\*\*",
          "unable to resolve ntdll!RtlpStackTraceDataBase",
        ]), x)
      ];
      # TODO: error resolving symbol should be handled by attempting to reload them, similar to cCdbWrapper_fasGetStack
      if len(asPageHeapReport) >= 4 and not asPageHeapReport[0].startswith("unable to resolve ntdll!"):
        assert re.match(r"^\s+address [0-9`a-f]+ found in\s*$", asPageHeapReport[0]), \
            "Unrecognized page heap report first line:\r\n%s" % "\r\n".join(asPageHeapReport);
        assert re.match(r"^\s+\w+ @ [0-9`a-f]+\s*$", asPageHeapReport[1]), \
            "Unrecognized page heap report second line:\r\n%s" % "\r\n".join(asPageHeapReport);
        oBlockTypeMatch = re.match(                       # line #3
            r"^\s+in (free-ed|busy) allocation \("        # space "in" space ("free-ed" | "busy") space  "allocation ("
            r"\s*\w+:"                                    #   [space] DPH_HEAP_BLOCK ":"
            r"(?:\s+UserAddr\s+UserSize\s+\-)?"           #   optional{ space "UserAddr" space "UserSize" space "-" }
            r"\s+VirtAddr\s+VirtSize"                     #   space "VirtAddr" space "VirtSize"
            r"\)\s*$",                                    # ")" [space]
            asPageHeapReport[2]);
        assert oBlockTypeMatch, \
            "Unrecognized page heap report third line:\r\n%s" % "\r\n".join(asPageHeapReport);
        oBlockAdressAndSizeMatch = re.match(              # line #4
            r"^\s+[0-9`a-f]+:"                            # space heap_header_address ":"
            r"(?:\s+([0-9`a-f]+)\s+([0-9`a-f]+)\s+\-)?"   # optional{ space (heap_block_address) space (heap_block_size) space "-" }
            r"\s+[0-9`a-f]+\s+[0-9`a-f]+"                 # space heap_pages_address space heap_pages_size
            r"\s*$",                                      # [space]
            asPageHeapReport[3]);
        assert oBlockAdressAndSizeMatch, \
            "Unrecognized page heap report fourth line:\r\n%s" % "\r\n".join(asPageHeapReport);
        sBlockType = oBlockTypeMatch.group(1);
        sBlockAddress, sBlockSize = oBlockAdressAndSizeMatch.groups();
        uBlockAddress = sBlockAddress and long(sBlockAddress.replace("`", ""), 16);
        uBlockSize = sBlockSize and long(sBlockSize.replace("`", ""), 16);
        if uBlockAddress:
          uMemoryDumpAddress = uBlockAddress;
          uMemoryDumpSize = uBlockSize;
          if uAddress < uBlockAddress:
            uPrefix = uBlockAddress - uAddress;
            uMemoryDumpAddress -= uPrefix;
            uMemoryDumpSize += uPrefix;
          elif uAddress >= uBlockAddress + uBlockSize:
            uPostFix = uAddress - (uBlockAddress + uBlockSize) + 1;
            uMemoryDumpSize += uPostFix;
          oBugReport.atxMemoryDumps.append(("Memory block in which access violation happened", uMemoryDumpAddress, uMemoryDumpSize));
          oBugReport.atxMemoryRemarks.append(("Memory block start", uBlockAddress, None));
          oBugReport.atxMemoryRemarks.append(("Memory block end", uBlockAddress + uBlockSize, None));
          oBugReport.atxMemoryRemarks.append(("Access violation", uAddress, None)); # TODO Find out size of access
        if sBlockType == "free-ed":
          # Page heap says the memory was freed:
          oBugReport.sBugTypeId = "UAF%s" % sViolationTypeId;
          sAddressDescription = "freed memory";
          sBugDescription = "Access violation while %s %s at 0x%X indicates a use-after-free." % \
              (sViolationTypeDescription, sAddressDescription, uAddress);
          sSecurityImpact = "Potentially exploitable security issue.";
        elif sBlockType == "busy":
          # Page heap says the region is allocated, so the heap block must be inaccessible or the access must have been
          # beyond the end of the heap block, in  the next memory page:
          uPageEndAddress = (uBlockAddress | 0xFFF) + 1; # Follows the page in which the block is located.
          bAccessIsBeyondBlock = uAddress >= uBlockAddress + uBlockSize;
          # The same type of block may have different sizes for 32-bit and 64-bit versions of an application, so the size
          # cannot be used in the id. The same is true for the offset, but the fact that there is an offset is unique to
          # the bug, so that can be added.
          if bAccessIsBeyondBlock:
            uOffsetPastEndOfBlock = uAddress - uBlockAddress - uBlockSize;
            sOffsetDescription = "%d/0x%X bytes beyond" % (uOffsetPastEndOfBlock, uOffsetPastEndOfBlock);
            sBugDescription = "Out-of-bounds access violation while %s memory at 0x%X; %s a %d/0x%X byte heap block at 0x%X." % \
                (sViolationTypeDescription, uAddress, sOffsetDescription, uBlockSize, uBlockSize, uBlockAddress);
            asCorruptedBytes= None;
            # Increase size of memory dump beyond end of block
            if uOffsetPastEndOfBlock != 0:
              if sViolationTypeDescription == "writing":
                # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start
                # of the heap block must be properly aligned. Bytes between the heap block and the end of the page are
                # initialized to 0xD0 and may have been modified before the program wrote beyond the end of the page.
                # We can use this to get a better idea of where to OOB write started:
                uHeapBlockEndAddress = uBlockAddress + uBlockSize;
                uPaddingSize = uPageEndAddress - uHeapBlockEndAddress;
                oCorruptionDetector = cCorruptionDetector(oCdbWrapper);
                oCorruptionDetector.fDetectCorruption(uHeapBlockEndAddress, *[0xD0 for x in xrange(uPaddingSize)]);
                if oCorruptionDetector.bCorruptionDetected:
                  # We detected a modified byte; there was an OOB write before the one that caused this access
                  # violation. Use it's offset instead and add this fact to the description.
                  uStartAddress = oCorruptionDetector.uCorruptionStartAddress;
                  oBugReport.atxMemoryRemarks.append(("Memory corruption", uStartAddress, uAddress - uStartAddress));
                  uOffsetPastEndOfBlock = uStartAddress - uHeapBlockEndAddress;
                  sBugDescription += (" An earlier out-of-bounds write was detected at 0x%X, %d/0x%X bytes " \
                      "beyond that block because it modified the page heap suffix pattern.") % \
                      (uStartAddress, uOffsetPastEndOfBlock, uOffsetPastEndOfBlock);
                  sMemoryDumpDescription = "memory corruption at 0x%X" % uStartAddress;
                  asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes();
              elif uAddress == uPageEndAddress and uAddress > uBlockAddress + uBlockSize:
                sBugDescription += " An earlier out-of-bounds access before this address may have happened without " \
                    "having triggered an access violation.";
            # The access was beyond the end of the block (out-of-bounds, OOB)
            oBugReport.sBugTypeId = "OOB%s[%s]%s" % (sViolationTypeId, fsGetNumberDescription(uBlockSize), \
                fsGetOffsetDescription(uOffsetPastEndOfBlock));
            if asCorruptedBytes:
              sBugDescription += " The following byte values were written to the corrupted area: %s." % \
                  ", ".join(asCorruptedBytes);
              oBugReport.sBugTypeId += oCorruptionDetector.fsCorruptionId() or "";
          else:
            # The access was inside the block but apparently the kind of access attempted is not allowed (e.g. write to
            # read-only memory).
            oBugReport.sBugTypeId = "AV%s[%s]@%s" % (sViolationTypeId, \
                fsGetNumberDescription(uBlockSize), fsGetNumberDescription(uOffsetFromStartOfBlock));
            sOffsetDescription = "%d/0x%X bytes into" % (uOffsetFromStartOfBlock, uOffsetFromStartOfBlock);
            sBugDescription = "Access violation while %s memory at 0x%X; %s a %d/0x%X byte heap block at 0x%X." % \
                (sViolationTypeDescription, uAddress, sOffsetDescription, uBlockSize, uBlockSize, uBlockAddress);
          sSecurityImpact = "Potentially exploitable security issue.";
        else:
          raise NotImplemented("NOT REACHED");
        sPageHeapOutputHTML = sBlockHTMLTemplate % {
          "sName": "Page heap report for address 0x%X" % uAddress,
          "sContent": "<pre>%s</pre>" % "\r\n".join([oCdbWrapper.fsHTMLEncode(s) for s in asPageHeapReport])
        };
        oBugReport.asExceptionSpecificBlocksHTML.append(sPageHeapOutputHTML);
      else:
        asMemoryProtectionInformation = oCdbWrapper.fasSendCommandAndReadOutput(
          "!vprot 0x%X; $$ Get memory protection information" % uAddress,
          bOutputIsInformative = True,
        );
        if not oCdbWrapper.bCdbRunning: return None;
        # BaseAddress:       00007df5ff5f0000
        # AllocationBase:    00007df5ff5f0000
        # AllocationProtect: 00000001  PAGE_NOACCESS
        # RegionSize:        0000000001d34000
        # State:             00002000  MEM_RESERVE
        # Type:              00040000  MEM_MAPPED
        
        # BaseAddress:       0000000000000000
        # AllocationBase:    0000000000000000
        # RegionSize:        0000000022f60000
        # State:             00010000  MEM_FREE
        # Protect:           00000001  PAGE_NOACCESS
        
        # BaseAddress:       00007ffffffe0000
        # AllocationBase:    00007ffffffe0000
        # AllocationProtect: 00000002  PAGE_READONLY
        # RegionSize:        0000000000010000
        # State:             00002000  MEM_RESERVE
        # Protect:           00000001  PAGE_NOACCESS
        # Type:              00020000  MEM_PRIVATE
        
        # !vprot: extension exception 0x80004002
        #     "QueryVirtual failed"
        assert len(asMemoryProtectionInformation) > 0, \
            "!vprot did not return any results.";
        if re.match(r"^(%s)$" % "|".join([
          "ERROR: !vprot: extension exception 0x80004002\.",
          "!vprot: No containing memory region found",
          "No export vprot found",
        ]), asMemoryProtectionInformation[0]):
          oBugReport.sBugTypeId = "AV%s:Unallocated" % sViolationTypeId;
          sBugDescription = "Access violation while %s unallocated memory at 0x%X." % (sViolationTypeDescription, uAddress);
          sSecurityImpact = "Potentially exploitable security issue, if the attacker can control the address or the memory at the address.";
        else:
          uAllocationStartAddress = None;
          uAllocationProtectionFlags = None;
          uAllocationSize = None;
          uStateFlags = None;
          uProtectionFlags = None;
          uTypeFlags = None;
          for sLine in asMemoryProtectionInformation:
            oLineMatch = re.match(r"^(\w+):\s+([0-9a-f]+)(?:\s+\w+)?$", sLine);
            assert oLineMatch, \
                "Unrecognized memory protection information line: %s\r\n%s" % (sLine, "\r\n".join(asMemoryProtectionInformation));
            sInfoType, sValue = oLineMatch.groups();
            uValue = long(sValue, 16);
            if sInfoType == "BaseAddress":
              pass; # Appear to be the address rounded down to the nearest start of a page, i.e. not useful information.
            elif sInfoType == "AllocationBase":
              uAllocationStartAddress = uValue;
            elif sInfoType == "AllocationProtect":
              uAllocationProtectionFlags = uValue;
            elif sInfoType == "RegionSize":
              uAllocationSize = uValue;
            elif sInfoType == "State":
              uStateFlags = uValue;
            elif sInfoType == "Protect":
              uProtectionFlags = uValue;
            elif sInfoType == "Type":
              uTypeFlags = uValue;
          oBugReport.atxMemoryRemarks.append(("Memory allocation start", uAllocationStartAddress, None));
          oBugReport.atxMemoryRemarks.append(("Memory allocation end", uAllocationStartAddress + uAllocationSize, None));
          if uStateFlags == 0x10000:
            oBugReport.sBugTypeId = "AV%s:Unallocated" % sViolationTypeId;
            sBugDescription = "Access violation while %s unallocated memory at 0x%X." % (sViolationTypeDescription, uAddress);
            sSecurityImpact = "Potentially exploitable security issue, if the attacker can control the address or the memory at the address.";
          elif uStateFlags == 0x2000: # MEM_RESERVE
# These checks were added to make sure I understood exactly what was going on. However, it turns out that I don't
# because these checks fail without me being able to understand why. So, I've decided to disable them and see what
# happens. If you have more information that can help me make sense of this and improve it, let me know!
#            assert uTypeFlags in [0x20000, 0x40000], \
#                "Expected MEM_RESERVE memory to have type MEM_PRIVATE or MEM_MAPPED\r\n%s" % "\r\n".join(asMemoryProtectionInformation);
#            # PAGE_READONLY !? Apparently...
#            assert uProtectionFlags == 0x1 or uAllocationProtectionFlags in [0x1, 02], \
#                "Expected MEM_RESERVE memory to have protection PAGE_NOACCESS or PAGE_READONLY\r\n%s" % "\r\n".join(asMemoryProtectionInformation);
            oBugReport.sBugTypeId = "AV%s:Reserved" % sViolationTypeId;
            sBugDescription = "Access violation while %s reserved but unallocated memory at 0x%X." % (sViolationTypeDescription, uAddress);
            sSecurityImpact = "Potentially exploitable security issue, if the address is attacker controlled.";
          elif uStateFlags == 0x1000: # MEM_COMMIT
            dsMemoryProtectionsDescription_by_uFlags = {
              0x01: "inaccessible",  0x02: "read-only",  0x04: "read- and writable",  0x08: "read- and writable",
              0x10: "executable", 0x20: "read- and executable", 0x40: "full-access", 0x80: "full-access"
            };
            sMemoryProtectionsDescription = dsMemoryProtectionsDescription_by_uFlags.get(uAllocationProtectionFlags);
            assert sMemoryProtectionsDescription, \
                "Unexpected MEM_COMMIT memory to have protection value 0x%X\r\n%s" % (uAllocationProtectionFlags, "\r\n".join(asMemoryProtectionInformation));
            oBugReport.sBugTypeId = "AV%s:Arbitrary" % sViolationTypeId;
            sBugDescription = "Access violation while %s %s memory at 0x%X." % (sViolationTypeDescription, sMemoryProtectionsDescription, uAddress);
            sSecurityImpact = "Potentially exploitable security issue, if the address is attacker controlled.";
            oBugReport.atxMemoryDumps.append(("Memory block in which access violation happened", uAllocationStartAddress, uAllocationSize));
            oBugReport.atxMemoryRemarks.append(("Access violation", uAddress, None)); # TODO Find out size of access
          else:
            raise AssertionError("Unexpected memory state 0x%X\r\n%s" % (uStateFlags, "\r\n".join(asMemoryProtectionInformation)));
  oBugReport.sBugDescription = sBugDescription + sViolationTypeNotes;
  oBugReport.sSecurityImpact = sSecurityImpact;
  dtxBugTranslations = ddtxBugTranslations.get(oBugReport.sBugTypeId, None);
  if dtxBugTranslations:
    oBugReport = oBugReport.foTranslate(dtxBugTranslations);
  if oBugReport:
    oBugReport.oStack.fHideTopFrames(asHiddenTopFrames);
  return oBugReport;
Exemple #2
0
def cCdbWrapper_fbDetectAndReportVerifierErrors(oCdbWrapper, asCdbOutput):
    uErrorNumber = None
    uProcessId = None
    sMessage = None
    uVerifierStopHeapBlockStartAddress = None
    uVerifierStopHeapBlockSize = None
    uCorruptedStamp = None
    uCorruptionAddress = None
    asRelevantLines = []

    for sLine in asCdbOutput:
        # Ignore exmpty lines
        if not sLine:
            continue
        # Look for the first VERIFIER STOP message and gather information
        if uErrorNumber is None:
            oErrorMessageMatch = re.match(
                r"^VERIFIER STOP ([0-9A-F]+): pid 0x([0-9A-F]+): (.*?)\s*$",
                sLine)
            if oErrorMessageMatch:
                sErrorNumber, sProcessId, sMessage = oErrorMessageMatch.groups(
                )
                uErrorNumber = long(sErrorNumber, 16)
                uProcessId = long(sProcessId, 16)
                asRelevantLines.append(sLine)
            continue
        # A VERIFIER STOP message has been detected, gather what information verifier provides:
        oInformationMatch = re.match(r"^\t([0-9A-F]+) : (.*?)\s*$", sLine)
        if oInformationMatch:
            asRelevantLines.append(sLine)
            sValue, sDescription = oInformationMatch.groups()
            uValue = long(sValue, 16)
            sDescription = sDescription.lower()
            # Both "Corruption address" and "corruption address" are used :(
            if sDescription == "heap block":
                uVerifierStopHeapBlockStartAddress = uValue
            elif sDescription == "block size":
                uVerifierStopHeapBlockSize = uValue
            elif sDescription == "corrupted stamp":
                uCorruptedStamp = uValue
            elif sDescription == "corruption address":
                uCorruptionAddress = uValue
        else:
            assert sLine.strip().replace("=", "") == "", \
                "Unknown VERIFIER STOP message line: %s\r\n%s" % (repr(sLine), "\r\n".join(asCdbOutput))
            break
    else:
        assert uErrorNumber is None, \
            "Detected the start of a VERIFIER STOP message but not the end\r\n%s" % "\r\n".join(asCdbOutput)
        return False
    if uErrorNumber == 0x303:
        # =======================================
        # VERIFIER STOP 0000000000000303: pid 0xB2C: NULL handle passed as parameter. A valid handle must be used.
        #
        # 0000000000000000 : Not used.
        # 0000000000000000 : Not used.
        # 0000000000000000 : Not used.
        # 0000000000000000 : Not used.
        #
        # =======================================
        # This is not interesting; do not report an error.
        return True

    assert uVerifierStopHeapBlockStartAddress is not None, \
        "The heap block start address was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines)
    assert uVerifierStopHeapBlockSize is not None, \
        "The heap block size was not found in the verifier stop message.\r\n%s" % "\r\n".join(asRelevantLines)

    oCorruptionDetector = cCorruptionDetector(oCdbWrapper)
    oPageHeapReport = cPageHeapReport.foCreate(
        oCdbWrapper, uVerifierStopHeapBlockStartAddress)
    if not oCdbWrapper.bCdbRunning: return None
    if oPageHeapReport:
        # Prefer page heap information over VERIFIER STOP info - the later has been known to be incorrect sometimes, for
        # instance when the application frees (heap pointer + offset), the VERIFIER STOP info will use that as the heap
        # block base, whereas the page heap report will correctly report (heap pointer) as the heap block base.
        uAllocationStartAddress = oPageHeapReport.uAllocationStartAddress
        uAllocationEndAddress = oPageHeapReport.uAllocationEndAddress
        # Check the page heap data near the heap block for signs of corruption:
        oPageHeapReport.fbCheckForCorruption(oCorruptionDetector)
        if not oCdbWrapper.bCdbRunning: return None
    if oPageHeapReport and oPageHeapReport.uBlockStartAddress:
        uHeapBlockStartAddress = oPageHeapReport.uBlockStartAddress
        uHeapBlockSize = oPageHeapReport.uBlockSize
        uHeapBlockEndAddress = uHeapBlockStartAddress + uHeapBlockSize
    else:
        uHeapBlockStartAddress = uVerifierStopHeapBlockStartAddress
        uHeapBlockSize = uVerifierStopHeapBlockSize
        uHeapBlockEndAddress = uVerifierStopHeapBlockStartAddress + uVerifierStopHeapBlockSize
    atxMemoryRemarks = []
    if oCdbWrapper.bGenerateReportHTML:
        uMemoryDumpStartAddress = uHeapBlockStartAddress
        uMemoryDumpSize = uVerifierStopHeapBlockSize
        if oPageHeapReport:
            atxMemoryRemarks.extend(oPageHeapReport.fatxMemoryRemarks())

    # Handle various VERIFIER STOP messages.
    sBugDescription = None
    if sMessage in ["corrupted start stamp", "corrupted end stamp"]:
        assert uCorruptionAddress is None, \
            "We do not expect the corruption address to be provided in this VERIFIER STOP message\r\n%s" % \
                "\r\n".join(asRelevantLines)
        if not oCorruptionDetector.bCorruptionDetected and uHeapBlockStartAddress != uVerifierStopHeapBlockStartAddress:
            # When the application attempts to free (heap pointer + offset), Verifier does not detect this and will assume
            # the application provided pointer is correct. This causes it to look for the start and end stamp in the wrong
            # location and report this bug as memory corruption. When the page heap data shows no signs of corruption, we
            # can special case it.
            iFreeAtOffset = uVerifierStopHeapBlockStartAddress - uHeapBlockStartAddress
            sBugTypeId = "MisalignedFree[%s]%s" % (fsGetNumberDescription(
                uHeapBlockSize), fsGetOffsetDescription(iFreeAtOffset))
            sOffsetBeforeOrAfter = iFreeAtOffset < 0 and "before" or "after"
            sBugDescription = "The application attempted to free memory using a pointer that was %d/0x%X bytes %s a " \
                "%d/0x%X byte heap block at address 0x%X" % (abs(iFreeAtOffset), abs(iFreeAtOffset), \
                sOffsetBeforeOrAfter, uHeapBlockSize, uHeapBlockSize, uHeapBlockStartAddress)
            sSecurityImpact = "Unknown: this type of bug has not been analyzed before"
        else:
            sBugTypeId = "OOBW[%s]" % (fsGetNumberDescription(uHeapBlockSize))
            assert oCorruptionDetector.bCorruptionDetected, \
                "Cannot find any sign of corruption"
    elif sMessage == "corrupted suffix pattern":
        assert uCorruptionAddress is not None, \
            "The corruption address is expected to be provided in this VERIFIER STOP message:\r\n%s" % \
                "\r\n".join(asRelevantLines)
        # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start of the
        # heap block must be properly aligned. Bytes between the heap block and the end of the page are initialized to
        # 0xD0. Verifier has detected that one of the bytes changed value, which indicates an out-of-bounds write. BugId
        # will try to find all bytes that were changed:
        sBugTypeId = "OOBW[%s]" % (fsGetNumberDescription(uHeapBlockSize))
        assert oCorruptionDetector.bCorruptionDetected, \
            "Cannot find any sign of corruption"
    elif sMessage == "corrupted infix pattern":
        assert uCorruptionAddress is not None, \
            "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \
                "\r\n".join(asRelevantLines)
        # Page heap sometimes does not free a heap block immediately, but overwrites the bytes with 0xF0. Verifier has
        # detected that one of the bytes changed value, which indicates a write-after-free. BugId will try to find all
        # bytes that were changed:
        sBugTypeId = "UAFW[%s]" % (fsGetNumberDescription(uHeapBlockSize))
        # TODO add these checks to cPaheHeapReport if possible.
        oCorruptionDetector.fbDetectCorruption(
            uHeapBlockStartAddress, [0xF0 for x in xrange(uHeapBlockSize)])
        assert oCorruptionDetector.bCorruptionDetected, \
            "Cannot find any sign of corruption"
    else:
        sBugTypeId = "HeapCorrupt[%s]" % (
            fsGetNumberDescription(uHeapBlockSize))
    # sBugDescription is not set if this is a memory corruption

    # See if we have a better idea of where the corruption started and ended:
    if oCorruptionDetector.bCorruptionDetected:
        uCorruptionStartAddress = oCorruptionDetector.uCorruptionStartAddress
        uCorruptionEndAddress = oCorruptionDetector.uCorruptionEndAddress
        uCorruptionSize = uCorruptionEndAddress - uCorruptionStartAddress
        if oCdbWrapper.bGenerateReportHTML:
            atxMemoryRemarks.extend(oCorruptionDetector.fatxMemoryRemarks())


# I believe verifier may not check various memory areas in order from lowest to highest address. So it may not report
# the byte that got corrupted with the lowest address; therefore it may not make sense to compare what verifier
# reported with what our corruption detector found:
#    assert uCorruptionAddress is None or uCorruptionAddress == oCorruptionDetector.uCorruptionStartAddress, \
#        "Verifier reported corruption at address 0x%X but BugId detected corruption at address 0x%X\r\n%s" % \
#        (uCorruptionAddress, oCorruptionDetector.uCorruptionStartAddress, "\r\n".join(asRelevantLines));
        bCorruptionDetected = True
    elif uCorruptionAddress:
        if oCdbWrapper.bGenerateReportHTML:
            atxMemoryRemarks.append(
                ("Corrupted memory", uCorruptionAddress, None))
        uCorruptionStartAddress = uCorruptionAddress
        uCorruptionEndAddress = uCorruptionAddress
        bCorruptionDetected = True
    else:
        bCorruptionDetected = False

    if bCorruptionDetected:
        # If the corruption starts before or ends after the heap block, expand the memory dump to include the entire
        # corrupted region.
        if oCdbWrapper.bGenerateReportHTML:
            if uCorruptionStartAddress < uMemoryDumpStartAddress:
                uMemoryDumpSize += uMemoryDumpStartAddress - uCorruptionStartAddress
                uMemoryDumpStartAddress = uCorruptionStartAddress
            if uCorruptionEndAddress < uMemoryDumpStartAddress + uMemoryDumpSize:
                uMemoryDumpSize += uCorruptionEndAddress - (
                    uMemoryDumpStartAddress + uMemoryDumpSize)
        # Get a human readable description of the start offset of corruption relative to the heap block, where corruption
        # starting before or inside the heap block will be relative to the start, and corruption after it to the end.
        uCorruptionStartOffset = uCorruptionStartAddress - uHeapBlockStartAddress
        if uCorruptionStartOffset >= uHeapBlockSize:
            uCorruptionStartOffset -= uHeapBlockSize
            sCorruptionStartOffsetDescription = "%d/0x%X bytes beyond" % (
                uCorruptionStartOffset, uCorruptionStartOffset)
            sBugTypeId += fsGetOffsetDescription(uCorruptionStartOffset)
        elif uCorruptionStartOffset > 0:
            sCorruptionStartOffsetDescription = "%d/0x%X bytes into" % (
                uCorruptionStartOffset, uCorruptionStartOffset)
            sBugTypeId += "@%s" % fsGetNumberDescription(
                uCorruptionStartOffset)
        else:
            sCorruptionStartOffsetDescription = "%d/0x%X bytes before" % (
                -uCorruptionStartOffset, -uCorruptionStartOffset)
            sBugTypeId += fsGetOffsetDescription(uCorruptionStartOffset)
        sBugDescription = "Page heap detected heap corruption at 0x%X; %s a %d/0x%X byte heap block at address 0x%X" % \
            (uCorruptionStartAddress, sCorruptionStartOffsetDescription, uHeapBlockSize, uHeapBlockSize, uHeapBlockStartAddress)
        # If we detected corruption by scanning certain bytes in the applications memory, make sure this matches what
        # verifier reported and save all bytes that were affected: so far, we only saved the bytes that had an unexpected
        # value, but there is a chance that a byte was overwritten with the same value it has before, in which case it was
        # not saved. This can be detect if it is surrounded by bytes that did change value. This code reads the value of all
        # bytes between the first and last byte that we detected was corrupted:
        if oCorruptionDetector.bCorruptionDetected:
            asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes()
            sBugDescription += " The following byte values were written to the corrupted area: %s." % ", ".join(
                asCorruptedBytes)
            sBugTypeId += oCorruptionDetector.fsCorruptionId() or ""
        sSecurityImpact = "Potentially exploitable security issue, if the corruption is attacker controlled"
    else:
        assert sBugDescription, \
            "sBugDescription should have been set"

    oBugReport = cBugReport.foCreate(oCdbWrapper, sBugTypeId, sBugDescription,
                                     sSecurityImpact)
    if not oCdbWrapper.bCdbRunning: return None
    if oCdbWrapper.bGenerateReportHTML:
        oBugReport.atxMemoryDumps.append(("Memory near heap block at 0x%X" % uMemoryDumpStartAddress, \
            uMemoryDumpStartAddress, uMemoryDumpSize))
        oBugReport.atxMemoryRemarks.extend(atxMemoryRemarks)
    # Output the VERIFIER STOP message for reference
    if oCdbWrapper.bGenerateReportHTML:
        sVerifierStopMessageHTML = sBlockHTMLTemplate % {
            "sName":
            "VERIFIER STOP message",
            "sCollapsed":
            "Collapsed",
            "sContent":
            "<pre>%s</pre>" % "\r\n".join([
                oCdbWrapper.fsHTMLEncode(s, uTabStop=8)
                for s in asRelevantLines
            ])
        }
        oBugReport.asExceptionSpecificBlocksHTML.append(
            sVerifierStopMessageHTML)
        # Output the page heap information for reference
        if oPageHeapReport:
            sPageHeapOutputHTML = sBlockHTMLTemplate % {
                "sName":
                "Page heap output for heap block at 0x%X" %
                uHeapBlockStartAddress,
                "sCollapsed":
                "Collapsed",
                "sContent":
                "<pre>%s</pre>" % "\r\n".join([
                    oCdbWrapper.fsHTMLEncode(s, uTabStop=8)
                    for s in oPageHeapReport.asPageHeapOutput
                ])
            }
            oBugReport.asExceptionSpecificBlocksHTML.append(
                sPageHeapOutputHTML)

    oBugReport.bRegistersRelevant = False
    oCdbWrapper.oBugReport = oBugReport
    return True
def cBugReport_foAnalyzeException_STATUS_ACCESS_VIOLATION(oBugReport, oCdbWrapper, oException):
  # 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 "";
  uAddress = oException.auParameters[1];
  if uAddress == 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".
    oCdbWrapper.fasSendCommandAndReadOutput( \
        ".prompt_allow +dis +ea; $$ 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.
    if not oCdbWrapper.bCdbRunning: return None;
    oCdbWrapper.fasSendCommandAndReadOutput( \
        "~s; $$ Show disassembly and optional symbol loading stuff");
    if not oCdbWrapper.bCdbRunning: return None;
    asLastInstructionAndAddress = oCdbWrapper.fasSendCommandAndReadOutput(
      "~s; $$ Show disassembly",
      bOutputIsInformative = True,
    );
    if not oCdbWrapper.bCdbRunning: return None;
    # Revert to not showing disassembly and address:
    oCdbWrapper.fasSendCommandAndReadOutput( \
        ".prompt_allow -dis -ea; $$ Revert to clean cdb prompt");
    if not oCdbWrapper.bCdbRunning: return None;
    # 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-9a-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-9a-f`]+", r"\s+",      # address   spaces
        r"[0-9a-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`]+)",          #   (address)
          r"=(\?+|[0-9a-f`]+)",     #   "=" (either{ "???????" }or{ value })
        r"|",                       # }or{
          r"\{([0-9a-f`]+)\}",      #   "{" (address) "}"
        r")",                       # }
      ]), asLastInstructionAndAddress[0]);
      assert oLastInstructionMatch, \
          "Unexpected last instruction output:\r\n%s" % "\r\n".join(asLastInstructionAndAddress);
      sDestinationOperandThatDoesNotReferenceMemory, sAddress1, sValue, sAddress2 = oLastInstructionMatch.groups();
      sAddress = sAddress1 or sAddress2;
      if sAddress1:
        if sDestinationOperandThatDoesNotReferenceMemory:
          # The destination operand does not reference memory, so this must be a read AV
          sViolationTypeId = "R";
          sViolationTypeDescription = "reading";
        elif sValue[0] != "?":
          # 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";
    uAddress = long(sAddress.replace("`", ""), 16);
  oBugReport.atxMemoryRemarks.append(("Access violation", uAddress, 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 == uAddress:
      oBugReport.oStack.aoFrames[0].bIsHidden = True;
  
  uPointerSize = oCdbWrapper.fuGetValue("@$ptrsize");
  if not oCdbWrapper.bCdbRunning: return None;
  uPageSize = oCdbWrapper.fuGetValue("@$pagesize");
  if not oCdbWrapper.bCdbRunning: return;
  
  dtsDetails_uSpecialAddress = ddtsDetails_uSpecialAddress_sISA[oCdbWrapper.sCurrentISA];
  for (uSpecialAddress, (sAddressId, sAddressDescription, sSecurityImpact)) in dtsDetails_uSpecialAddress.items():
    sBugDescription = "Access violation while %s memory at 0x%X using %s." % \
        (sViolationTypeDescription, uAddress, sAddressDescription);
    iOffset = uAddress - uSpecialAddress;
    if iOffset != 0:
      uOverflow = {"x86": 1 << 32, "x64": 1 << 64}[oCdbWrapper.sCurrentISA];
      if iOffset > dxBugIdConfig["uMaxAddressOffset"]: # Maybe this is wrapping:
        iOffset -= uOverflow;
      elif iOffset < -dxBugIdConfig["uMaxAddressOffset"]: # Maybe this is wrapping:
        iOffset += uOverflow;
    uOffset = abs(iOffset);
    if uOffset <= dxBugIdConfig["uMaxAddressOffset"]:
      oBugReport.sBugTypeId = "AV%s:%s%s" % (sViolationTypeId, sAddressId, fsGetOffsetDescription(iOffset));
      break;
  else:
    if uAddress >= 0x800000000000:
      oBugReport.sBugTypeId = "AV%s:Invalid" % sViolationTypeId;
      sBugDescription = "Access violation while %s memory at the invalid address 0x%X." % (sViolationTypeDescription, uAddress);
      sSecurityImpact = "Potentially exploitable security issue.";
    else:
      # This is not a special marker or NULL, so it must be an invalid pointer
      # See is page heap has more details on the address at which the access violation happened:
      oPageHeapReport = cPageHeapReport.foCreate(oCdbWrapper, uAddress);
      if not oCdbWrapper.bCdbRunning: return None;
      if oPageHeapReport:
        oBugReport.atxMemoryRemarks.extend(oPageHeapReport.fatxMemoryRemarks());
        if oPageHeapReport.uBlockStartAddress:
          if oCdbWrapper.bGenerateReportHTML:
            uMemoryDumpStartAddress = oPageHeapReport.uBlockStartAddress;
            uMemoryDumpSize = oPageHeapReport.uBlockSize;
          if oCdbWrapper.bGenerateReportHTML:
            if uAddress < oPageHeapReport.uBlockStartAddress:
              uPrefix = oPageHeapReport.uBlockStartAddress - uAddress;
              uMemoryDumpStartAddress -= uPrefix;
              uMemoryDumpSize += uPrefix;
            elif uAddress >= oPageHeapReport.uBlockEndAddress:
              uPostFix = uAddress - oPageHeapReport.uBlockEndAddress + 1;
              uMemoryDumpSize += uPostFix;
            # Check if we're not trying to dump a rediculous amount of memory:
            # Clamp start and end address
            uMemoryDumpStartAddress, uMemoryDumpSize = ftuLimitedAndAlignedMemoryDumpStartAddressAndSize(
              uAddress, uPointerSize, uMemoryDumpStartAddress, uMemoryDumpSize
            );
            oBugReport.atxMemoryDumps.append(("Memory near access violation at 0x%X" % uAddress, \
                uMemoryDumpStartAddress, uMemoryDumpSize));
        if oPageHeapReport.sBlockType == "free-ed":
          # Page heap says the memory was freed: unfortunately, it does not tell us how big the block was, or exactly
          # where in the allocated memory it was stored, but we know it must have been allocated as close to the end
          # as possible, so the offset from the end of the allocated memory should be static unless the size of the
          # block changes and/or the offset at which the code tries to read.
          uOffsetFromEndOfAllocation = uAddress - oPageHeapReport.uAllocationEndAddress;
          if uOffsetFromEndOfAllocation < 0:
            oBugReport.sBugTypeId = "UAF%s[]~%s" % (sViolationTypeId, fsGetNumberDescription(-uOffsetFromEndOfAllocation));
          else:
            # The code tried to access data outside the bounds of the freed memory: double face-palm!
            oBugReport.sBugTypeId = "OOBUAF%s[]%s" % (sViolationTypeId, fsGetOffsetDescription(uOffsetFromEndOfAllocation));
          sAddressDescription = "freed memory";
          sBugDescription = "Access violation while %s %s at 0x%X indicates a use-after-free." % \
              (sViolationTypeDescription, sAddressDescription, uAddress);
          sSecurityImpact = "Potentially exploitable security issue.";
        elif oPageHeapReport.sBlockType == "busy":
          # Page heap says the region is allocated, so the heap block must be inaccessible or the access must have been
          # beyond the end of the heap block, in  the next memory page:
          bAccessIsBeyondBlock = uAddress >= oPageHeapReport.uBlockEndAddress;
          # The same type of block may have different sizes for 32-bit and 64-bit versions of an application, so the size
          # cannot be used in the id. The same is true for the offset, but the fact that there is an offset is unique to
          # the bug, so that can be added.
          if bAccessIsBeyondBlock:
            uOffsetPastEndOfBlock = uAddress - oPageHeapReport.uBlockEndAddress;
            sOffsetDescription = "%d/0x%X bytes beyond" % (uOffsetPastEndOfBlock, uOffsetPastEndOfBlock);
            sBugDescription = "Out-of-bounds access violation while %s memory at 0x%X; %s a %d/0x%X byte heap block at 0x%X." % \
                (sViolationTypeDescription, uAddress, sOffsetDescription, oPageHeapReport.uBlockSize, \
                oPageHeapReport.uBlockSize, oPageHeapReport.uBlockStartAddress);
            asCorruptedBytes= None;
            # Increase size of memory dump beyond end of block
            if uOffsetPastEndOfBlock != 0:
              if sViolationTypeDescription == "writing":
                # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start
                # of the heap block must be properly aligned. Bytes between the heap block and the end of the page are
                # initialized to 0xD0 and may have been modified before the program wrote beyond the end of the page.
                # We can use this to get a better idea of where to OOB write started:
                oCorruptionDetector = cCorruptionDetector(oCdbWrapper);
                if oPageHeapReport.fbCheckForCorruption(oCorruptionDetector):
                  # We detected a modified byte; there was an OOB write before the one that caused this access
                  # violation. Use it's offset instead and add this fact to the description.
                  if oCdbWrapper.bGenerateReportHTML:
                    oBugReport.atxMemoryRemarks.extend(oCorruptionDetector.fatxMemoryRemarks());
                  uStartAddress = oCorruptionDetector.uCorruptionStartAddress;
                  uOffsetPastEndOfBlock = uStartAddress - oPageHeapReport.uBlockEndAddress;
                  sBugDescription += (" An earlier out-of-bounds write was detected at 0x%X, %d/0x%X bytes " \
                      "beyond that block because it modified the page heap suffix pattern.") % \
                      (uStartAddress, uOffsetPastEndOfBlock, uOffsetPastEndOfBlock);
                  asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes();
              elif uAddress == oPageHeapReport.uAllocationEndAddress and uAddress > oPageHeapReport.uBlockEndAddress:
                sBugDescription += " An earlier out-of-bounds access before this address may have happened without " \
                    "having triggered an access violation.";
            # The access was beyond the end of the block (out-of-bounds, OOB)
            oBugReport.sBugTypeId = "OOB%s[%s]%s" % (sViolationTypeId, \
                fsGetNumberDescription(oPageHeapReport.uBlockSize), fsGetOffsetDescription(uOffsetPastEndOfBlock));
            if asCorruptedBytes:
              sBugDescription += " The following byte values were written to the corrupted area: %s." % \
                  ", ".join(asCorruptedBytes);
              oBugReport.sBugTypeId += oCorruptionDetector.fsCorruptionId() or "";
          else:
            # The access was inside the block but apparently the kind of access attempted is not allowed (e.g. write to
            # read-only memory).
            oBugReport.sBugTypeId = "AV%s[%s]@%s" % (sViolationTypeId, \
                fsGetNumberDescription(oPageHeapReport.uBlockSize), fsGetNumberDescription(uOffsetFromStartOfBlock));
            sOffsetDescription = "%d/0x%X bytes into" % (uOffsetFromStartOfBlock, uOffsetFromStartOfBlock);
            sBugDescription = "Access violation while %s memory at 0x%X; %s a %d/0x%X byte heap block at 0x%X." % \
                (sViolationTypeDescription, uAddress, sOffsetDescription, oPageHeapReport.uBlockSize, \
                oPageHeapReport.uBlockSize, oPageHeapReport.uBlockStartAddress);
          sSecurityImpact = "Potentially exploitable security issue.";
        else:
          raise NotImplemented("NOT REACHED");
        if oCdbWrapper.bGenerateReportHTML:
          sPageHeapOutputHTML = sBlockHTMLTemplate % {
            "sName": "Page heap output for address 0x%X" % uAddress,
            "sCollapsed": "Collapsed",
            "sContent": "<pre>%s</pre>" % "\r\n".join([
              oCdbWrapper.fsHTMLEncode(s, uTabStop = 8) for s in oPageHeapReport.asPageHeapOutput
            ])
          };
          oBugReport.asExceptionSpecificBlocksHTML.append(sPageHeapOutputHTML);
      else:
        oThreadEnvironmentBlock = cThreadEnvironmentBlock.foCreate(oCdbWrapper);
        uOffsetFromTopOfStack = uAddress - oThreadEnvironmentBlock.uStackTopAddress;
        uOffsetFromBottomOfStack = oThreadEnvironmentBlock.uStackBottomAddress - uAddress;
        if uOffsetFromTopOfStack >= 0 and uOffsetFromTopOfStack <= uPageSize:
          oBugReport.sBugTypeId = "AV%s[Stack]+%s" % (sViolationTypeId, fsGetOffsetDescription(uOffsetFromTopOfStack));
          sBugDescription = "Access violation while %s memory at 0x%X; %d/0x%X bytes passed the top of the stack at 0x%X." % \
              (sViolationTypeDescription, uAddress, uOffsetFromTopOfStack, uOffsetFromTopOfStack, oThreadEnvironmentBlock.uStackTopAddress);
          sSecurityImpact = "Potentially exploitable security issue.";
        elif uOffsetFromBottomOfStack >= 0 and uOffsetFromBottomOfStack <= uPageSize:
          oBugReport.sBugTypeId = "AV%s[Stack]-%s" % (sViolationTypeId, fsGetOffsetDescription(-uOffsetFromBottomOfStack));
          sBugDescription = "Access violation while %s memory at 0x%X; %d/0x%X bytes before the bottom of the stack at 0x%X." % \
              (sViolationTypeDescription, uAddress, uOffsetFromBottomOfStack, uOffsetFromBottomOfStack, oThreadEnvironmentBlock.uStackTopAddress);
          sSecurityImpact = "Potentially exploitable security issue.";
        else:
          asMemoryProtectionInformation = oCdbWrapper.fasSendCommandAndReadOutput(
            "!vprot 0x%X; $$ Get memory protection information" % uAddress,
            bOutputIsInformative = True,
          );
          if not oCdbWrapper.bCdbRunning: return None;
          # BaseAddress:       00007df5ff5f0000
          # AllocationBase:    00007df5ff5f0000
          # AllocationProtect: 00000001  PAGE_NOACCESS
          # RegionSize:        0000000001d34000
          # State:             00002000  MEM_RESERVE
          # Type:              00040000  MEM_MAPPED
          
          # BaseAddress:       0000000000000000
          # AllocationBase:    0000000000000000
          # RegionSize:        0000000022f60000
          # State:             00010000  MEM_FREE
          # Protect:           00000001  PAGE_NOACCESS
          
          # BaseAddress:       00007ffffffe0000
          # AllocationBase:    00007ffffffe0000
          # AllocationProtect: 00000002  PAGE_READONLY
          # RegionSize:        0000000000010000
          # State:             00002000  MEM_RESERVE
          # Protect:           00000001  PAGE_NOACCESS
          # Type:              00020000  MEM_PRIVATE
          
          # !vprot: extension exception 0x80004002
          #     "QueryVirtual failed"
          assert len(asMemoryProtectionInformation) > 0, \
              "!vprot did not return any results.";
          if re.match(r"^(%s)$" % "|".join([
            "ERROR: !vprot: extension exception 0x80004002\.",
            "!vprot: No containing memory region found",
            "No export vprot found",
          ]), asMemoryProtectionInformation[0]):
            oBugReport.sBugTypeId = "AV%s:Unallocated" % sViolationTypeId;
            sBugDescription = "Access violation while %s unallocated memory at 0x%X." % (sViolationTypeDescription, uAddress);
            sSecurityImpact = "Potentially exploitable security issue, if the attacker can control the address or the memory at the address.";
          else:
            uAllocationStartAddress = None;
            uAllocationProtectionFlags = None;
            uAllocationSize = None;
            uStateFlags = None;
            uProtectionFlags = None;
            uTypeFlags = None;
            for sLine in asMemoryProtectionInformation:
              oLineMatch = re.match(r"^(\w+):\s+([0-9a-f]+)(?:\s+\w+)?$", sLine);
              assert oLineMatch, \
                  "Unrecognized memory protection information line: %s\r\n%s" % (sLine, "\r\n".join(asMemoryProtectionInformation));
              sInfoType, sValue = oLineMatch.groups();
              uValue = long(sValue, 16);
              if sInfoType == "BaseAddress":
                pass; # Appear to be the address rounded down to the nearest start of a page, i.e. not useful information.
              elif sInfoType == "AllocationBase":
                uAllocationStartAddress = uValue;
              elif sInfoType == "AllocationProtect":
                uAllocationProtectionFlags = uValue;
              elif sInfoType == "RegionSize":
                uAllocationSize = uValue;
              elif sInfoType == "State":
                uStateFlags = uValue;
              elif sInfoType == "Protect":
                uProtectionFlags = uValue;
              elif sInfoType == "Type":
                uTypeFlags = uValue;
            if oCdbWrapper.bGenerateReportHTML:
              oBugReport.atxMemoryRemarks.append(("Memory allocation start", uAllocationStartAddress, None));
              oBugReport.atxMemoryRemarks.append(("Memory allocation end", uAllocationStartAddress + uAllocationSize, None));
            if uStateFlags == 0x10000:
              oBugReport.sBugTypeId = "AV%s:Unallocated" % sViolationTypeId;
              sBugDescription = "Access violation while %s unallocated memory at 0x%X." % \
                  (sViolationTypeDescription, uAddress);
              sSecurityImpact = "Potentially exploitable security issue, if the attacker can control the address or the memory at the address.";
            elif uStateFlags == 0x2000: # MEM_RESERVE
  # These checks were added to make sure I understood exactly what was going on. However, it turns out that I don't
  # because these checks fail without me being able to understand why. So, I've decided to disable them and see what
  # happens. If you have more information that can help me make sense of this and improve it, let me know!
  #            assert uTypeFlags in [0x20000, 0x40000], \
  #                "Expected MEM_RESERVE memory to have type MEM_PRIVATE or MEM_MAPPED\r\n%s" % "\r\n".join(asMemoryProtectionInformation);
  #            # PAGE_READONLY !? Apparently...
  #            assert uProtectionFlags == 0x1 or uAllocationProtectionFlags in [0x1, 02], \
  #                "Expected MEM_RESERVE memory to have protection PAGE_NOACCESS or PAGE_READONLY\r\n%s" % "\r\n".join(asMemoryProtectionInformation);
              oBugReport.sBugTypeId = "AV%s:Reserved" % sViolationTypeId;
              sBugDescription = "Access violation while %s reserved but unallocated memory at 0x%X." % \
                  (sViolationTypeDescription, uAddress);
              sSecurityImpact = "Potentially exploitable security issue, if the address is attacker controlled.";
            elif uStateFlags == 0x1000: # MEM_COMMIT
              dsMemoryProtectionsDescription_by_uFlags = {
                0x01: "inaccessible",  0x02: "read-only",  0x04: "read- and writable",  0x08: "read- and writable",
                0x10: "executable", 0x20: "read- and executable", 0x40: "full-access", 0x80: "full-access"
              };
              sMemoryProtectionsDescription = dsMemoryProtectionsDescription_by_uFlags.get(uAllocationProtectionFlags);
              assert sMemoryProtectionsDescription, \
                  "Unexpected MEM_COMMIT memory to have protection value 0x%X\r\n%s" % \
                   (uAllocationProtectionFlags, "\r\n".join(asMemoryProtectionInformation));
              oBugReport.sBugTypeId = "AV%s:Arbitrary" % sViolationTypeId;
              sBugDescription = "Access violation while %s %s memory at 0x%X." % \
                  (sViolationTypeDescription, sMemoryProtectionsDescription, uAddress);
              sSecurityImpact = "Potentially exploitable security issue, if the address is attacker controlled.";
              if oCdbWrapper.bGenerateReportHTML:
                # Clamp size, potentially update start if size needs to shrink but end is not changed.
                uMemoryDumpStartAddress, uMemoryDumpSize = ftuLimitedAndAlignedMemoryDumpStartAddressAndSize(
                  uAddress, uPointerSize, uAllocationStartAddress, uAllocationSize
                );
                oBugReport.atxMemoryDumps.append(("Memory near access violation at 0x%X" % uAddress, \
                    uMemoryDumpStartAddress, uMemoryDumpSize));
            else:
              raise AssertionError("Unexpected memory state 0x%X\r\n%s" % \
                  (uStateFlags, "\r\n".join(asMemoryProtectionInformation)));

  oBugReport.sBugDescription = sBugDescription + sViolationTypeNotes;
  oBugReport.sSecurityImpact = sSecurityImpact;
  dtxBugTranslations = ddtxBugTranslations.get(oBugReport.sBugTypeId, None);
  if dtxBugTranslations:
    oBugReport = oBugReport.foTranslate(dtxBugTranslations);
  return oBugReport;
def cCdbWrapper_fbDetectAndReportVerifierErrors(oCdbWrapper, asCdbOutput):
    uErrorNumber = None
    uProcessId = None
    sMessage = None
    uHeapBlockAddress = None
    uHeapBlockSize = None
    uCorruptedStamp = None
    uCorruptionAddress = None
    asRelevantLines = []

    for sLine in asCdbOutput:
        # Ignore exmpty lines
        if not sLine:
            continue
        # Look for the first VERIFIER STOP message and gather information
        if uErrorNumber is None:
            oErrorMessageMatch = re.match(
                r"^VERIFIER STOP ([0-9A-F]+): pid 0x([0-9A-F]+): (.*?)\s*$",
                sLine)
            if oErrorMessageMatch:
                sErrorNumber, sProcessId, sMessage = oErrorMessageMatch.groups(
                )
                uErrorNumber = long(sErrorNumber, 16)
                uProcessId = long(sProcessId, 16)
            asRelevantLines.append(sLine)
            continue
        asRelevantLines.append(sLine)
        # A VERIFIER STOP message has been detected, gather what information verifier provides:
        oInformationMatch = re.match(r"\t([0-9A-F]+) : (.*?)\s*$", sLine)
        if oInformationMatch:
            sValue, sDescription = oInformationMatch.groups()
            uValue = long(sValue, 16)
            if sDescription == "Heap block": uHeapBlockAddress = uValue
            elif sDescription == "Block size": uHeapBlockSize = uValue
            elif sDescription == "Corrupted stamp": uCorruptedStamp = uValue
            elif sDescription == "corruption address":
                uCorruptionAddress = uValue
        else:
            assert sLine.strip().replace("=", "") == "", \
                "Unknown VERIFIER STOP message line: %s" % repr(sLine)
            break
    else:
        assert uErrorNumber is None, \
            "Detected the start of a VERIFIER STOP message but not the end\r\n%s" % "\r\n".join(asLines)
        return False

    uHeapBlockEndAddress = uHeapBlockAddress + uHeapBlockSize
    uHeapPageEndAddress = (uHeapBlockEndAddress | 0xFFF) + 1
    assert uHeapPageEndAddress >= uHeapBlockEndAddress, \
        "The heap block at 0x%X is expected to end at 0x%X, but the page is expected to end at 0x%X, which is impossible." % \
        (uHeapBlockAddress, uHeapBlockEndAddress, uHeapPageEndAddress)
    oCorruptionDetector = cCorruptionDetector(oCdbWrapper)
    # End of VERIFIER STOP message; report a bug.
    if sMessage in ["corrupted start stamp", "corrupted end stamp"]:
        assert uCorruptionAddress is None, \
            "We do not expect the corruption address to be provided in the VERIFIER STOP message"
        sBugTypeId = "OOBW"
        # Both the start and end stamp may have been corrupted and it appears that a bug in verifier causes a corruption
        # of the end stamp to be reported as a corruption of the start stamp, so we'll check both for unexpected values:
        uPointerSize = oCdbWrapper.fuGetValue("$ptrsize")
        if not oCdbWrapper.bCdbRunning: return
        # https://msdn.microsoft.com/en-us/library/ms220938(v=vs.90).aspx
        uEndStampAddress = uHeapBlockAddress - uPointerSize
        # ULONG with optional padding to pointer size
        if uPointerSize == 8:
            # End stamp comes immediately after other header values
            oCorruptionDetector.fDetectCorruption(uEndStampAddress, 0, 0, 0, 0,
                                                  [0xBA, 0xBB], 0xBB, 0xBA,
                                                  0xDC)
        else:
            oCorruptionDetector.fDetectCorruption(uEndStampAddress,
                                                  [0xBA, 0xBB], 0xBB, 0xBA,
                                                  0xDC)
        uStackTraceAddress = uEndStampAddress - uPointerSize
        # PVOID
        uFreeQueueAddress = uStackTraceAddress - 2 * uPointerSize
        # LIST_ENTRY
        uActualSizeAddress = uFreeQueueAddress - uPointerSize
        # size_t
        uRequestedSizeAddress = uActualSizeAddress - uPointerSize
        # size_t
        uHeapAddressAddress = uRequestedSizeAddress - uPointerSize
        # PVOID
        uStartStampAddress = uHeapAddressAddress - uPointerSize
        # ULONG with optional padding to pointer size
        if uPointerSize == 8:
            # End stamp comes immediately before other header values
            oCorruptionDetector.fDetectCorruption(uStartStampAddress,
                                                  [0xBA, 0xBB], 0xBB, 0xCD,
                                                  0xAB, 0, 0, 0, 0)
        else:
            oCorruptionDetector.fDetectCorruption(uStartStampAddress,
                                                  [0xBA, 0xBB], 0xBB, 0xCD,
                                                  0xAB)
        assert oCorruptionDetector.bCorruptionDetected, \
            "Cannot find any sign of corruption"
    elif sMessage == "corrupted suffix pattern":
        assert uCorruptionAddress is not None, \
            "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \
                "\r\n" % (asRelevantLines)
        # Page heap stores the heap as close as possible to the edge of a page, taking into account that the start of the
        # heap block must be properly aligned. Bytes between the heap block and the end of the page are initialized to
        # 0xD0. Verifier has detected that one of the bytes changed value, which indicates an out-of-bounds write. BugId
        # will try to find all bytes that were changed:
        sBugTypeId = "OOBW"
        uPaddingSize = uHeapPageEndAddress - uHeapBlockEndAddress
        oCorruptionDetector.fDetectCorruption(
            uHeapBlockEndAddress, *[0xD0 for x in xrange(uPaddingSize)])
        assert oCorruptionDetector.bCorruptionDetected, \
            "Cannot find any sign of corruption"
    elif sMessage == "corrupted infix pattern":
        assert uCorruptionAddress is not None, \
            "The corruption address is expected to be provided in the VERIFIER STOP message:\r\n%s" % \
                "\r\n" % (asRelevantLines)
        # Page heap sometimes does not free a heap block immediately, but overwrites the bytes with 0xF0. Verifier has
        # detected that one of the bytes changed value, which indicates a write-after-free. BugId will try to find all
        # bytes that were changed:
        sBugTypeId = "UAFW"
        oCorruptionDetector.fDetectCorruption(
            uHeapBlockAddress, *[0xF0 for x in xrange(uHeapBlockSize)])
        assert oCorruptionDetector.bCorruptionDetected, \
            "Cannot find any sign of corruption"
    else:
        sBugTypeId = "HeapCorrupt"
    if uHeapBlockSize is not None:
        sBugTypeId += "[%s]" % (fsGetNumberDescription(uHeapBlockSize))
    if oCorruptionDetector.bCorruptionDetected:
        if uCorruptionAddress is None:
            uCorruptionAddress = oCorruptionDetector.uCorruptionStartAddress
        else:
            assert uCorruptionAddress == oCorruptionDetector.uCorruptionStartAddress, \
                "Verifier reported corruption at address 0x%X but BugId detected corruption at address 0x%X" % \
                (uCorruptionAddress, oCorruptionDetector.uCorruptionStartAddress)
    if uCorruptionAddress is not None:
        sMessage = "heap corruption"
        uCorruptionOffset = uCorruptionAddress - uHeapBlockAddress
        if uCorruptionOffset >= uHeapBlockSize:
            uCorruptionOffset -= uHeapBlockSize
            sOffsetDescription = "%d/0x%X bytes beyond" % (uCorruptionOffset,
                                                           uCorruptionOffset)
            sBugTypeId += fsGetOffsetDescription(uCorruptionOffset)
        elif uCorruptionOffset > 0:
            sOffsetDescription = "%d/0x%X bytes into" % (uCorruptionOffset,
                                                         uCorruptionOffset)
            sBugTypeId += "@%s" % fsGetNumberDescription(uCorruptionOffset)
        else:
            sOffsetDescription = "%d/0x%X bytes before" % (-uCorruptionOffset,
                                                           -uCorruptionOffset)
            sBugTypeId += fsGetOffsetDescription(uCorruptionOffset)
        sBugDescription = "Page heap detected %s at 0x%X; %s a %d/0x%X byte heap block at address 0x%X" % \
            (sMessage, uCorruptionAddress, sOffsetDescription, uHeapBlockSize, uHeapBlockSize, uHeapBlockAddress)
        uRelevantAddress = uCorruptionAddress
    else:
        sBugDescription = "Page heap detected %s in a %d/0x%X byte heap block at address 0x%X." % \
            (sMessage, uHeapBlockSize, uHeapBlockSize, uHeapBlockAddress)
        uRelevantAddress = uHeapBlockAddress

    # If we detected corruption by scanning certain bytes in the applications memory, make sure this matches what
    # verifier reported and save all bytes that were affected: so far, we only saved the bytes that had an unexpected
    # value, but there is a chance that a byte was overwritten with the same value it has before, in which case it was
    # not saved. This can be detect if it is surrounded by bytes that did change value. This code reads the value of all
    # bytes between the first and last byte that we detected was corrupted:
    asCorruptedBytes = oCorruptionDetector.fasCorruptedBytes()
    if asCorruptedBytes:
        sBugDescription += " The following byte values were written to the corrupted area: %s." % ", ".join(
            asCorruptedBytes)
        sBugTypeId += oCorruptionDetector.fsCorruptionId() or ""

    sSecurityImpact = "Potentially exploitable security issue, if the corruption is attacker controlled"
    oCdbWrapper.oBugReport = cBugReport.foCreate(oCdbWrapper, sBugTypeId,
                                                 sBugDescription,
                                                 sSecurityImpact)
    oCdbWrapper.oBugReport.duRelevantAddress_by_sDescription \
        ["memory corruption at 0x%X" % uRelevantAddress] = uRelevantAddress
    oCdbWrapper.oBugReport.bRegistersRelevant = False
    return True