Partager via


Kernel dump analysis - Bugcheck 1E (KMODE_EXCEPTION_NOT_HANDLED)

It’s been a long time since my last post, but for some reason lately I’ve been receiving so many nice feedbacks about the blog and the other posts that I feel really motivated again to post a new article. See how important your feedback is for me? J

Ok, normally I post about user mode debugging, but last week I was working on developing some content and I got this kernel memory dump I decided to take the risk of analyzing it, even considering Kernel debugging is not my specialty.

Normally the first step when you’re analyzing dumps resultant from Blue Screens (I will write about the differences between kernel mode dumps and user mode dumps in another post) is to run the command !analyze –v. Well, for this specific dump, !analyze –v is just not pointing to the right driver, at least I did not think so and that’s what motivated me to make a manual analysis of this specific dump. Ok, too much said and very little action here… Let’s start with some hands on!

After opening the dump with Windbg.exe, setting the correct symbol path and running the command !analyze –v, this is what we have in summary:

KMODE_EXCEPTION_NOT_HANDLED (1e)

Arguments:

Arg1: c0000005, The exception code that was not handled

Arg2: a012a6c5, The address that the exception occurred at

Arg3: 00000000, Parameter 0 of the exception

Arg4: 00000014, Parameter 1 of the exception

FAULTING_IP:

win32k!DdDxApiOpenCaptureDevice+18

a012a6c5 8b5814 mov ebx,dword ptr [eax+14h]

EXCEPTION_PARAMETER1: 00000000

EXCEPTION_PARAMETER2: 00000014

READ_ADDRESS: 00000014

DEFAULT_BUCKET_ID: DRIVER_FAULT

BUGCHECK_STR: 0x1E

FOLLOWUP_IP:

Dxapi!DxApi+43

bdae59f7 8bc6 mov eax,esi

SYMBOL_STACK_INDEX: 3

SYMBOL_NAME: Dxapi!DxApi+43

FOLLOWUP_NAME: MachineOwner

MODULE_NAME: Dxapi

IMAGE_NAME: Dxapi.sys

DEBUG_FLR_IMAGE_TIMESTAMP: 382b8097

FAILURE_BUCKET_ID: 0x1E_Dxapi!DxApi+43

BUCKET_ID: 0x1E_Dxapi!DxApi+43

Followup: MachineOwner

So it’s telling us a lot of useful information like what was the bugcheck (1E), what kind of unhandled exception happened (Access Violation – 0xC0000005) and what was the instruction pointer (IP) when the exception happened. In this case it was address 0xa012a6c5. It’s also pointing to the driver Dxapi.sys as the probable guilty for the problem. Well, my curiosity on that was exactly because the instruction pointer was pointing to an address that doesn’t belong to Dxapi.sys. Let’s check that:

kd> lmvm Dxapi

start end module name

bdae4000 bdae6640 Dxapi (pdb symbols) c:\debuggers\sym\dxapi.pdb\382B80971\dxapi.pdb

    Loaded symbol image file: c:\debuggers\sym\Dxapi.dbg\382B80972640\Dxapi.dbg

    Image path: \SystemRoot\System32\DRIVERS\Dxapi.sys

    Image name: Dxapi.sys

    Timestamp: Fri Nov 12 00:51:03 1999 (382B8097)

    CheckSum: 0000730A

    ImageSize: 00002640

    File version: 5.0.2180.1

    Product version: 5.0.2180.1

    File flags: 0 (Mask 3F)

    File OS: 40004 NT Win32

    File type: 3.7 Driver

    File date: 00000000.00000000

    Translations: 0409.04b0

    CompanyName: Microsoft Corporation

    ProductName: Microsoft(R) Windows (R) 2000 Operating System

    InternalName: dxapi.sys

    OriginalFilename: dxapi.sys

    ProductVersion: 5.00.2180.1

    FileVersion: 5.00.2180.1

    FileDescription: DirectX API Driver

    LegalCopyright: Copyright (C) Microsoft Corp. 1981-1999

By checking the base address (yellow) of the module and its end address (green) we notice that the address for the instruction pointers is not in between them. Actually the instruction point address was from Win32k.sys:

kd> lmvm win32k

start end module name

a0000000 a01a5e60 win32k (pdb symbols) c:\debuggers\sym\win32k.pdb\38E910C02\win32k.pdb

    Loaded symbol image file: c:\debuggers\sym\win32k.dbg\3947E2231a5e60\win32k.dbg

    Image path: \??\D:\WINNT\system32\win32k.sys

    Image name: win32k.sys

    Timestamp: Wed Jun 14 16:50:59 2000 (3947E223)

    CheckSum: 001B02D1

    ImageSize: 001A5E60

    Translations: 0000.04b0 0000.04e0 0409.04b0 0409.04e0

