Внедряем код в процессы из режима ядра
Всем привет! Сегодня мы затронем очень интересную тему, повествующую о написании драйвера, который будет внедрять код в адресное пространство таргетированного процесса двумя способами. Наверное, Вы сразу подумаете, «зачем это делать из режима ядра, если можно использовать такие API функции из режима пользователя, как OpenProcess,WriteProcessMemory и тд?». На это хочется сразу ответить, что разобраться как работать с памятью из режима ядра — очень полезный скил, который пригодится в дальнейшем, как для реверса коварной малвари (особенно это касается руткитов/буткитов), так и для написания каких-либо более-менее серьёзных механизмов защиты. К примеру, можно будет написать программу, которая будет снимать дамп нужного процесса. При этом можно будет быть уверенным, что дамп снимется таким, какой он есть, а не с затёртым кодом.
Сейчас наша задача — внедрить свой shell код в адресное пространство целевого процесса. А лучше установить нотификатор на создание процесса. Нотификатор — это такая процедура, которая вызываетсясистемой тогда, когда происходит определённое событие. Это может быть создание/завершение процесса/потока, загрузка/выгрузка образа и тд. Если вы пишите свой защитный механизм и реализуете в нём перехваты API функций, то лучше всего сделать это через установку нотификатора. Кстатиговоря, думаю, вы уже догадались, что вредоносное ПО также использует этот механизм в своих целях.
Способ 1. Используем ZwAllocateVirtualMemory
Этот способ почти такой же, как и использование последовательности вызовов OpenProcess, VirtualAllocate в режиме пользователя. Алгоритм тут такой:
- Аттатчимся в нужный процесс
- Получаем дескриптор этого процесса
- Вызываем ZwAllocateVirtualMemory
Дескриптор процесса нужен нам для того, чтобы ZwAllocateVirtualMemory знала в адресном пространстве какого именно процесса выделять память. Эта функция принимает шесть параметров: дескриптор процесса, в котором будет выделена память, указатель, который будет указывать на кусок выделенной памяти. Третий параметр указывает сколько старших битов во втором указатели должны быть нулевыми. Это число должно быть < 21. Но нас такая фича не интересует, поэтому мы выставим сюда 0. Следующий параметр — тип выделяемой памяти. Сюда выставляем MEM_COMMIT, для нас это обычное дело :-). Далее идёт размер памяти, который мы хотим выделить и права, который мы, естественно выставим на PAGE_EXECUTE_READWRITE. Дескриптор процесса мы можем получить разными способами, но наиболее удобным для нас будет — вызов функции NtCurrentProcess. Она возвращает дескриптор текущего процесса. Чтобы какой-либо процесс стал для нас текущим , мы должны будем к нему приаттатчиться, используя функцию KeAttachProcess. Ниже будет приведена функция, которая это реализует.
DWORD InsertCodeToProcess(LPVOID shellcode_, DWORD shellcodeSize_, PEPROCESS eProcess) { KEVENT Event; HANDLE hThread = 0; HANDLE hProcessHandle = 0; NTSTATUS status = -1; LPVOID memory = NULL; DWORD size = shellcodeSize_; KeAttachProcess(eProcess); hProcessHandle = NtCurrentProcess(); if (hProcessHandle) { status = ZwAllocateVirtualMemory(hProcessHandle, &memory, 0, &size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NT_SUCCESS(status)) { #ifdef DEBUG DbgPrint("Allocated memory %s!: 0x%08x, size: %d, hProcessHandle: 0x%08x\n", PsGetProcessImageFileName(eProcess), memory, size, hProcessHandle); #endif memcpy(memory, shellcode_, shellcodeSize_); #ifdef DEBUG DbgPrint("0x%02x, 0x%02x, 0x%02x, 0x%02x\n", *(char *)memory, *(unsigned char *)((DWORD)memory+1), *(unsigned char *)((DWORD)memory+2), *(unsigned char *)((DWORD)memory+3)); #endif } } else { #ifdef DEBUG DbgPrint("Process HANDLE error!!!: hProcessHandle: 0x%08x\n", hProcessHandle); #endif } KeDetachProcess(); return status; }
Проверяем, это работает! Наш шелкод окажется в адресном пространстве нужного процесса. А теперь представьте, что вы по каким-то причинам захотели внедрять из ядра код во все запушенные процессы, или будите внедрять код во все только что запустившиеся процессы. В этом случае, использование данного метода будет приносить некоторые неудобства. Первое — выделить память размером меньше, чем размер страницы нельзя. Минимальный размер страницы — 1кб (бывает и 4кб, в зависимости от настроек системы). Если у вас запущено 100 процессов, и в каждый вы внедрите код, то истратите аж 100 страниц памяти! Это довольно расточительно. А если вы вдруг захотите изменить shell code, на другой, то что тогда?! Будите его искать в каждом процессе и исправлять? Вот в этом случае давайте рассмотрим второй способ.
Способ 2. Отображение глобальной страницы памяти в адресное пространство процесса
В этот раз мы изменим логику работы драйвера. Мы не будем 100 раз выделять память в контексте какого-то процесса, а вместо этого выделим одну страницу неподкачиваемой памяти, выставим на неё нужные права и заполним её shell кодом. После этого отобразим эту страницу во все нужные нам процессы. Под «отобразим» я имею ввиду — предоставим к ней доступ. У каждого процесса виртуальный адрес отображённой нами страницы может быть разным, но всегда вести он будет к одной нашей странице с shell кодом. Поэтому, если мы изменим логику работы shell кода, то есть перепишем его в той глобальной странице памяти, которую проецируем в пользовательские процессы, то результат отразится на всех сразу. Для отображения какой-либо страницы мы будем придерживаться следующего алгоритма:
- Выделяем пулл неподкачиваемой памяти с помощью ExAllocatePool
- Выделяем место для MDL, которая опишет нам все занимаемые физические адреса. Юзаем IoAllocateMdl
- Заполняем тело MDL, используя MmBuildMdlForNonPagedPool
- Назначаем RWX права нашему региону памяти с помощью MmProtectMdlSystemAddress
- Копируем в пулл памяти наш shell code
- Ататтчимся к целевому процессу с помощью KeAttachProcess
- Отображаем нашу глобальную страницу памяти, описанную с помощью MDL в адресное пространство процесса, используя MmMapLockedPagesSpecifyCache

Отображение физической страницы в виртуальную память процессов через MDL
Давайте остановимся по подробнее о том, что такое MDL.
Memory Descriptor List
MDL — это такая структура, которая описывает регион физических страниц. Из этого утверждения следует, что с помощью нею можно описать регион физической памяти, а не виртуальной. Поэтому, мы в первом шаге будем выделять память с помощью ExAllocatePool, с первым параметром — NonPagedPool, который означает, что выделенный кусок будет храниться именно в оперативке и никогда не будет сброшен в файл подкачки. Сама эта структура представляет из себя заголовок и тело, которое идёт сразу после него. Заголовок MDL представлен ниже:

Структура MDL
Сразу после этого заголовка идёт массив двойных слов Pages, каждый из которых представлен номер физической страницы page frame number (PFN). Вообще, я тут пытаюсь пересказать статью из KMD TUT от MS-Remа, так что, настоятельно советую прочитать его. Итак, в общем виде, функция по отображению физической страницы в память процесса будет такая:
LPVOID InsertCodeToProcessWithMDL(LPVOID shellcode_, DWORD shellcodeSize_, PEPROCESS eProcess, PMDL pMdl) { NTSTATUS status = -1; LPVOID address = NULL; kernel_memory = ExAllocatePool(NonPagedPool, shellcodeSize_); //allocate mem for MDL header and init pMdl = IoAllocateMdl(kernel_memory, shellcodeSize_, FALSE, FALSE, NULL); //Fill MDL Body to phisical frame numbers MmBuildMdlForNonPagedPool(pMdl); // MmProtectMdlSystemAddress(pMdl, PAGE_EXECUTE_READWRITE); //copy shellcode to global mem memcpy(kernel_memory, shellcode_, shellcodeSize_); KeAttachProcess(eProcess); address = (LPVOID)MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); DbgPrint("InsertCodeToProcessWithMDL, address: 0x%08x", address); #ifdef DEBUG if (eProcess) DbgPrint("Process: %s Mapped VA address: 0x%08x %02x", PsGetProcessImageFileName(eProcess), address, *(char *)address); #endif KeDetachProcess(); return address; }
Непосредственно само отображение делает функция MmMapLockedPagesSpecifyCache. В качестве первого параметра выставляется указатель на подготовленную нами MDL, далее не забудьте выставить параметр UserMode, который будет означать, что к региону памяти можно будет обращаться из пользовательского режима.
Устанавливаем нотификатор на создание процесса и тестим
Теперь, давайте проверять, как всё работает. Сделаем так, чтобы shell code проецировался во все запускаемые процессы с каким-нибудь именем, у меня на этот раз это notepad.exe. Для этого напишем специальную call back функцию, которая будет вызываться всякий раз, когда какой либо процесс будет создан. В ней мы проверим имя процесса и, если оно совпадает с нашим, то проинжектим туда код, отобразим неподкачиваемую физическую страницу с нашем шелл кодом в адресное пространство конечного процесса. Согласно документации, функция нотификатора должна иметь следующий прототип:
void NotifyRoutine(IN HANDLE hParentId, IN HANDLE hProcessId, IN BOOLEAN bCreate)
Система передаст в hParentId идентификатор родительского процесса, в hProcessId — идентификатор новосозданного процесса, а в bCreate будет true, если процесс создался и false — в противном случае. Учитывая это, пишем код:
void NotifyRoutine(IN HANDLE hParentId, IN HANDLE hProcessId, IN BOOLEAN bCreate) { LPSTR processName; PEPROCESS eProcess = NULL; NTSTATUS status = -1; LPVOID address = NULL; if (bCreate) { status = PsLookupProcessByProcessId(hProcessId, &eProcess); processName = PsGetProcessImageFileName(eProcess); if (strcmp(ProcessName, processName) == 0) { address = InsertCodeToProcessWithMDL(eProcess); #ifdef DEBUG DbgPrint("\n\nNotify: shell address: 0x%08x: \n\n", address); #endif } ObDereferenceObject(eProcess); } }
Зарегистрировать функцию нотификатор нужно в функции DriverEntry. Делается это так:
status = PsSetCreateProcessNotifyRoutine(NotifyRoutine, FALSE);
Когда драйвер выгружается, нужно обязательно снять нотификатор! Делается это аналогично, только вместо последнего параметра передаётся TRUE. Если драйвер выгрузится, а нотификатор останется, то у вас вывалится BSOD с ошибкой DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS.
Итак, смотрим результат

Демонстрация отображение одной страницы в два процесса
Мы видим, что по адресу 0x864a3f08 располагается наш shellcode. Эта та самая страница, которая находится в неподкачиваемом пуле. Она и проецируется в адресное пространство процесса. Также, мы видим, что по виртуальному адресу 0x30f08 у нас отображается эта страница. В моём случае, виртуальные адреса спроецированной страницы совпали, но это не обязательно. Ладно, посмотрим, что у нас по адресу 0x864a3f08, а что по адресу 0x30f08.

Страница с шелл кодом в ядре отображается в виртуальное адресное пространство процесса режима пользователя
Мы тут отчётливо видим, что по виртуальному адресу0x00030F008 у нас находятся те же данные (тот же шелл), что и по адресу 0x864a3F08. Просто VA у нас спроецирован на него, вот мы и видим одно и тоже. Хорошо, давайте, теперь проведём эксперимент. Изменим первые байты шелкода (47 F9 93 9B) на 90 90 90 90 и посмотрим, отразятся ли изменения в двух процессах. Для этого выполним в отладчике ядра команду ed 0x864a3f08 90909090. Переаттатчимся Olly Debug ом и посмотрим, что теперь у нас в памяти:

Изменения в отображаемой странице памяти режима ядра видны во всех процессах, куда отображалась страница
Ага! Во всех процессах, куда была спроецирована страница памяти произошли изменения! А всё потому, что страница-то у нас одна, просто отображается она в разные адресные пространства процессов. Ну, теперь, задача с внедрением кода решена, осталось только передать на него управление. Как это сделать, это другая уже тема, можно, например, вставить APC инжект. Сейчас я приведу полный код драйвера, который может производить инжекты двумя способами.
Driver.h
#define deviceName L"\\Device\\KernelCodeInject" #define symbolicName L"\\DosDevices\\KernelCodeInject" #define DEBUG #pragma comment(lib,"ntoskrnl.lib") #pragma comment(lib,"Ntstrsafe.lib") ULONG ActiveProcessLinkOffset; ULONG ProcessNameOffset; ULONG PIDOffset; LPSTR ProcessName = "notepad.exe"; LPVOID kernel_memory = NULL; PMDL pMdl = NULL; int shellcodeSize = 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"; UNICODE_STRING SymbolicLinkName; PDEVICE_OBJECT deviceObject; int initGlobalMemory(); void freeGlogalMemory(); NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDeviceObject, IN PUNICODE_STRING RegistryPath); NTSTATUS CtlDriverDispatch(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp); NTSTATUS CtlPass(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp); void CtlUnloadRoutine(IN PDRIVER_OBJECT pDeviceObject);
Driver.c
#include <ntifs.h> #include <ntddk.h> #include <Ntstrsafe.h> #include "types.h" #include "driver.h" #include "functions.h" #include "kernel-inject.h" #include "kernel-inject-mdl.h" //инициализирует глобальные переменные int initGlobalMemory() { kernel_memory = NULL; pMdl = NULL; shellcodeSize = sizeof(shellcode); if (shellcodeSize > 0) { //allocate global memory in kernel space kernel_memory = ExAllocatePool(NonPagedPool, shellcodeSize); if (kernel_memory) { //allocate mem for MDL header and init pMdl = IoAllocateMdl(kernel_memory, shellcodeSize, FALSE, FALSE, NULL); //Fill MDL Body to phisical frame numbers MmBuildMdlForNonPagedPool(pMdl); MmProtectMdlSystemAddress(pMdl, PAGE_EXECUTE_READWRITE); //copy shellcode to global mem memcpy(kernel_memory, shellcode, shellcodeSize); #ifdef DEBUG DbgPrint("\nglobal mem: %08x, shellcode byts: %08x, shellcodeSize: %08x, shellcode g byts: %08x\n", kernel_memory, *(DWORD*)shellcode, shellcodeSize, *(DWORD*)kernel_memory); #endif return 1; } } return 0; } void freeGlogalMemory() { if (pMdl) IoFreeMdl(pMdl); if (kernel_memory) ExFreePool(kernel_memory); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDeviceObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS status; UNICODE_STRING DeviceName; PDRIVER_DISPATCH *pDispacher; PEPROCESS process; DWORD pid; LPVOID userSpaceShellAddress; LPVOID address = 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 = CtlUnloadRoutine; pDispacher[IRP_MJ_DEVICE_CONTROL] = CtlDriverDispatch; pDispacher[IRP_MJ_WRITE] = CtlPass; pDispacher[IRP_MJ_READ] = CtlPass; pDispacher[IRP_MJ_CREATE] = CtlPass; pDispacher[IRP_MJ_CLOSE] = CtlPass; if (status == STATUS_SUCCESS) { #ifdef DEBUG DbgPrint("\ndriver has been loaded!\n"); #endif if (!InitParams()) { #ifdef DEBUG DbgPrint("Unsupported OS!\n"); #endif return -1; } if (!initGlobalMemory()) DbgPrint("\nError Init Global Vars!\n"); status = PsSetCreateProcessNotifyRoutine(NotifyRoutine, FALSE); #ifdef DEBUG if (NT_SUCCESS(status)) DbgPrint("NotifyRoutineSet Ok!\n"); else DbgPrint("NotifyRoutineSet False!\n"); #endif } } else { #ifdef DEBUG DbgPrint("driver not loaded!"); #endif } return status; } NTSTATUS CtlDriverDispatch(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { PIO_STACK_LOCATION pIrpStack; DWORD inSize, ioctl; pIrpStack = IoGetCurrentIrpStackLocation(Irp); ioctl = pIrpStack->Parameters.DeviceIoControl.IoControlCode; inSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS CtlPass(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp) { Irp->IoStatus.Status=STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } void CtlUnloadRoutine(IN PDRIVER_OBJECT pDeviceObject) { NTSTATUS status; status = PsSetCreateProcessNotifyRoutine(NotifyRoutine, TRUE); Sleep(3000); if (status == STATUS_SUCCESS) { #ifdef DEBUG DbgPrint("Notifi Removed!\n"); #endif } freeGlogalMemory(); status = IoDeleteSymbolicLink(&SymbolicLinkName); if (status == STATUS_SUCCESS) { IoDeleteDevice(deviceObject); #ifdef DEBUG DbgPrint("driver has been unloaded!\n"); #endif return; } #ifdef DEBUG DbgPrint("driver has`t been unloaded!"); #endif }
functions.h
#include <ntifs.h> #include <ntddk.h> #include <string.h> #include <Ntstrsafe.h> //#include "driver.h" //inicilise in driver.h from 13 lines extern ULONG ActiveProcessLinkOffset; extern ULONG ProcessNameOffset; extern ULONG PIDOffset; extern LPSTR ProcessName; extern LPVOID kernel_memory; extern int shellcodeSize; extern PMDL pMdl; ////////////////////////////////////// extern PSHORT NtBuildNumber; int InitParams(); LPSTR PsGetProcessImageFileName(PEPROCESS Process); DWORD getPidByName(LPSTR processName); BOOLEAN Sleep(ULONG MillionSecond); void NotifyRoutine(IN HANDLE hParentId, IN HANDLE hProcessId, IN BOOLEAN bCreate);
functions.c
#include "types.h" #include "functions.h" #include "kernel-inject-mdl.h" int InitParams() { // ? wo(nt!NtBuildNumber) switch (*NtBuildNumber) { case 2195: // Win 2k ActiveProcessLinkOffset = 0xA0; ProcessNameOffset = 0x01FC; PIDOffset = 0x09C; break; case 2600: // Win XP ActiveProcessLinkOffset = 0x88; ProcessNameOffset = 0x174; PIDOffset = 0x084; break; case 3790: // W2K3 ActiveProcessLinkOffset = 0x98; ProcessNameOffset = 0x164; PIDOffset = 0x094; break; case 7600: ActiveProcessLinkOffset = 0x0b8; ProcessNameOffset = 0x16c; PIDOffset = 0x0b4; break; } #ifdef DEBUG DbgPrint("NtBuildNumber: 0x%08x\n", *NtBuildNumber); DbgPrint("ActiveProcessLinkOffset: 0x%08x\n", ActiveProcessLinkOffset); DbgPrint("ProcessNameOffset: 0x%08x\n", ProcessNameOffset); DbgPrint("PIDOffset: 0x%08x\n", PIDOffset); #endif return (ActiveProcessLinkOffset != 0); } DWORD getPidByName(LPSTR processName) { PEPROCESS CurrentProcess; PLIST_ENTRY CurrentProcessAPL; PLIST_ENTRY ProcessAPL; ULONG ProcessPID; PCHAR ProcessName; DWORD result = 0; CurrentProcess = PsGetCurrentProcess(); if (!CurrentProcess) return result; CurrentProcessAPL = (PLIST_ENTRY)((ULONG)CurrentProcess + ActiveProcessLinkOffset); ProcessAPL = CurrentProcessAPL; do { ProcessPID = *(PULONG)((ULONG)ProcessAPL - ActiveProcessLinkOffset + PIDOffset); ProcessName = (PCHAR)((ULONG)ProcessAPL - ActiveProcessLinkOffset + ProcessNameOffset); if (strcmp(ProcessName, processName) == 0) { result = ProcessPID; #ifdef DEBUG DbgPrint("getPidByName %s pid: %u", processName, ProcessPID); #endif break; } ProcessAPL = ProcessAPL -> Flink; } while (ProcessAPL != CurrentProcessAPL); return result; } BOOLEAN Sleep(ULONG MillionSecond) { NTSTATUS st; LARGE_INTEGER DelayTime; DelayTime = RtlConvertLongToLargeInteger(-10000*MillionSecond); st=KeDelayExecutionThread( KernelMode, FALSE, &DelayTime ); return (NT_SUCCESS(st)); } void NotifyRoutine(IN HANDLE hParentId, IN HANDLE hProcessId, IN BOOLEAN bCreate) { LPSTR processName; PEPROCESS eProcess = NULL; NTSTATUS status = -1; LPVOID address = NULL; if (bCreate) { status = PsLookupProcessByProcessId(hProcessId, &eProcess); processName = PsGetProcessImageFileName(eProcess); if (strcmp(ProcessName, processName) == 0) { address = InsertCodeToProcessWithMDL(eProcess); #ifdef DEBUG DbgPrint("\n\nNotify: shell address: 0x%08x: \n\n", address); #endif } ObDereferenceObject(eProcess); } }
kernel-inject.h
#include <ntifs.h> #include <ntddk.h> #include <string.h> #include <Ntstrsafe.h> DWORD InsertCodeToProcess(LPVOID shellcode_, DWORD shellcodeSize_, PEPROCESS eProcess);
kernel-inject.c
#include <ntifs.h> #include <ntddk.h> #include <Ntstrsafe.h> #include "types.h" #include "functions.h" DWORD InsertCodeToProcess(LPVOID shellcode_, DWORD shellcodeSize_, PEPROCESS eProcess) { KEVENT Event; HANDLE hThread = 0; HANDLE hProcessHandle = 0; NTSTATUS status = -1; LPVOID memory = NULL; DWORD size = shellcodeSize_; KeAttachProcess(eProcess); hProcessHandle = NtCurrentProcess(); if (hProcessHandle) { status = ZwAllocateVirtualMemory(hProcessHandle, &memory, 0, &size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NT_SUCCESS(status)) { #ifdef DEBUG DbgPrint("Allocated memory %s!: 0x%08x, size: %d, hProcessHandle: 0x%08x\n", PsGetProcessImageFileName(eProcess), memory, size, hProcessHandle); #endif memcpy(memory, shellcode_, shellcodeSize_); #ifdef DEBUG DbgPrint("0x%02x, 0x%02x, 0x%02x, 0x%02x\n", *(char *)memory, *(unsigned char *)((DWORD)memory+1), *(unsigned char *)((DWORD)memory+2), *(unsigned char *)((DWORD)memory+3)); #endif } } else { #ifdef DEBUG DbgPrint("Process HANDLE error!!!: hProcessHandle: 0x%08x\n", hProcessHandle); #endif } KeDetachProcess(); return status; }
kernel-inject-mdl.h
#include <ntifs.h> #include <ntddk.h> #include <string.h> #include <Ntstrsafe.h> LPVOID InsertCodeToProcessWithMDL(PEPROCESS eProcess);
kernel-inject-mdl.c
#include <ntifs.h> #include <ntddk.h> #include <string.h> #include <Ntstrsafe.h> #include "types.h" #include "functions.h" #include "kernel-inject-mdl.h" #define DEBUG LPVOID InsertCodeToProcessWithMDL(PEPROCESS eProcess) { NTSTATUS status = -1; LPVOID address = NULL; KeAttachProcess(eProcess); address = (LPVOID)MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); #ifdef DEBUG DbgPrint("InsertCodeToProcessWithMDL, address: 0x%08x", address); if (eProcess) DbgPrint("Process: %s Mapped VA address: 0x%08x %02x", PsGetProcessImageFileName(eProcess), address, *(char *)address); #endif KeDetachProcess(); return address; } //address = InsertCodeToProcessWithMDL(shellcode, shellcodeSize, process, pMdl); LPVOID InsertCodeToProcessWithMDL2(LPVOID shellcode_, DWORD shellcodeSize_, PEPROCESS eProcess, PMDL pMdl) { NTSTATUS status = -1; LPVOID address = NULL; KeAttachProcess(eProcess); __asm { int 3 } kernel_memory = ExAllocatePool(NonPagedPool, shellcodeSize_); //allocate mem for MDL header and init pMdl = IoAllocateMdl(kernel_memory, shellcodeSize_, FALSE, FALSE, NULL); //Fill MDL Body to phisical frame numbers MmBuildMdlForNonPagedPool(pMdl); // MmProtectMdlSystemAddress(pMdl, PAGE_EXECUTE_READWRITE); //copy shellcode to global mem memcpy(kernel_memory, shellcode_, shellcodeSize_); address = (LPVOID)MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); DbgPrint("InsertCodeToProcessWithMDL, address: 0x%08x", address); #ifdef DEBUG if (eProcess) DbgPrint("Process: %s Mapped VA address: 0x%08x %02x", PsGetProcessImageFileName(eProcess), address, *(char *)address); #endif KeDetachProcess(); return address; }
types.h
typedef void *LPVOID; typedef unsigned long int DWORD;
sources
typedef void *LPVOID; typedef unsigned long int DWORD; TARGETNAME = kernel-injecter TARGETPATH = C:\WinDDK\7600.16385.0\MyDrivers\KernalInjectMdl #это путь к исходнику драйвера TARGETTYPE = DRIVER SOURCES = driver.c functions.c kernel-inject-mdl.c
Все вопросы прошу оставлять в коментах!
If you found an error, highlight it and press Shift + Enter or click here to inform us.