Archive for August 18th, 2010

Webinar: Fundamentals of Complete Crash and Hang Memory Dump Analysis (Second Session)

Wednesday, August 18th, 2010

Do to the attendee limit not all registered for the first session were able to attend. The second session is available:

Date: 23rd of August 2010
Time: 19:00 (BST) 14:00 (Eastern) 11:00 (Pacific)
Duration: 90 minutes

Space is limited.
Reserve your Webinar seat now at:
https://www1.gotomeeting.com/register/823155577

After the second session webinar slides will be published and later a Q&A page will be compiled.

- Dmitry Vostokov @ DumpAnalysis.org + TraceAnalysis.org -

Modeling C++ Object Corruption

Wednesday, August 18th, 2010

Having encountered several crash dumps with the code running on heap and the following similar stack traces

1: kd> k
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  Args to Child             
WARNING: Frame IP not in any known module. Following frames may be wrong.
02cdfbfc 0056511a 0x634648
02cdfc24 005651a1 ModuleA!ClassA::~ClassA+0x5a
02cdfc30 00562563 ModuleA!ClassA::`scalar deleting destructor'+0x11
[...]
02cdffec 00000000 kernel32!BaseThreadStart+0x37

I decided to model this situation. The idea was to corrupt a class member by overriding its vtable pointer with a heap entry address. Because the virtual destructor address is a first virtual method table entry in our class memory layout I made sure that it points to the same heap address by making vtable pointer a dereference fixpoint. Here is a source code based on how Visual C++ compiler implements objects in memory:

class Member {
public:
    virtual ~Member() { data = 1; };
public:
    int data;
};

class Compound {
public:
    Compound(): pm(NULL) { pm = new Member(); }
    virtual ~Compound() { delete pm; }
    void Corrupt() {
        unsigned int * pbuf = new unsigned int[0x10];
        *pbuf = reinterpret_cast<unsigned int>(pbuf); // to ensure that the code would run through pbuf pointer
        *reinterpret_cast<unsigned int *>(pm) = reinterpret_cast<unsigned int>(pbuf);
    }
    Member *pm;
};

int _tmain(int argc, _TCHAR* argv[])
{
    Compound *pc = new Compound();
    pc->Corrupt();
    delete pc;
    return 0;
}

In a crash dump we therefore see the similar stack trace:

0:000> .ecxr
eax=001f4c28 ebx=7efde000 ecx=001f4c18 edx=001f4c28 esi=00000000 edi=00000000
eip=001f4c28 esp=003cf7d0 ebp=003cf7e8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
001f4c28 284c1f00        sub     byte ptr [edi+ebx],cl      ds:002b:7efde000=00

0:000> k
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  Args to Child             
WARNING: Frame IP not in any known module. Following frames may be wrong.
003cf7cc 011d10e5 0×1f4c28
003cf7e8 011d114f Destructors!Compound::~Compound+0×35
003cf7f4 011d121e Destructors!Compound::`scalar deleting destructor’+0xf
003cf82c 011d1498 Destructors!wmain+0×8e
003cf874 77043677 Destructors!__tmainCRTStartup+0xfa
003cf880 77719d72 kernel32!BaseThreadInitThunk+0xe
003cf8c0 77719d45 ntdll!__RtlUserThreadStart+0×70
003cf8d8 00000000 ntdll!_RtlUserThreadStart+0×1b

We now check the correctness of the stack trace by examining the return addresses:

0:000> .asm no_code_bytes
Assembly options: no_code_bytes

0:000> ub 011d10e5
Destructors!Compound::~Compound+0×21:
011d10d1 cmp  dword ptr [ebp-4],0
011d10d5 je   Destructors!Compound::~Compound+0×3a (011d10ea)
011d10d7 push 1
011d10d9 mov  ecx,dword ptr [ebp-4]
011d10dc mov  edx,dword ptr [ecx]
011d10de mov  ecx,dword ptr [ebp-4]
011d10e1 mov  eax,dword ptr [edx]
011d10e3 call eax

0:000> ub 011d114f
Destructors!Compound::Corrupt+0×3e:
011d113e int  3
011d113f int  3
Destructors!Compound::`scalar deleting destructor’:
011d1140 push ebp
011d1141 mov  ebp,esp
011d1143 push ecx
011d1144 mov  dword ptr [ebp-4],ecx
011d1147 mov  ecx,dword ptr [ebp-4]
011d114a call Destructors!Compound::~Compound (011d10b0)

We now examine the crash address:

0:000> u 001f4c28
001f4c28 sub  byte ptr [edi+ebx],cl
001f4c2c les  eax,fword ptr [eax]
001f4c2e pop  ds
001f4c2f add  byte ptr [eax],al
001f4c31 add  byte ptr [eax],al
001f4c33 add  byte ptr [eax],al
001f4c35 add  byte ptr [eax],al
001f4c37 add  byte ptr [eax],al

Then we check that it resides in a heap segment:

0:000> dt _PEB 7efde000
Destructors!_PEB
[...]
  +0x088 NumberOfHeaps : 2
  +0x08c MaximumNumberOfHeaps : 0x10
  +0x090 ProcessHeaps : 0x777e4740 -> 0x004b0000 Void
[...]

0:000> dd 0x777e4740 l2
777e4740 004b0000 001f0000

0:000> !heap 001f0000
Index Address Name Debugging options enabled
  2: 001f0000
  Segment at 001f0000 to 00200000 (00005000 bytes committed)

Now we check vtable to see that it was normal for Compound object but corrupt for Member object:

0:000> .frame 1
01 003cf7e8 011d114f Destructors!Compound::~Compound+0x35

0:000> dv /i /V
prv local  003cf7dc @ebp-0x0c            this = 0x001f4c08
0:000> dt Destructors!Compound 0x001f4c08
   +0x000 __VFN_table : 0x011daa0c
   +0x004 pm               : 0x001f4c18 Member

0:000> dps 0x001f4c08 l1
001f4c08  011daa0c Destructors!Compound::`vftable'

0:000> dps 0x001f4c18 l1
001f4c18  001f4c28

0:000> dps 001f4c28 l1
001f4c28  001f4c28

The application, its source code and PDB file are available for download here.

- Dmitry Vostokov @ DumpAnalysis.org + TraceAnalysis.org -