So the address 0xa012a6c5 is indeed in between those addresses which means it was an instruction from this driver (Win32k.sys) that tried to an illegal operation. But let’s wait a little bit before blaming the poor kernel image of the Win32 Subsystem (This is basically what the Win32k.sys is). Let’s check our assembly code and try to see what really happened here…

Checking our thread’s stack:

kd> k

ChildEBP RetAddr

bd2858a0 80463b2b nt!KiDispatchException+0x30e

bd285908 80463add nt!CommonDispatchException+0x4d

bd285908 a012a6c5 nt!KiUnexpectedInterruptTail+0x1f4

bd2859b4 f0593a8e Dxapi!DxApi+0x43

WARNING: Stack unwind information not available. Following frames may be wrong.

bd285a14 f0592bb4 3dfxV3TV+0x3a8e

bd285a34 f05928cc 3dfxV3TV+0x2bb4

bd285a4c f05a0cfd 3dfxV3TV+0x28cc

00000000 00000000 STREAM!SCIssueRequestToDevice+0xad

This actually happened after the exception happened – It was dispatching the exception. The original context (call stack and registers) was actually saved on a trap frame. From the debugger help we get the information which states that the third parameter passed to the function KiDispatchException is a pointer to our trap frame. The command .trap will load the context saved on the trap.

kd> kb1

ChildEBP RetAddr Args to Child

bd2858a0 80463b2b bd2858bc 00000000 bd285910 nt!KiDispatchException+0x30e

kd> .trap bd285910

ErrCode = 00000000

eax=00000000 ebx=84f0ba10 ecx=a012a6ad edx=00000020 esi=bd2859dc edi=80d724a8

eip=a012a6c5 esp=bd285984 ebp=bd2859a0 iopl=0 nv up ei pl zr na pe nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246

win32k!DdDxApiOpenCaptureDevice+0x18:

a012a6c5 8b5814 mov ebx,dword ptr [eax+14h] ds:0023:00000014=????????

Ok, now we have the right context where the exception happened. Notice that our current EIP on this context matches the one pointed by the !analyze –v as the one where the exception happened (in red above). We basically crashed because we’re trying to move the content pointed by EAX+0x14 to EBX. Since EAX = 0 (green above) we failed when trying to read from the invalid address 0x00000014 (which is the resultant address from 0x0 + 0x14).

Let’s look at the assembly code that execute immediately before this a012a6c5.

kd> ub a012a6c8

win32k!DdDxApiOpenCaptureDevice+0x7:

a012a6b4 56 push esi

a012a6b5 8b7508 mov esi,dword ptr [ebp+8]

a012a6b8 57 push edi

a012a6b9 8365fc00 and dword ptr [ebp-4],0

a012a6bd 8b06 mov eax,dword ptr [esi]

a012a6bf 8b7814 mov edi,dword ptr [eax+14h]

a012a6c2 8b4604 mov eax,dword ptr [esi+4]

a012a6c5 8b5814 mov ebx,dword ptr [eax+14h] ß This is where we crashed

So we need to find out on the assembly block above where EAX gets set to 0x0.

kd> ub a012a6c8

win32k!DdDxApiOpenCaptureDevice+0x7:

a012a6b4 56 push esi

a012a6b5 8b7508 mov esi,dword ptr [ebp+8]

a012a6b8 57 push edi

a012a6b9 8365fc00 and dword ptr [ebp-4],0

a012a6bd 8b06 mov eax,dword ptr [esi]

a012a6bf 8b7814 mov edi,dword ptr [eax+14h]

a012a6c2 8b4604 mov eax,dword ptr [esi+4] ß This is where EAX gets updated for the last time before the crash

a012a6c5 8b5814 mov ebx,dword ptr [eax+14h]

So EAX is actually receiving its value from the content of ESI+4. Following the same logic we need to find out where ESI is getting updated for the last time before being involved on the instruction with EAX.

kd> ub a012a6c8

win32k!DdDxApiOpenCaptureDevice+0x7:

a012a6b4 56 push esi

a012a6b5 8b7508 mov esi,dword ptr [ebp+8] ß This where ESI gets updated for the last time before the instruction at a012a6c2

a012a6b8 57 push edi

a012a6b9 8365fc00 and dword ptr [ebp-4],0

a012a6bd 8b06 mov eax,dword ptr [esi]

a012a6bf 8b7814 mov edi,dword ptr [eax+14h]

a012a6c2 8b4604 mov eax,dword ptr [esi+4]

a012a6c5 8b5814 mov ebx,dword ptr [eax+14h]

So ESI is getting its value from the content of EBP+8. EBP+8 happens to be a fixed position on the stack used as a reference for the first parameter passed to the function (considering the calling convention used is not fast call). EBP+8 being the first parameter, EBP+0xC the second, EBP+0x10 the third and so on… There are other pre-defined positions on the stack you should be aware of like EBP+4 is where the return address gets saved or EBP-4 for the first local variable etc, but this is subject for another post…

