Vault7: CIA Hacking Tools Revealed
Navigation: » Latest version
Kernel to User land: APC injection
Overview
When running in Kernel mode, it may be necessary to inject code into a User-land process. There are two ways that Asynchronous Procedure Calls (APCs) can be used to accomplish this goal.
Method 1: Queue User APCAsynchronous Procedure Call to Alertable Thread
This method requires the kernel code to find a thread in the target process that is in an alertable state. This method relies on searching undocumented structures in the ETHREADis an opaque structure that servers as the thread object for athread structure for each thread. There is a flag that is set if a thread is Alertable. This section could be expanded to give more information in the future... It is general thought that this method is a
bit unreliable, and subject to Microsoft randomly changing the ETHREADis an opaque structure that servers as the thread object for athread struct (and the structures within it).
Method 2: Queue User APCAsynchronous Procedure Call to new Thread
This method requires the kernel code to have a way of getting to a Thread before it is started. Typically this is accomplished through registering a CreateThreadNotifyRoutine (CTNRCreate Thread Notify Routine). When the CreateThreadNotifyRoutine is called, one of two different scenarios could be happening:
- A thread is dying. The CTNRCreate Thread Notify Routine is called in the context of the dying thread. This doesn't really help us.
- A thread is being created. The CTNRCreate Thread Notify Routine is called in the context of the thread that is creating the new thread. This is helpful.
After verifying that the CTNRCreate Thread Notify Routine was called for thread creation, the kernel code can do some basic checks to see if the thread is being created in an interesting process. The important thing to remember about running code in the CTNRCreate Thread Notify Routine is that NO new threads can be created until each CTNRCreate Thread Notify Routine is finished. If your CTNRCreate Thread Notify Routine code takes 1 minute to run, then you've bottlenecked thread creation to 1 new thread a minute (extreme example of course). Whatever you do in the CTNR, make sure it's quick.
To Queue an APCAsynchronous Procedure Call to the thread being created, the kernel code needs to have the ETHREADis an opaque structure that servers as the thread object for athread structure for the new thread. In Windows 7, and according to the MSDNMicrosoft Developer Network (which never lies...) you can call PsLookupThreadByThreadId to obtain a handle to the ETHREADis an opaque structure that servers as the thread object for athread structure. In Windows XP, this will not work due to a code check that fails. It is thought that Windows XPWindows operating system (Version) fails to retrieve the ETHREADis an opaque structure that servers as the thread object for athread structure at this point, because it is not fully created/added to the list of objects.
Next, you need to attach to the process' context using KeStackAttachProcess(), which has 2 parameters. The first parameter is a handle to the EPROCESS struct (Obtained by calling PsGetCurrentProcess()). The second parameter is a pointer to memory allocated for a KAPC struct (using ExAllocatePool). Save the KAPC memory pointer, as it will be needed later to detach.
After attaching, you must use ZwAllocateVirtualMemory twice. Once to allocate memory for your APCAsynchronous Procedure Call parameter. For example, if your APCAsynchronous Procedure Call is going to inject a DLLDynamic Link Library into a target process, then you need to allocate memory for the DLLDynamic Link Library name to inject, and the address of LoadLibraryA. The second memory allocation is for your user APCAsynchronous Procedure Call code. You can use the following trick to get the size of your APCAsynchronous Procedure Call code:
VOID UserAPC(PVOID context, PVOID sysarg1, PVOID sysarg2)
{
(LOAD_LIB)(context->LoadLibrary_Func)(context->DllName);
}
VOID UserAPC_end()
{}
ULONG CalcApcSize()
{
return ((ULONG_PTR)UserAPC_end - (ULONG_PTR)UserAPC);
}
typedef struct _CONTEXT
{
ULONGLONG LoadLibrary_Func;
CHAR DllName[MAX_PATH];
}
VOID MyCreateThreadNotifyRoutine(....)
{
....
PCONTEXT context = NULL;
KeStackAttachProcess(PsGetCurrentProcess(), (PKAPC_STATE)apc);
ZwAllocateVirtualMemory(ZwCurrentProcess(), (PVOID*)&name, NULL, &rsize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE
}