Hello, my name is Graham, and I’m an escalation engineer on the Platforms Global Escalation Team. I recently worked a case where a group of Windows XP machines were hitting a bugcheck on boot, error 0xC000021A. This error occurs when a critical usermode process such as winlogon or csrss crashes. I had access to a failing machine, so I attached the kernel debugger to find out why winlogon was crashing. I found the cause, and a little bit more about Data Execution Prevention (DEP) in the process.
The initial debugger spew gave me this information:
*** An Access Violation occurred in winlogon.exe:
The instruction at 10030F90 tried to write to an invalid address, 10030F90
*** enter .exr 0006F4AC for the exception record
*** enter .cxr 0006F4C8 for the context
*** then kb to get the faulting stack
So I followed its cue and got the exception record and context record:
1: kd> .exr 0006F4AC
ExceptionCode: c0000005 (Access violation)
Attempt to execute non-executable address 10030f90
Ahh, OK, so we know this is a DEP crash now.
1: kd> .cxr 0006F4C8
eax=00000400 ebx=00000000 ecx=00000000 edx=00010000 esi=00000000 edi=00084370
eip=10030f90 esp=0006f794 ebp=0006f81c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
001b:10030f90 33c0 xor eax,eax
Let's check out the crashing stack to see what's going on:
1: kd> kb
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
0006f81c 010297c1 00084370 01010ab4 00000000 3rdparty!nosymbols
0006fcfc 010312a6 00072364 7c80b6a1 00000000 winlogon!ExecSystemProcesses+0x14d
0006ff50 0103d4d0 01000000 00000000 00072364 winlogon!WinMain+0x2b6
0006fff4 00000000 7ffd7000 000000c8 000001ec winlogon!WinMainCRTStartup+0x174
The first thing I decided to look for was how we got to this address. To begin, I unassembled the code right before the return address to winlogon!ExecSystemProcesses.
kd> ub 010297c1
010297a2 6a02 push 2
010297a4 ffb594fbffff push dword ptr [ebp-46Ch]
010297aa 6880000000 push 80h
010297af 56 push esi
010297b0 56 push esi
010297b1 68b40a0101 push offset winlogon!`string' (01010ab4)
010297b6 ffb5a0fbffff push dword ptr [ebp-460h]
010297bc e891fcffff call winlogon!StartSystemProcess (01029452)
According to the stack, winlogon!ExecSystemProcesses didn't call the function currently running. So, I suspected some hooking was going on. Using !chkimg, I verified this was the case. Note that chkimg requires a valid copy of the binary in the symbol path.
1: kd> !chkimg -db kernel32
10 errors : kernel32 (7c802332-7c80236b)
7c802330 90 90 *e9 *59 *ec *82 *93 6a 00 ff 75 2c ff 75 28 ff ...Y...j..u,.u(.
7c802360 28 00 90 90 90 90 90 *e9 *d4 *eb *82 *93 6a 00 ff 75 (...........j..u
1: kd> u 7c802330
7c802330 90 nop
7c802331 90 nop
7c802332 e959ec8293 jmp 3rdparty!nosymbols (10030f90)
Aha! Something has hooked CreateProcessW to jump to our current instruction. Now that we know how we got there, let's understand why we crashed. Since DEP fired, that means this address is non-executable. I verified this by dumping out the PTE for the address.
1: kd> !pte 10030F90
PDE at 00000000C0600400 PTE at 00000000C0080180
contains 000000004E102867 contains 800000004E021867
pfn 4e102 ---DA--UWEV pfn 4e021 ---DA--UW-V
Notice that in the protection flags for the PTE, the 'E' bit isn't set, saying this page isn't executable. So, where is this address we were trying to execute? Many times with DEP crashes this will be in stack or heap memory. But not this time. In this case, the address is actually in a module's memory mapped address space, as shown by the 'lm' command
1: kd> lm m 3rdparty
10000000 1003c000 3rdparty C (export symbols) 3rdparty.dll
Hmm... So the address falls in this module. Why isn't it executable? Usually when I think of image files, I think of running code. But, remembering back to how the PE images are laid out, a module is broken into subsections, with different types of data in each one, and different protection levels. There's a place in the image for code, and for data, such as global variables and static data. So, let's dump the image header and find which section offset 0x30F90 is in.
1: kd>!dh 3rdparty
SECTION HEADER #3
1EE3C virtual size
1A000 virtual address // (1A000+1EE3C=0x38e3c so mem range for section is 1A000 to 0x38e3c)
3000 size of raw data
1A000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
(no align specified)
Read Write // no Execute !
This is our section, since the virtual address starts at 0x1A000 and is 0x1EE3C in size, putting the end of the section at 0x38e3c. Our address of 0x30F90 falls between them.
Sure enough, this section is labeled as "Initialized Data", and the protection flags show Read and Write, but no Execute! So, this address is not in a code section of the module, and DEP will not allow it to run.
Knowing this, I was able to find an update on the 3rd party manufacturer's site that modified their DLL to prevent this from occurring. Mystery solved!