Let’s confirm our theory:

kd> dd poi(ebp+8)+4 l1

bd2859e0 00000000

kd> dd ebp+8 l1

bd2859a8 bd2859dc

Ok, so this is really where the 0x0 on EAX is coming from. At this point we exclude Win32K.sys from our list of suspects after all it’s just receiving an invalid parameter being passed from the previous function on the stack:

kd> kb3

ChildEBP RetAddr Args to Child

bd2859a0 bdae59f7 bd2859dc bd285a0c 00028bb1 win32k!DdDxApiOpenCaptureDevice+0x18

bd2859b4 f0593a8e 00000513 bd2859dc 00000020 Dxapi!DxApi+0x43

bd285a14 f0592bb4 81c83228 84f0ba10 822efac8 3dfxV3TV+0x3a8e

As we can see from the stack above, the first parameter passed to the Win32k!DdDxApiOpenCaptureDevice is really the one with the corrupted value. The parameter is being passed from Dxapi!dDxApi.

Now take at that stack again… Pay attention to the details on the stack.. Can you give me a reason for excluding DxApi.sys from our suspect list also? No? Ok, let’s do it on the funniest way then J, shall we?

Now we need to look at the assembly code of Dxapi!Dxapi. Here is the entire assembly block for such function:

kd> uf Dxapi!DxApi

Dxapi!DxApi:

bdae59b4 55 push ebp

bdae59b5 8bec mov ebp,esp

bdae59b7 8b4508 mov eax,dword ptr [ebp+8]

bdae59ba 56 push esi

bdae59bb 2d00050000 sub eax,500h

bdae59c0 33f6 xor esi,esi

bdae59c2 83f816 cmp eax,16h

bdae59c5 7330 jae Dxapi!DxApi+0x43 (bdae59f7)

Dxapi!DxApi+0x13:

bdae59c7 8d0440 lea eax,[eax+eax*2]

bdae59ca c1e002 shl eax,2

bdae59cd 8b88005baebd mov ecx,dword ptr Dxapi!gDxApiEntryPoint (bdae5b00)[eax]

Here we’re loading ECX whith the pointer to an external function – In this case this is the function Win32k!DdDxApiOpenCaptureDevice

bdae59d3 85c9 test ecx,ecx

bdae59d5 7420 je Dxapi!DxApi+0x43 (bdae59f7)

Dxapi!DxApi+0x23:

bdae59d7 8b5510 mov edx,dword ptr [ebp+10h]

bdae59da 3b90045baebd cmp edx,dword ptr Dxapi!gDxApiEntryPoint+0x4 (bdae5b04)[eax]

bdae59e0 7215 jb Dxapi!DxApi+0x43 (bdae59f7)

Dxapi!DxApi+0x2e:

bdae59e2 8b80085baebd mov eax,dword ptr Dxapi!gDxApiEntryPoint+0x8 (bdae5b08)[eax]

bdae59e8 394518 cmp dword ptr [ebp+18h],eax

bdae59eb 720a jb Dxapi!DxApi+0x43 (bdae59f7)

Dxapi!DxApi+0x39:

bdae59ed ff7514 push dword ptr [ebp+14h]

bdae59f0 8bf0 mov esi,eax

bdae59f2 ff750c push dword ptr [ebp+0Ch]

bdae59f5 ffd1 call ecx  ß Here is where the function effectively gets called

Dxapi!DxApi+0x43:

bdae59f7 8bc6 mov eax,esi  ß This is our return address as you check on the stack

bdae59f9 5e pop esi

bdae59fa 5d pop ebp

bdae59fb c21400 ret 14h

Look at this specific part of the block:

Dxapi!DxApi+0x39:

bdae59ed ff7514 push dword ptr [ebp+14h]

bdae59f0 8bf0 mov esi,eax

bdae59f2 ff750c push dword ptr [ebp+0Ch]  ß This is the first parameter being pushed on the stack. This will be read by the next function at the EBP+8 relative position.

bdae59f5 ffd1 call ecx  ß Here is where the function effectively gets called

So our bad parameter comes from EBP+0Ch on this specific function. EBP+0Ch happens to be the location for the second parameter (the parameters, considering we’re using the calling convention STANDARD or CDECL – FAST_CALL will pass the two first parameters through the registers ECX and EDX instead) passed to this function from the previous one (EBP+0x8 is relative location where the first parameter can be retrieved, EBP+0xC is basically 4 bytes beyond and is the second parameter, the same way EBP+0x10 will store the third parameter and so on).

So our next step will be revisiting the stack and investigate the previous function.

kd> kb3

