Troubleshooting CRT heap corruption

This article focuses on troubleshooting a heap corruption caused by writing into the next allocated block. Heap corruption comes into notice when the overridden memory is accessed, leaving in a state where it becomes hard to figure out the original code that is overriding the memory.

In this scenario, call stack could look like

0:000> kp

ChildEBP RetAddr

0:000> kb

ChildEBP RetAddr Args to Child

0012c2f4 7c91a3bc 01660000 01d0b000 0012c320 ntdll!RtlpFindAndCommitPages+0x116

0012c32c 7c911917 01660000 00000300 0012eb04 ntdll!RtlpExtendHeap+0xa6

0012c55c 00f4103e 01660000 00000000 000002f4 ntdll!RtlAllocateHeap+0x623

0012c578 00f4fd76 000002f4 5a7fcbb3 0012eb04 msvcr90d!_heap_alloc_base+0x5e

0012c5c0 00f4fb2f 000002d0 00000001 00000000 msvcr90d!_nh_malloc_dbg+0x2c6

0012c5e0 00f4fadc 000002d0 00000000 00000001 msvcr90d!_nh_malloc_dbg+0x7f

0012c608 00f5b25b 000002d0 00000000 00000001 msvcr90d!_nh_malloc_dbg+0x2c

0012c628 00f3d691 000002d0 cccccccc cccccccc msvcr90d!malloc+0x1b

0012c644 007ab543 000002d0 cccccccc 0012c690 msvcr90d!operator new+0x11

0012eb14 0073b367 MyApp!DummyFun

01660000 is the heap used by msvcr90d.dll in this case

0:000> !heap -av 01660000

Index Address Name Debugging options enabled

 12: 01660000

    Segment at 01660000 to 01670000 (00010000 bytes committed)

    Segment at 01d00000 to 01e00000 (00011000 bytes committed)

    Flags: 00001002

    ForceFlags: 00000000

    Granularity: 8 bytes

    Segment Reserve: 00200000

    Segment Commit: 00002000

    DeCommit Block Thres: 00000200

    DeCommit Total Thres: 00002000

    Total Free Size: 00000089

                -

                -

                -

        0166e8e0: 00048 . 00300 [01] - busy (2f4)

        0166ebe0: 00300 . 00048 [01] - busy (3c)

        0166ec28: 00048 . 00048 [01] - busy (3c)

        0166ec70: 00048 . 00048 [01] - busy (3c)

        0166ecb8: 00048 . 00300 [01] - busy (2f4)

        0166efb8: 00300 . 00030 [01] - busy (25)

        0166efe8: 281a8 . 17188 [01] - busy (1717c)

   

##CORRUPTION FOUND at 0x0166EFE8

    PreviousSize field does not match Size field in previous entry

    Entry->PreviousSize == 0x2e31

    PreviousEntry->Size == 0x60

##The above errors were found in segment at 0x01660640

Looking at the 0166efe8 memory block, it is clear that the current size of previous block ‘00030’ is not matching with the previous size ‘281a8’. This infers that 0166efe8 is corrupted and previous size field is overridden by some data.

_ CrtMemBlockHeader is the block header structure used to store the debug heap's bookkeeping information, for more information msdn.microsoft.com/en-us/library/bebs9zyz.aspx

0:000> dt msvcr90d!_CrtMemBlockHeader 0166efb8+8

   +0x000 pBlockHeaderNext : 0x0166ecc0 _CrtMemBlockHeader

   +0x004 pBlockHeaderPrev : 0x0166eff0 _CrtMemBlockHeader

   +0x008 szFileName : (null)

   +0x00c nLine : 0

   +0x010 nDataSize : 1

   +0x014 nBlockUse : 1

   +0x018 lRequest : 3686

   +0x01c gap : [4] "???"

0:000> dt msvcr90d!_CrtMemBlockHeader 0166efe8+8

   +0x000 pBlockHeaderNext : 0x0166efc0 _CrtMemBlockHeader

   +0x004 pBlockHeaderPrev : 0x0166f028 _CrtMemBlockHeader

   +0x008 szFileName : (null)

   +0x00c nLine : 0

   +0x010 nDataSize : 8

   +0x014 nBlockUse : 1

   +0x018 lRequest : 3687

   +0x01c gap : [4] "???"

The reason behind adding 8 bytes is that, each memory block in a given segment has 8 bytes metadata associated with it. The metadata is used by the heap manager to effectively manage the heap blocks within a segment.

lRequest is the allocation count. The above msvcr90d!_CrtMemBlockHeader structures show that they are adjacent allocations (3686 & 3687). If the corruption is in the same spot and allocation numbers are sequential, _CrtSetBreakAlloc(3686) can be used to determine the code that is overriding the memory 0166efe8 and how that block is being corrupted.

Call _CrtSetBreakAlloc(3686) at the code startup for ex: main(), start debugging(F5) the code and this should break the debugger when 0166efb8 memory block is allocated. Once the debugger breaks, code could be reviewed for two things

1) How many bytes are allocated

2) Are there any chances that suspected buffer could overflow.

For Example:

char *ptr = new char;

strcpy(ptr, source); //where ‘source’ variable holds more than one character.

Please make a note that 3686 & 3687 may be different when its debugged second time, but it’s very rare case.