Article address: http://www.codeproject.com/useritems/KernelExec.asp
Download source files-55 Kb
Download demo project-27 Kb
Introduction
After a lot of unsuccessful attempts to find a way to start the runable Win32 process in the core mode (KernelMode), I finally accidentally found a piece of promising code, these codes are both original and innovative (note: this idea comes from Valerino)
Unfortunately, the Code does not seem to run correctly on my machine, and it always ends with a crash, or just (in many lucky examples) is a process. Therefore, I decided to re-implement the great Valerino's idea in my own way (although the code structure is the same.
Let's get started. First, you must know...
APC
APC, called an asynchronous program, indicates that the core program is included in a unique thread queue waiting for execution. In other words, a piece of code is forced in a thread context (thread's
(This method is usually used by some I/O managers ). This is the simplest explanation that I can provide to you, and it is all you need to know now.
There are three methods for APC:
Kernel APC's-they can be included in any Core thread team and they will be executed if the established thread has not executed a core APC.
Special Kernel APC's -- basically the same as above. They run at the level of the interrupt request (IRQL) APC_LEVEL and cannot be locked unless they run at an elevated interrupt request level (IRQL ). They can always run ahead of normal core APC.
User APC's -- these APC's are like this: they can be included in the User mode thread queue, but this has a condition: this thread must have called a waiting service before, just like WaitForSingleObject.
Alertable
The domain is set to TRUE. This APC will be called the next time this thread returns from the kernel mode. This is the APC we will process from now on.
Enough. Let's start with this interesting part :)
Running Process
A brief description of this idea before starting a Win32 process is as follows:
1.we cyclically look for the running route table and go straight to mongoer.exe. What if I want to upload er.exe? The reason is that the table service (I try to bring up a dialog box from winlogon.exe and I can only hear the sound when it pops up) also has many waiting threads (both
Alertable
Or non-
Alertable
), So it can run well with this code.
2.once we find explorer.exe, we use its thread loop to find
Alertable
If such a thread cannot be found, we only need to save
Alertable
Thread pointer, and set its ApcState. UserApcPending to TRUE, so this makes this non-
Alertable
Thread
Alertable
(Note: In this case, it usually takes several seconds to return the thread to the kernel mode )).
3.now we have the PEPROCESS of assumer.exe and its pethread. Next we will include our APC object in the queue (this will include the Code executed in UserMode) and when the queue is complete, we only need to release the previously allocated memory, which is the whole process.
Implementation
The main program is RunProcess (LPSTR lpProcess), and its lpProcess must be the full path of the application to be run (in our example, 'c:/RawWrite.exe ').
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
// 'System' process
PETHREAD pTempThread = NULL;
PLIST_ENTRY pNextEntry, pListHead, pThNextEntry;
//...
}
We start with recycling the pointer to the System process:
PSystemProcess = PsGetCurrentProcess ();
// Make sure you are running at IRQL PASSIVE_LEVEL
PSystemProcess
-> ActiveProcessLinks is a LIST_ENTRY domain that contains links (pointers) to other processes running on the machine (PEPROCESS. Let's
Search for assumer.exe and save the pointer to it and to a thread that points to it. (Note: You can include APC in the queue of any process, even if
CSRSS or SVCHOST, but the system may crash ). One day we have to point to assumer.exe and point to one of its threads (here I will not explain how to do that)
The pointer is the time to include our APC in the queue of such threads:
If (! PTargetThread)
{
// No
Alertable
Thread was found, so let's hope
// We 've at least got a non-
Alertable
One
PTargetThread = pNotAlertableThread;
}
If (pTargetThread)
{
Dbuplint ("KernelExec-> Targeted thread: 0x % p ",
PTargetThread );
// We have a thread, now install the APC
InstallUserModeApc (lpProcess,
PTargetThread,
PTargetProcess );
}
In this case, ptargetprocessrefers to the peprocess of assumer.exe and pTargetThread points to
PKTHREAD. Now we allocate some Memory for APC and map the MDL (Memory Descriptor List) to our user mode 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
MMP robeandlockpages (pMdl, KernelMode, IoWriteAccess );
The APC installed in user mode is the following function prototype:
NTSTATUS
InstallUserModeApc (
In lpstr lpProcess,
In pkthread pTargetThread,
In peprocess pTargetProcess );
Our APC is now effective and pMdl is memory resident and mapped to our user mode code (ApcCreateProcess ()
This is exactly the case. We will see later ). So what are you doing now? Should we pass our APC to this thread and observe the running of our Win32 process? No, no, no...
Fast! :)
If the assumer.exe thread cannot access the core memory, how should it call our APC routine? It cannot do that! So good. Let's map our APC code to the user mode 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 (this code is mapped to the user mode memory and enters the Explorer Address Space) runs:
_ Declspec (naked)
Void ApcCreateProcess (
PVOID NormalContext,
PVOID SystemArgument1,
PVOID SystemArgument2)
{
_ Asm
{
Mov eax, 0x7C86114D
Push 1
Nop
Push 0 xabcd
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 above
We move the WinExec address to eax (in WinXP
0x7C86114D on SP2 is its address). We push 1push into the stack (SW_SHOWNORMAL) and call 0 xabcd before WinExec.
Push to stack. Why is it 0 xabcd? Oh, push
0xabcd is the first parameter of WinExec. It points to the path of the application to be executed. However, this means that 0xabcd cannot always point to this path.
So why not only do you push the lpProcess of RunProcess (LPSTR lpProcess) into the stack? The answer is --
Because WinExec will not be able to Access lpProcess, and this will throw you an Access
Violation exception! You cannot access the core memory (Kernel
Memory), remember? On the contrary, we should immediately map our code to the user-mode memory, and copy this path to this location followed by the first nop command (that's why
There are so many nop commands), then we modify 0xabcd to point to this location. The Code is as follows:
ULONG * data_addr = 0; // just a helper to change the address of the 'push' instruction
// In the ApcCreateProcess routine
ULONG dwMappedAddress = 0; // same as abve
PMappedAddress =
MmMapLockedPagesSpecifyCache (pMdl,
UserMode,
MmCached,
NULL, FALSE,
NormalPagePriority );
DwMappedAddress = (ULONG) pMappedAddress;
// Zero everything out of memory T our receiver 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 0 xabcd )...
* Data_addr = dwMappedAddress + 0x14; // gets changed to point to our exe's path
// All done, detach now
KeUnstackDetachProcess (& ApcState );
What is left behind now is to initialize the APC and include it in the thread queue. I do not explain how KeInitializeApc and KeInsertQueueApc run. Here, as Tim Deveaux has completed: (rxxi Note: I do not know whether the translation is correct or not .)
// 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;
}
Compile code
This is simple-execute the cd sys_path command, sys_path is the path of the driver project, and then run build-ceZ. Or press F7 only in MS Visual Studio 6. :)
Copy KernelExec. sys to your C:/directory and run dbgviewto check the output result of the driver. Then, double-click start_ke_driver.exe to install and start executing the driver! The RawWrite.exe window is now displayed on your screen! :-)
Note: Make sure you put rawwrite.exe called by the application into the C:/directory first, because it is the directory where the driver tries to run it.