ChildEBP RetAddr Args to Child

bd2859a0 bdae59f7 bd2859dc bd285a0c 00028bb1 win32k!DdDxApiOpenCaptureDevice+0x18

bd2859b4 f0593a8e 00000513 bd2859dc 00000020 Dxapi!DxApi+0x43

bd285a14 f0592bb4 81c83228 84f0ba10 822efac8 3dfxV3TV+0x3a8e

So the module 3dfxV3TV.sys is the one whose an internal function is passing the bad parameter which eventually caused our crash. This is a third party module which means we will have no symbols for it. In this case it’s time to look at the assembly code once again J

This is the assembly code for the function in question:

kd> ub f0593a8e l10

3dfxV3TV+0x3a5f:

f0593a5f 2bc7 sub eax,edi

f0593a61 7902 jns 3dfxV3TV+0x3a65 (f0593a65)

f0593a63 f7d8 neg eax

f0593a65 3bc8 cmp ecx,eax

f0593a67 7e06 jle 3dfxV3TV+0x3a6f (f0593a6f)

f0593a69 8993c4000000 mov dword ptr [ebx+0C4h],edx

f0593a6f 8b83c4000000 mov eax,dword ptr [ebx+0C4h]

f0593a75 8945d8 mov dword ptr [ebp-28h],eax

f0593a78 8d45f8 lea eax,[ebp-8]

f0593a7b 6a08 push 8

f0593a7d 50 push eax

f0593a7e 8d45c8 lea eax,[ebp-38h]

f0593a81 6a20 push 20h   ß  Third parameter being pushed (this will be read by the next function at the EBP+0x10 relative position)

f0593a83 50 push eax  ß  Second parameter being pushed (this will be read by the next function at the EBP+0xC relative position) This is the one we’re interested in.

f0593a84 6813050000 push    513h ß First parameter being pushed (this will be read by the next function at the EBP+0x8 relative position)

f0593a89 e874750000 call 3dfxV3TV!SetDecoderSize+0x32ec (f059b002)  ß This is where the next function on the stack gets called

Ok, so we identified on the assembly block what would be the second parameter passed to the next function. EAX was the register storing its value, so the next step is find out where EAX is being updated for the last time before being used to push the parameter on the stack. Giving a second look at the same assembly block, now focused on any instruction involving EAX we see this:

kd> ub f0593a8e l10

3dfxV3TV+0x3a5f:

f0593a5f 2bc7 sub eax,edi

f0593a61 7902 jns 3dfxV3TV+0x3a65 (f0593a65)

f0593a63 f7d8 neg eax

f0593a65 3bc8 cmp ecx,eax

f0593a67 7e06 jle 3dfxV3TV+0x3a6f (f0593a6f)

f0593a69 8993c4000000 mov dword ptr [ebx+0C4h],edx

f0593a6f 8b83c4000000 mov eax,dword ptr [ebx+0C4h]

f0593a75 8945d8 mov dword ptr [ebp-28h],eax

f0593a78 8d45f8 lea eax,[ebp-8]

f0593a7b 6a08 push 8

f0593a7d 50 push eax

f0593a7e 8d45c8 lea eax,[ebp-38h]

f0593a81 6a20 push 20h

f0593a83 50 push eax

f0593a84 6813050000 push 513h

f0593a89 e874750000 call 3dfxV3TV!SetDecoderSize+0x32ec (f059b002)

The instruction above (lea eax, [ebp-38h]) is basically copying the content of the relative position EBP – 38h to EAX. So our bad value is whatever EBP-0x38 points to. Well, I told you EBP+0x8 is the reference for what the first parameter is being passed and then the following parameters would be 4 bytes apart… EBP is also a reference for some other important things also stored on the stack (as long as we’re not FPO optimized – again this is subject for another post) like EBP+0x4 is always the location where the return address will be stored and EBP-0x4 is where the first local variable for the function will be stored.

EBP-0x38 is for sure a location of a local variable used by the function (EBP-0x4 is the first local variable. From this point on, the location of other variables will actually depend on the variable type since this will determine how much space we need to reserve on the stack to store such values). Being a local variable on the function, there isn’t much we can do without symbols and source code, but we thing is for sure: Different from what “!analyze –v” told us the problem is actually being caused by the driver 3DFXV3TV.SYS and the solution or part of it would be either remove or update such driver J .

Until the next post J

Comments

  • Anonymous
    January 01, 2003
    Thanks Brad!  I will try to post more often :)

  • Anonymous
    January 01, 2003
    Thx for breaking it down so clearly for us once again.  Glad you posted something again...

  • Anonymous
    July 31, 2014
    Thank you for explaining that. It's sooo hard to explore kernel mode code and crash dump analysis. If I had more time, I'd surely learn to write kernel mode and be fluent in assembler.