PsSetLoadImageNotifyRoutine ImageBase

I have a driver that registers for load image notification callbacks and does some processing on the loaded images. In some scenarios on Win 2k3 machines I am finding that the ImageBase address reported by the image load notification routine is not valid. Also the ImageSize field is 0. Are there any known scenarios in which this is bound to happen?

Pasting the relevant bits from dump:

b8565bf0 struct _UNICODE_STRING * FullImageName = 0xe8126c00 “\Device\HarddiskVolume1\WINDOWS\notepad.exe”
b8565bf4 void * ProcessId = 0x0000183c
b8565bf8 struct _IMAGE_INFO * ImageInfo = 0xb8565cd4

(*((driver!_IMAGE_INFO *)0xffffffffb8565cd4)) [Type: _IMAGE_INFO]
[+0x000] Properties : 0x3
[+0x000 ( 7: 0)] ImageAddressingMode : 0x3
[+0x000 ( 8: 8)] SystemModeImage : 0x0
[+0x000 ( 9: 9)] ImageMappedToAllPids : 0x0
[+0x000 (10:10)] ExtendedInfoPresent : 0x0
[+0x000 (31:11)] Reserved : 0x0
[+0x004] ImageBase : 0x1000000 [Type: void *]
[+0x008] ImageSelector : 0x0
[+0x00c] ImageSize : 0x0
[+0x010] ImageSectionNumber : 0x0

Callstack:
nt!KeBugCheckEx
nt!KiDispatchException
nt!CommonDispatchException
nt!KiExceptionExit
nt!RtlImageNtHeader
driver!LoadImageNotify
nt!PsCallImageNotifyRoutines
nt!DbgkCreateThread
nt!PspUserThreadStartup
nt!KiThreadStartup

1: kd> !pte 0x1000000
VA 01000000
PDE at C0600040 PTE at C0008000
contains 0000000000000000
not valid

Thanks,
Vishal

Search is your friend. Did you read this:

https://www.osronline.com/showthread.cfm?link=248847

That short thread has some very good guidance from Mr. Johnson as well.

Peter
OSR
@OSRDrivers

The callback is called in the context of the process creator (the one that called CreateProces), not in the context of the newly created process. So the usermode VA is not valid in the callback context. That’s why the “!pte” returns an invalid PTE.

Because you are dealing with a usermod VA, you should first use the “!vad” command and will notice that there is no VAD associated with VA 0x01000000 in the “current” process.

There is at least one case where the callback does not run in the context of the newly created process: when the process’s binary image is mapped.

This may be also the case whth NTDLL.DLL but I’m not sure.

So, you have to check the current process ID and compare it with the callback’s ProcessId parameter:

//When ProcessId is not 0, a usermode image has been mapped
if(ProcessId != 0){
if(PsGetCurrentProcessId() == ProcessId){
//the VAD belongs to the current process
}
else{
// in the wrong context, ImageBase can not be worked with here.
}
}

There is also the case where ZwMapViewOfSection is used to map a binary image into the address space of a remote process.

Use the “!vad” command for user mode VAs and the “!pte” command for kernel mode VAs.

You must be in the context of the right process to use the “!vad” command.

https://msdn.microsoft.com/en-us/library/windows/hardware/ff564723(v=vs.85).aspx

https://msdn.microsoft.com/en-us/library/windows/hardware/ff564717(v=vs.85).aspx

Thanks Peter, DT for helping with the problem.

I am seeing a few things in the dump which do not seem to be adding up. I am looking more though. I do understand that all accesses to user mode addresses should be Probed but wanted to understand the scenario more. Here are my observations:

  1. The image for which I am seeing the notification is the process image and the callback is in the context of the process which is beginning life and not it’s parent. This is clear from the stack pasted.

  2. If I assume that the mapping failed for some reason then I expect the ETHREAD->DeadThread bit to be set in the thread. This I figured out by looking at the disassembly. But this bit is not set in this case. Please correct if I am missing something.

uf nt!PspUserThreadStartup
nt!PspUserThreadStartup:
8094c016 6a1c push 1Ch
8094c018 68f04b8080 push offset nt!ObWatchHandles+0x5e4 (80804bf0)
8094c01d e8ee97f3ff call nt!_SEH_prolog (80885810)
8094c022 32c9 xor cl,cl
8094c024 ff1500118080 call dword ptr [nt!_imp_KfLowerIrql (80801100)]
8094c02a 648b3524010000 mov esi,dword ptr fs:[124h]
8094c031 8975dc mov dword ptr [ebp-24h],esi
8094c034 8b4638 mov eax,dword ptr [esi+38h]
8094c037 8945e0 mov dword ptr [ebp-20h],eax
8094c03a 33db xor ebx,ebx
8094c03c 885de7 mov byte ptr [ebp-19h],bl
8094c03f f6864002000002 test byte ptr [esi+240h],2 <– testing for DeadThread bit
8094c046 7404 je nt!PspUserThreadStartup+0x36 (8094c04c) Branch

