Introduction
After many unsuccessful attempts trying to find a way to start a working Win32 process from KernelMode, I finally stumbled upon a promising piece of code that was both original and innovative (Note: the ideea belongs to Valerino).
Unfortunately that code didn't seem to work properly on my machine, and it always ended up crashing it, or just a process (in the most fortunate cases). So I've decided to reimplement Mighty Valerino's ideea in my own way (although the structure of the code is basically the same).
So let's get started. First, you must know of...
APC's
APC, standing for Asynchronous Procedure Call, represents a kernel procedure that that is queued to a particular thread for execution. In other words it's a code that is forcibly executed in a thread's context (This method is mostly employed by the I/O Manager). That is the SIMPLEST explanation I can give you, and that's all you need to know for now. There are three types of APC's:
Kernel APC's - They can be queued to any kernel thread and they will execute only if the specified thread isn't already executing a Kernel APC.
Special Kernel APC's - Basically the same as above. They run at IRQL APC_LEVEL and cannot be blocked except by running at a raised IRQL. They can always preempt normal Kernel APC's.
User APC's - These are APC's that can be queued to UserMode threads, but there's a catch: the thread must have previously called a wait service like WaitForSingleObject
with the Alertable
field set to TRUE
. The APC will be invoked next time the thread returns from KernelMode. This is the kind of APCs that we will be dealing with from now on.
Enough talking. Let's get to the fun part :)
Running that process
A brief description of the ideea behind starting a Win32 process is the following:
1. We loop through the list of running processes until we find Explorer.exe. Why explorer.exe? Because it's desktop-interactive service (I tried popping up a message box from WinLogon.exe and I could only hear the sound of it). It also has many waiting threads (both alertable and non-alertable), so it works best with this code.
2. Once we've found Explorer.exe we iterate through it's threads searching for an alertable thread. If no such thread is found, we simply save a pointer to a non-alertable one and set its ApcState.UserApcPending
to TRUE
, thus making it alertable (notice: in this case it usually takes a few seconds for the thread to return to KernelMode)
3. Now we have the PEPROCESS
of Explorer.exe and one of its PETHREAD
s. Next we queue our APC object (which will contain the code to be executed in UserMode) and when it completes, we just release the memory that we have previously allocated for it. That's all.
The Implementation
The main procedure is RunProcess(LPSTR lpProcess) wherelpProcess
must be the FULL PATH to the application that is to be run ('c:/RawWrite.exe' in our example)
void RunProcess(LPSTR lpProcess) { PEPROCESS pTargetProcess = NULL;//self explanatory PKTHREAD pTargetThread = NULL; //thread that can be either //alertable or not PKTHREAD pNotAlertableThread = NULL;//non-alertable thread PEPROCESS pSystemProcess = NULL; //May not necessarily be the //'System' process PETHREAD pTempThread = NULL; PLIST_ENTRY pNextEntry, pListHead, pThNextEntry; //... }
We start off by retrieving a pointer to the 'System' process:
pSystemProcess = PsGetCurrentProcess();
//make sure you are running at IRQL PASSIVE_LEVEL
pSystemProcess->ActiveProcessLinks
is a
LIST_ENTRY
field that contains links(pointers) to other processes (
PEPROCESS
) running on the machine. Let's search for
Explorer.exe and save a pointer to it and to one of its threads. (Note: you can queue an APC to ANY process, even CSRSS or SVCHOST, but the system will probably crash). Once we've got a pointer to
Explorer.exe and one of its threads (i will not explain how to do that here) it's time to queue our APC to that thread:
if(!pTargetThread) { //No alertable thread was found, so let's hope //we've at least got a non-alertable one pTargetThread = pNotAlertableThread; } if(pTargetThread) { DbgPrint("KernelExec -> Targeted thread: 0x%p", pTargetThread); //We have a thread, now install the APC InstallUserModeApc(lpProcess, pTargetThread, pTargetProcess); }InstallUserModeApc has the following prototype:
NTSTATUS InstallUserModeApc( IN LPSTR lpProcess, IN PKTHREAD pTargetThread, IN PEPROCESS pTargetProcess);where
pTargetProcess
points to the PEPROCESS
of Explorer.exe and pTargetThread
is the PKTHREAD
which the APC will be queued to. Let's now allocate some memory for that APC and for an MDL
(Memory Descriptor List) to map our UserMode code:
PRKAPC pApc = NULL; PMDL pMdl = NULL; ULONG dwSize = 0; //Size of code to be executed in Explorer's address space pApc = ExAllocatePool (NonPagedPool,sizeof (KAPC)); dwSize = (unsigned char*)ApcCreateProcessEnd- (unsigned char*)ApcCreateProcess; pMdl = IoAllocateMdl (ApcCreateProcess, dwSize, FALSE,FALSE,NULL); //Probe the pages for Write access and make them memory resident MmProbeAndLockPages (pMdl,KernelMode,IoWriteAccess);Our APC is now valid and
pMdl
is memory resident and maps our
UserMode code (
ApcCreateProcess()
that is. We'll get to that later). So what now? Should we deliver our APC to the thread and watch our Win32 process run? No no no.. not so fast! :)
How is Explorer.exe's thread supposed to invoke our APC routine if it doesn't have access to the Kernel memory? It cannot do that! Fine then, then let's map our APC code to UserMode memory:
KAPC_STATE ApcState; //Attach to the Explorer's address space KeStackAttachProcess(&(pTargetProcess->Pcb),&ApcState); //Now map the physical pages (our code) described by pMdl pMappedAddress = MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL,FALSE, NormalPagePriority);To continue, first I must show you how
ApcCreateProcess
(the code mapped to
UserMode memory, into Explorer's address space) works:
__declspec(naked) void ApcCreateProcess( PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2) { __asm { mov eax,0x7C86114D push 1 nop push 0xabcd call eax jmp end nop nop //...about 400 nop's here end: nop ret 0x0c } } void ApcCreateProcessEnd(){} //Used only to calculate the size of the code aboveWe move the address of
WinExec
into
eax
(0x7C86114D is its address, on WinXP SP2), we push 1 on the stack (SW_SHOWNORMAL) and then we push...
0xabcd
before calling
WinExec
. Why
0xabcd
you might ask? Well,
push 0xabcd
is the first parameter of
WinExec
and it points to the path of the application to be executed. But that means
0xabcd
cannot possibly point to the path all the times!
Why don't you simply push lpProcess
from RunProcess(LPSTR lpProcess)
then? Answer - Because WinExec
will NOT be able to access it and it will throw an 'Access Violation' at you! You cannot access Kernel memory from UserMode, remember? Instead, right after we map our code to UserMode memory, we copy the path to the location right after the first nop
instruction (that's why there are so many nop
's in there) and we modify 0xabcd
to point to it. Now here's the code:
ULONG *data_addr=0; //just a helper to change the address of the 'push' instruction //in the ApcCreateProcess routine ULONG dwMappedAddress = 0; //same as above pMappedAddress = MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL,FALSE, NormalPagePriority); dwMappedAddress = (ULONG)pMappedAddress; //zero everything out except our assembler code memset ((unsigned char*)pMappedAddress + 0x14, 0, 300); //copy the path to the executable memcpy ((unsigned char*)pMappedAddress + 0x14, lpProcess, strlen (lpProcess)); data_addr = (ULONG*)((char*)pMappedAddress+0x9);//address pushed on the stack //(originally 0xabcd)... *data_addr = dwMappedAddress+0x14; //gets changed to point to our exe's path //all done, detach now KeUnstackDetachProcess (&ApcState);
What's left now is to initialize the APC and queue it to the thread. I will not explain how KeInitializeApc and KeInsertQueueApc works as Tim Deveaux as already done that here.
//Initialize the APC... KeInitializeApc(pApc, pTargetThread, OriginalApcEnvironment, &ApcKernelRoutine, //this will fire after //the APC has returned NULL, pMappedAddress, UserMode, NULL); //...and queue it KeInsertQueueApc(pApc,0,NULL,0); //is this a non-alertable thread? if(!pTargetThread->ApcState.UserApcPending) { //if yes then alert it pTargetThread->ApcState.UserApcPending = TRUE; } return STATUS_SUCCESS; }
Compiling the code
This is simple - do a cd sys_path, where sys_path is the path to the driver project, then run build -ceZ. Or just press the F7 key in MS Visual Syudio 6 :)
Now copy KernelExec.sys to your C:/ directory, run Dbgview to see the output of the driver then double-click on Start_KE_Driver.exe to install and start the driver. Et voila! RawWrite.exe's window should be on your screen right now! :-)
P.S.: Make sure you first put an application called RawWrite.exe in your c:/ directory, because that is what the driver attempts to run.
<script src="/script/togglePre.js" type="text/javascript"></script>