nt!PspUserThreadStartup+0x32:
8094c048 c645e701 mov byte ptr [ebp-19h],1

nt!PspUserThreadStartup+0x36:
8094c04c 648b3d18000000 mov edi,dword ptr fs:[18h]
8094c053 385de7 cmp byte ptr [ebp-19h],bl
8094c056 7530 jne nt!PspUserThreadStartup+0x72 (8094c088) Branch

nt!PspUserThreadStartup+0x42:
8094c058 895dfc mov dword ptr [ebp-4],ebx
8094c05b e8ea58feff call nt!MmGetSessionLocaleId (8093194a)
8094c060 8987c4000000 mov dword ptr [edi+0C4h],eax
8094c066 8a861d010000 mov al,byte ptr [esi+11Dh]
8094c06c 8887770f0000 mov byte ptr [edi+0F77h],al
8094c072 834dfcff or dword ptr [ebp-4],0FFFFFFFFh
8094c076 eb10 jmp nt!PspUserThreadStartup+0x72 (8094c088) Branch

nt!PspUserThreadStartup+0x72:
8094c088 f6864002000006 test byte ptr [esi+240h],6
8094c08f 7509 jne nt!PspUserThreadStartup+0x84 (8094c09a) Branch

nt!PspUserThreadStartup+0x7b:
8094c091 ff750c push dword ptr [ebp+0Ch]
8094c094 56 push esi
8094c095 e8bc850500 call nt!DbgkCreateThread (809a4656)

nt!PspUserThreadStartup+0x84:
8094c09a 385de7 cmp byte ptr [ebp-19h],bl
8094c09d 7412 je nt!PspUserThreadStartup+0x9b (8094c0b1) Branch

nt!PspUserThreadStartup+0x89:
8094c09f 6a01 push 1 <– Terminating if deadthread bit is set
8094c0a1 684b0000c0 push 0C000004Bh
8094c0a6 56 push esi
8094c0a7 e818240000 call nt!PspTerminateThreadByPointer (8094e4c4)
8094c0ac e9a2000000 jmp nt!PspUserThreadStartup+0x13d (8094c153)

!process -1 7
PROCESS 88f26580 SessionId: 0 Cid: 183c Peb: 7ffd8000 ParentCid: 16ac
DirBase: f7fd3c60 ObjectTable: e74c8df0 HandleCount: 0.
Image: notepad.exe
VadRoot 88f292a0 Vads 11 Clone 0 Private 264. Modified 1. Locked 0.
DeviceMap e1440c18
Token e818f500
ElapsedTime 00:00:00.015
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 6084
QuotaPoolUsage[NonPagedPool] 440
Working Set Sizes (now,min,max) (275, 50, 345) (1100KB, 200KB, 1380KB)
PeakWorkingSetSize 275
VirtualSize 2 Mb
PeakVirtualSize 2 Mb
PageFaultCount 271
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 306

THREAD 89279530 Cid 183c.1840 Teb: 7ffdf000 Win32Thread: 00000000 RUNNING on processor 1
Not impersonating
DeviceMap e1440c18
Owning Process 88f26580 Image: notepad.exe
Attached Process N/A Image: N/A
Wait Start TickCount 108537 Ticks: 0
Context Switch Count 4 IdealProcessor: 0
UserTime 00:00:00.000
KernelTime 00:00:00.000
.
.
.

dt nt!_ETHREAD 89279530
+0x000 Tcb : _KTHREAD
+0x1b8 CreateTime : _LARGE_INTEGER 0x01d24726df0aacb9 +0x1c0 ExitTime : _LARGE_INTEGER 0x892796f0892796f0
+0x1c0 LpcReplyChain : _LIST_ENTRY [0x892796f0 - 0x892796f0]
+0x1c0 KeyedWaitChain : _LIST_ENTRY [0x892796f0 - 0x892796f0]
+0x1c8 ExitStatus : 0n0
+0x1c8 OfsChain : (null)
+0x1cc PostBlockList : _LIST_ENTRY [0x892796fc - 0x892796fc]
+0x1d4 TerminationPort : (null)
+0x1d4 ReaperLink : (null)
+0x1d4 KeyedWaitValue : (null)
+0x1d8 ActiveTimerListLock : 0
+0x1dc ActiveTimerListHead : _LIST_ENTRY [0x8927970c - 0x8927970c]
+0x1e4 Cid : _CLIENT_ID
+0x1ec LpcReplySemaphore : _KSEMAPHORE
+0x1ec KeyedWaitSemaphore : _KSEMAPHORE
+0x200 LpcReplyMessage : (null)
+0x200 LpcWaitingOnPort : (null)
+0x204 ImpersonationInfo : (null)
+0x208 IrpList : _LIST_ENTRY [0x89279738 - 0x89279738]
+0x210 TopLevelIrp : 0
+0x214 DeviceToVerify : (null)
+0x218 ThreadsProcess : 0x88f26580 _EPROCESS
+0x21c StartAddress : 0x63021930 Void
+0x220 Win32StartAddress : 0x010073a5 Void
+0x220 LpcReceivedMessageId : 0x10073a5
+0x224 ThreadListEntry : _LIST_ENTRY [0x88f26700 - 0x88f26700]
+0x22c RundownProtect : _EX_RUNDOWN_REF
+0x230 ThreadLock : _EX_PUSH_LOCK
+0x234 LpcReplyMessageId : 0
+0x238 ReadClusterSize : 7
+0x23c GrantedAccess : 0x1f03ff
+0x240 CrossThreadFlags : 0
+0x240 Terminated : 0y0
+0x240 DeadThread : 0y0 <– This bit is 0.
+0x240 HideFromDebugger : 0y0
+0x240 ActiveImpersonationInfo : 0y0
+0x240 SystemThread : 0y0
+0x240 HardErrorsAreDisabled : 0y0
+0x240 BreakOnTermination : 0y0
+0x240 SkipCreationMsg : 0y0
+0x240 SkipTerminationMsg : 0y0
+0x244 SameThreadPassiveFlags : 0
+0x244 ActiveExWorker : 0y0
+0x244 ExWorkerCanWaitUser : 0y0
+0x244 MemoryMaker : 0y0
+0x244 KeyedEventInUse : 0y0
+0x248 SameThreadApcFlags : 0
+0x248 LpcReceivedMsgIdValid : 0y0
+0x248 LpcExitThreadCalled : 0y0
+0x248 AddressSpaceOwner : 0y0
+0x248 OwnsProcessWorkingSetExclusive : 0y0
+0x248 OwnsProcessWorkingSetShared : 0y0
+0x248 OwnsSystemWorkingSetExclusive : 0y0
+0x248 OwnsSystemWorkingSetShared : 0y0
+0x248 OwnsSessionWorkingSetExclusive : 0y0
+0x249 OwnsSessionWorkingSetShared : 0y0
+0x249 ApcNeeded : 0y0
+0x24c ForwardClusterOnly : 0 ‘’
+0x24d DisablePageFaultClustering : 0 ‘’
+0x24e ActiveFaultCount : 0 ‘’
+0x250 KernelStackReference : 1

Ok sorry about the context story.

And what does the “!vad” command prints ?

Below is the output of !vad command:

1: kd> !vad
VAD Level Start End Commit
89575ef8 3 10 11 2 Private READWRITE
89133c88 2 20 20 1 Private READWRITE
89088cd8 3 30 30 1 Private READWRITE
88f99cd8 1 40 7f 18 Private READWRITE
88f2a368 3 80 84 0 Mapped READONLY Pagefile section, shared commit 0x5
88f84298 2 90 91 0 Mapped READONLY Pagefile section, shared commit 0x2
88f89318 3 400 50f 272 Private READWRITE
88f292a0 0 7c800 7c8c4 6 Mapped Exe EXECUTE_WRITECOPY \WINDOWS\system32\ntdll.dll
88f53ec8 2 7ffb0 7ffd3 0 Mapped READONLY Pagefile section, shared commit 0x24
88f537f8 1 7ffd8 7ffd8 1 Private READWRITE
88f77460 2 7ffdf 7ffdf 1 Private READWRITE

Total VADs: 11, average level: 3, maximum depth: 3
Total private commit: 0x12e pages (1208 KB)
Total shared commit: 0x2b pages (172 KB)

Ok so there is no VAD for NOTEPAD.EXE. The mapping failed for some reason.

User mode VAs are aligned on 64k (0x10000) boundaries. So the following call can be used to probe the memory:

ProbeForRead(Va, VaSize, 0x10000);

Could be interesting to have the error message (returned by CreateProcess).

Use DUMPBIN.EXE against NOTEPAD.EXE to see if the file is corrupted.

I tried failing MmMapViewOfSection() in disassembly through WinDbg on 2k3 machine for notepad.exe.
Without calling MmMapViewOfSection(), I returned error code STATUS_ACCESS_VIOLATION through WinDbg.
In that scenario, I didn’t get any load image notification for notepad.exe.

Is it the correct way or am I missing something?

Sorry, I do not understand.

It looks like the crash happened within RtlImageNtHeader which is not documented btw so, at least, make the call in a try/except block and break in the debugger if an exception is raised.