- Оригинальный адрес:Userland API Monitoring and Code Injection Detection
- Оригинальный автор:dtm
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:Xekin-FE
- Корректор:Starrier,sunhaokk
Мониторинг пользовательского API и обнаружение внедрения кода
Документация Введение
本文实属作者对恶意程式(或者病毒)是如何与 Windows 应用程序编程接口(WinAPI)进行交互的研究成果。当中详细赘述了恶意程式如何能够将 Payload [译注:Payload 指一种对设备造成伤害的程序]植入到其他进程中的基本概念,以及如何通过监控与 Windows 操作系统的通信来检测此类功能。 и черезфункциональный хукПерехватываем некоторые функции, чтобы представить процесс наблюдения за вызовами API, и эти функции используются для реализации функции внедрения кода.
заявление перед чтением: Из-за нехватки времени это относительно короткий проект. Поэтому, если вы обнаружите возможную связанную с этим неверную информацию при чтении, я заранее очень сожалею и, пожалуйста, сообщите мне как можно скорее, чтобы я мог исправить ее вовремя. Кроме того, часть кода, прикрепленная к статье, имеет определенные конструктивные недостатки в плане масштабируемости проекта, а также может быть недоступна для успешного выполнения в данный момент из-за обратной версии.
содержание
- Документация Введение
- Глава 1: Основные понятия
- Глава 2: Инструмент UnRunPE
- Глава III: инструмент Dreadnount
- Суммировать
- использованная литература
Преамбула
Сегодня вредоносное ПО разрабатывается киберпреступниками и нацелено на компьютеры, склонные к утечке информации в Интернет, выполняя вредоносные задачи на этих компьютерных системах с целью получения прибыли. В большинстве вторжений вредоносных программ эти вредоносные программы остаются вне поля зрения, поскольку их действия должны оставаться скрытыми от администраторов и предотвращать обнаружение системным антивирусным программным обеспечением. Таким образом, сделать себя «невидимым» посредством внедрения кода стало распространенным средством вторжения.
Глава 1: Основные понятия
встроенный крюк
Встроенный крючок делается черезГорячие исправленияПроцесс для обхода поведения потока кода. Исправление определяется как способ изменить поведение приложения путем изменения двоичного кода во время работы программы.[1]. Его основная цель - иметь возможность зафиксировать период, когда программа вызывает функцию, чтобы отслеживать и вызывать программу. Вот процесс моделирования встроенных хуков, когда программа работает правильно:
正常调用函数时的程序
+---------+ +----------+
| Program | ----------------------- calls function -----------------------------> | Function | | execution
+---------+ | . | | of
| . | | function
| . | |
| | v
+----------+
По сравнению с программой после выполнения функции ловушки:
程序中调用钩子函数
+---------+ +--------------+ + -------> +----------+
| Program | -- calls function --> | Intermediate | | execution | | Function | | execution
+---------+ | Function | | of calls | . | | of
| . | | intermediate normal | . | | function
| . | | function function | . | |
| . | v | | | v
+--------------+ ------------------+ +----------+
Этот процесс можно разделить на три этапа выполнения. Здесь мы можем использовать метод WinAPIMessageBoxдля демонстрации всего процесса.
- крючок в функции
Если мы хотим подключить функцию, нам сначала нужендолженПромежуточные функции, копирующие аргументы целевой функции.MessageBoxМетоды определены в Microsoft Developer Network (MSDN) следующим образом:
int WINAPI MessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCTSTR lpText,
_In_opt_ LPCTSTR lpCaption,
_In_ UINT uType
);
Таким образом, наша промежуточная функция может выглядеть так:
int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
// our code in here
}
После того, как промежуточная функция срабатывает, поток выполнения кода будет перенаправлен в определенное местоположение. ДуматьMessageBoxМетод связан, мы можем дополнить первые несколько байтов кода (запомните, мы должны поддерживать резервную копию исходного байта, чтобы позволить исходной функции восстановить исходную функцию после выполнения промежуточной функции). Ниже приведен соответствующий модуль в методе msgboxuser32.dllОригинальные инструкции по кодированию в:
; MessageBox
8B FF mov edi, edi
55 push ebp
8B EC mov ebp, esp
По сравнению с подключенной функцией:
; MessageBox
68 xx xx xx xx push <HookedMessageBox> ; our intermediate function
C3 ret
Основываясь на прошлом опыте и соображении надежности сокрытия, здесь я предпочту использоватьpush-retкомбинация инструкций вместо абсолютногоjmpутверждение.xx xx xx xxвыражатьHookedMessageBoxНизкий уровень порядка последовательности символов.
- вызов функции захвата
когда программа вызываетMessageBoxметод, он будет выполнятьсяpush-retсоответствующие инструкции и немедленно вставьтеHookedMessageBoxВ функции, если выполнение прошло успешно, можно вызвать функцию, чтобы получить полный контроль над параметрами программы и самим вызовом. Например, если вы хотите заменить текстовое содержимое, отображаемое в диалоговом окне сообщения, вы можетеHookedMessageBoxзаявляет следующее:
int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
TCHAR szMyText[] = TEXT("This function has been hooked!");
}
вszMyTextможно использовать для заменыMessageBoxсерединаLPCTSTR lpTextпараметр.
- возобновить нормальное выполнение
Чтобы переслать замененные параметры, вам нужно позволить коду выполнитьMessageBoxМетод возвращается к исходному состоянию, чтобы операционная система могла отобразить диалоговое окно. из-за продолжающегося звонкаMessageBoxМетод приведет только к бесконечной рекурсии, поэтому нам нужно восстановить исходный байт (как упоминалось ранее).
int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
TCHAR szMyText[] = TEXT("This function has been hooked!");
// 还原 MessageBox 中的原始字节
// ...
// 使用已替换参数的 MessageBox 方法,并将值返回给程序
return MessageBox(hWnd, szMyText, lpCaption, uType);
}
Отклонить призыв при необходимостиMessageBoxметод, это так же просто, как возврат значения, предпочтительно определенного в документации. Например, чтобы вернуть опцию «Отмена» в диалоговом окне «Подтвердить/Отменить», промежуточную функцию можно объявить следующим образом:
int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
return IDNO; // IDNO defined as 7
}
API-мониторинг
Основываясь на методическом механизме перехвата функций, мы можем полностью контролировать процесс вызова функции, а также управлять всеми параметрами в программе, что также упоминается в названии нашего документа реализации.API-мониторингПринцип понятия. Однако здесь все же есть небольшая проблема, то есть из-за разной полезности разных глубоких API вызовы этих API будут уникальными, а в неглубоких вызовах все они могут использовать один и тот же набор API, который называетсяфункция вложенности,определяется какВызов подпрограмм в подпрограммах. назадMessageBoxВ примере внутри метода мы объявляем две функцииMessageBoxAиMessageBoxW, первый используется для параметров, содержащих символы ASCII, а второй — для параметров, содержащих расширенные символы. На практике, если мыMessageBoxметод, вам нужноMessageBoxA и MessageBoxWПервые несколько байтов дополняются. На самом деле, столкнувшись с такой проблемой, нам достаточно вызвать функцию на уровнесамый низкийизобщественное местоПросто подключите его.
+---------+
| Program |
+---------+
/ \
| |
+------------+ +------------+
| Function A | | Function B |
+------------+ +------------+
| |
+-------------------------------+
| user32.dll, kernel32.dll, ... |
+-------------------------------+
+---------+ +-------- hook -----------------> |
| API | <---- + +-------------------------------------+
| Monitor | <-----+ | ntdll.dll |
+---------+ | +-------------------------------------+
+-------- hook -----------------> | User mode
-----------------------------------------------------
Kernel mode
Ниже приведен иерархический порядок имитации вызова метода Message:
существуетMessageBoxAсередина:
user32!MessageBoxA -> user32!MessageBoxExA -> user32!MessageBoxTimeoutA -> user32!MessageBoxTimeoutW
существуетMessageBoxWсередина:
user32!MessageBoxW -> user32!MessageBoxExW -> user32!MessageBoxTimeoutW
Послойные вызовы в приведенном выше методе в конечном итоге будут объединены вMessageBoxTimeoutWфункция, это было бы подходящей точкой крючка. Для слишком глубоких функций со сложностью параметров функции перехват любой низкоуровневой точки принесет только лишние проблемы.MessageBoxTimeoutWэто функция, не описанная в документации WinAPI, она определяется следующим образом:
int WINAPI MessageBoxTimeoutW(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType,
WORD wLanguageId,
DWORD dwMilliseconds
);
использование:
int WINAPI MessageBoxTimeoutW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType, WORD wLanguageId, DWORD dwMilliseconds) {
std::wofstream logfile; // declare wide stream because of wide parameters
logfile.open(L"log.txt", std::ios::out | std::ios::app);
logfile << L"Caption: " << lpCaption << L"\n";
logfile << L"Text: " << lpText << L"\n";
logfile << L"Type: " << uType << :"\n";
logfile.close();
// 恢复原始字节
// ...
// pass execution to the normal function and save the return value
int ret = MessageBoxTimeoutW(hWnd, lpText, lpCaption, uType, wLanguageId, dwMilliseconds);
// rehook the function for next calls
// ...
return ret; // 返回原始函数的值
}
покаMessageBoxTimeoutWПодключайтесь успешно,MessageBoxAиMessageBoxWповедение может быть зафиксировано нами.
Начало работы с методами внедрения кода
Для целей этой статьи мы определяем технологию внедрения кода как встроенное поведение, которое может вызывать и изменять исполняемый код внутри программы извне или даже удаленно. В самом WinAPI есть некоторые функции, которые позволяют реализовать встраивание. Когда некоторые из этих методов функций объединены и инкапсулированы вместе, можно получить доступ к существующим процессам, изменить записанные данные, а затем выполнить удаленное выполнение, скрытое в потоке кода. В этом разделе автор представит соответствующие методы внедрения кода, использованные в исследовании.
Техника внедрения DLL
В компьютере код может существовать во многих формах файлов, одна из которыхDynamic Link Library(DLL библиотеки динамической компоновки). Файл DLL также называется библиотекой расширения приложения.Как следует из названия, он используется для расширения других программ путем экспорта подпрограмм приложения. В остальной части этой статьи будет использоваться этот пример файла DLL:
extern "C" void __declspec(dllexport) Demo() {
::MessageBox(nullptr, TEXT("This is a demo!"), TEXT("Demo"), MB_OK);
}
bool APIENTRY DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved) {
if (fdwReason == DLL_PROCESS_ATTACH)
::CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)Demo, nullptr, 0, nullptr);
return true;
}
Когда файл DLL загружается и инициализируется в программе, загрузчик вызываетDllMainэтот метод и судитьfdwReasonУстановлен ли параметрDLL_PROCESS_ATTACH. В этом примере, когда файл DLL загружается в процессе, он пройдетDemoЭтот метод отображения сDemoтитул иThis is a demo!Окно сообщения с текстовым содержимым. Чтобы правильно инициализировать файл DLL, окно сообщения должно вернутьсяtrueзначение, иначе файл будет отклонен для выполнения.
Создать удаленный поток
CreateRemoteThreadЯвляется одним из способов внедрения DLL, который можно использовать для выполнения удаленных потоков в виртуальном пространстве процесса. Как упоминалось ранее, все, что мы делаем, это форсируем его процесс, внедряя DLL-файл.LoadLibraryфункция. Мы добьемся этого с помощью следующего кода:
void injectDll(const HANDLE hProcess, const std::string dllPath) {
LPVOID lpBaseAddress = ::VirtualAllocEx(hProcess, nullptr, dllPath.length(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
::WriteProcessMemory(hProcess, lpBaseAddress, dllPath.c_str(), dllPath.length(), &dwWritten);
HMODULE hModule = ::GetModuleHandle(TEXT("kernel32.dll"));
LPVOID lpStartAddress = ::GetProcAddress(hModule, "LoadLibraryA"); // LoadLibraryA for ASCII string
::CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpStartAddress, lpBaseAddress, 0, nullptr);
}
MSDN PARY.LoadLibraryопределяется следующим образом:
HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);
При использовании вышеуказанной функции нам нужно передать параметр, который является путем для загрузки библиотеки. пока вLoadLibraryЭтот параметр, объявленный в подпрограмме, будет переданCreateRemoteThreadСоответствующий параметр пути в методе. Цель такого поведения — иметь возможность передавать строковые аргументы в виртуальном адресном пространстве целевого процесса, а затемCreateRemoteThreadАргументы аргументов методов назначаются пространственным адресам для вызоваLoadLibraryдля загрузки DLL.
- Выделить виртуальную память в целевом процессе
использоватьVirtualAllocExФункция может указать виртуальное пространство процесса для резервирования или предоставления области памяти.После выполнения функция вернет первый адрес выделенной памяти.
目标进程的虚拟地址空间:
+--------------------+
| |
VirtualAllocEx +--------------------+
Allocated memory ---> | Empty space |
+--------------------+
| |
+--------------------+
| Executable |
| Image |
+--------------------+
| |
| |
+--------------------+
| kernel32.dll |
+--------------------+
| |
+--------------------+
- Записать путь к файлу DLL в выделенной памяти
Пока инициализация памяти успешна, путь к DLL можно внедрить вVirtualAllocExиспользоватьWriteProcessMemoryв возвращенной выделенной памяти.
目标进程的虚拟地址空间
+--------------------+
| |
WriteProcessMemory +--------------------+
Inject DLL path ----> | "..\..\myDll.dll" |
+--------------------+
| |
+--------------------+
| Executable |
| Image |
+--------------------+
| |
| |
+--------------------+
| kernel32.dll |
+--------------------+
| |
+--------------------+
- оказаться
LoadLibraryадрес
Поскольку все системные файлы DLL отображаются в одно и то же адресное пространство для всех процессов,LoadLibraryАдрес не нужно извлекать в целевом процессе. просто позвониGetModuleHandle(TEXT("kernel32.dll"))иGetProcAddress(hModule, "LoadLibraryA")Вот и все.
- Загрузите файл DLL
Если нам нужно загрузить файл DLL,LoadLibraryПути файлов адреса и DLL являются двумя основными параметрами, которые нам нужно знать. в настоящее время используетCreateRemoteThreadфункция,LoadLibraryБудет выполняться в потоке кода целевого процесса с путем к файлу DLL в качестве аргумента.
目标进程的虚拟地址空间
+--------------------+
| |
+--------------------+
+--------- | "..\..\myDll.dll" |
| +--------------------+
| | |
| +--------------------+ <---+
| | myDll.dll | |
| +--------------------+ |
| | | | LoadLibrary
| +--------------------+ | loads
| | Executable | | and
| | Image | | initialises
| +--------------------+ | myDll.dll
| | | |
| | | |
CreateRemoteThread v +--------------------+ |
LoadLibraryA("..\..\myDll.dll") --> | kernel32.dll | ----+
+--------------------+
| |
+--------------------+
Функция ловушки SetWindowsHookEx
SetWindowsHookExФункция — это API, предоставляемый Windows разработчикам программ.Он реализует функцию перехвата сообщений путем перехвата событийного процесса.Хотя эта функция часто используется для контроля ввода и записи с клавиатуры, ее также можно использовать для внедрения DLL. Следующий код демонстрирует, как внедрить DLL в само событие.
int main() {
HMODULE hMod = ::LoadLibrary(DLL_PATH);
HOOKPROC lpfn = (HOOKPROC)::GetProcAddress(hMod, "Demo");
HHOOK hHook = ::SetWindowsHookEx(WH_GETMESSAGE, lpfn, hMod, ::GetCurrentThreadId());
::PostThreadMessageW(::GetCurrentThreadId(), WM_RBUTTONDOWN, (WPARAM)0, (LPARAM)0);
// 捕捉事件的消息队列
MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0) > 0) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return 0;
}
SetWindowsHookExОн определяется в MSDN следующим образом:
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
В приведенном выше определенииHOOKPROC— это объявленная пользователем функция ловушки, которая выполняется, когда запускается определенное событие ловушки. В нашем примере это событие относится кWH_GETMESSAGEХук, он в основном отвечает за работу по обработке сообщений в очереди. Этот код представляет собой функцию обратного вызова, которая сначала загрузит файл DLL в собственное виртуальное пространство процесса, а затем получит ранее экспортированный файл.Demoадрес функции, наконец, вSetWindowsHookExобъявленная и вызванная функция. Чтобы применить эту функцию ловушки, мы просто вызываемPostThreadMessageфункции и назначьте сообщение какWM_RBUTTONDOWNможет вызватьWH_GETMESSAGEПосле хука может отображаться окно сообщения, упомянутое ранее.
Интерфейс QueueUserAPC
использоватьQueueUserAPCВнедрение DLL и методы интерфейсаCreateRemoteThreadТочно так же он вынужден вызывать поток кода после выделения и внедрения адреса DLL в виртуальное адресное пространство целевого процесса.LoadLibraryфункция.
int injectDll(const std::string dllPath, const DWORD dwProcessId, const DWORD dwThreadId) {
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessId);
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, false, dwThreadId);
LPVOID lpLoadLibraryParam = ::VirtualAllocEx(hProcess, nullptr, dllPath.length(), MEM_COMMIT, PAGE_READWRITE);
::WriteProcessMemory(hProcess, lpLoadLibraryParam, dllPath.data(), dllPath.length(), &dwWritten);
::QueueUserAPC((PAPCFUNC)::GetProcAddress(::GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryA"), hThread, (ULONG_PTR)lpLoadLibraryParam);
return 0;
}
этот метод иCreateRemoteThreadЕсть одно существенное отличие,QueueUserAPCТолькостатус предупрежденияВызывается ниже. То есть вQueueUserAPCАсинхронные программы в очереди могут вызывать функции APC только тогда, когда поток находится в состоянии предупреждения.
Кукольный технологический процесс (Процесс долбления)
Опустошение процессов, также известное как RunPE, является распространенным методом, используемым для уклонения от обнаружения антивирусом. Он может внедрить весь исполняемый файл в целевой процесс и выполнить его в своем потоке кода. Обычно в зашифрованных приложениях мы видим, что файл на диске, где хранится полезная нагрузка, будет выбран в качестве хоста и создан как процесс, а основной исполнительный модуль этого файла будетвыдолбитьи заменил. Такой процесс можно разбить на четыре шаги для выполнения.
- Создайте основной процесс
Чтобы внедрить полезную нагрузку, сначала загрузчик должен найти загрузочный основной файл. Если полезной нагрузкой является приложение .NET, то основной файл также должен быть приложением .NET. Если полезная нагрузка является собственным исполняемым файлом, который может вызывать консольную подсистему, основной файл также имеет те же свойства. Этому условию должны соответствовать как 32-битные, так и 64-битные программы. Как только главный файл найден, системная функцияCreateProcess(PATH_TO_HOST_EXE, ..., CREATE_SUSPENDED, ...)Можно создать процесс в приостановленном состоянии.
主进程中的可执行映像
+--- +--------------------+
| | PE |
| | Headers |
| +--------------------+
| | .text |
| +--------------------+
CreateProcess + | .data |
| +--------------------+
| | ... |
| +--------------------+
| | ... |
| +--------------------+
| | ... |
+--- +--------------------+
- Приостановить основной процесс
Чтобы внедренный Paylaod работал, мы должны сопоставить его с заголовком PE-образа.optional headerизImageBaseЗначение того же виртуального адресного пространства.
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; // <---- this is required later
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase; // <----
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; // <---- size of the PE file as an image
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
Это важно, потому что абсолютный адрес, скорее всего, будет полностью зависеть от их кода расположения в памяти. Чтобы безопасно выполнить отображение изображения, которое будет описано изImageBaseПространство виртуальной памяти, где значение начинает выгружать сопоставление. Поскольку многие исполняемые файлы имеют общий базовый адрес (обычно0x400000), поэтому исполняемый образ самого основного процесса нередко оказывается неотображенным. Удаление этой операции можно выполнить с помощьюNtUnmapViewOfSection(IMAGE_BASE, SIZE_OF_IMAGE)Что нужно сделать.
主进程中的可执行映像
+--- +--------------------+
| | |
| | |
| | |
| | |
| | |
NtUnmapViewOfSection + | |
| | |
| | |
| | |
| | |
| | |
| | |
+--- +--------------------+
- Впрыск полезной нагрузки
Чтобы внедрить полезную нагрузку, нам нужно вручную проанализировать PE-файл и преобразовать его из формата диска в формат изображения. в настоящее время используетVirtualAllocExПосле завершения выделения виртуальной памяти головной образ PE копируется непосредственно на базовый адрес.
主进程中的可执行映像
+--- +--------------------+
| | PE |
| | Headers |
+--- +--------------------+
| | |
| | |
WriteProcessMemory + | |
| |
| |
| |
| |
| |
| |
+--------------------+
А если вы хотите преобразовать PE-файл в образ, то все блоки (разделы) нужно читать по одному из смещения файла, а затем с помощьюWriteProcessMemoryПоместите его в правильное виртуальное смещение. Каждая глава в этом документе MSDNsection headerвводятся.
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; // <---- 虚拟偏移量
DWORD SizeOfRawData;
DWORD PointerToRawData; // <---- 文件偏移量
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
主进程中的可执行映像
+--------------------+
| PE |
| Headers |
+--- +--------------------+
| | .text |
+--- +--------------------+
WriteProcessMemory + | .data |
+--- +--------------------+
| | ... |
+---- +--------------------+
| | ... |
+---- +--------------------+
| | ... |
+---- +--------------------+
- Выполнить полезную нагрузку
Последний шаг — указать первый адрес выполнения на упомянутую выше полезную нагрузку (создать основной процесс)AddressOfEntryPoint. Поскольку основной поток процесса уже приостановлен, вы можете использоватьGetThreadContextспособ получения соответствующей информации. Его структуру кода можно объявить следующим образом:
typedef struct _CONTEXT
{
ULONG ContextFlags;
ULONG Dr0;
ULONG Dr1;
ULONG Dr2;
ULONG Dr3;
ULONG Dr6;
ULONG Dr7;
FLOATING_SAVE_AREA FloatSave;
ULONG SegGs;
ULONG SegFs;
ULONG SegEs;
ULONG SegDs;
ULONG Edi;
ULONG Esi;
ULONG Ebx;
ULONG Edx;
ULONG Ecx;
ULONG Eax; // <----
ULONG Ebp;
ULONG Eip;
ULONG SegCs;
ULONG EFlags;
ULONG Esp;
ULONG SegSs;
UCHAR ExtendedRegisters[512];
} CONTEXT, *PCONTEXT;
Если мы хотим изменить первый адрес, мы должны изменить указанный вышеEaxЭлемент данных изменен на PayloadAddressOfEntryPointизвиртуальный адрес. просто означает,context.Eax = ImageBase + AddressOfEntryPoint. перечислитьSetThreadContextметод и передать измененныйCONTEXTСтруктура, мы можем применить изменения к потоку процесса. После того, как мы теперь просто позвонимResumeThreadПолезная нагрузка должна начать выполнение.
Технология атомной бомбардировки
Атомная бомбардировка — это метод внедрения кода, использующий преимущества Windows.глобальная таблица атомовреализовать глобальное хранилище данных. Данные в глобальной таблице атомов доступны для всех процессов, поэтому мы можем реализовать внедрение кода. Данные в таблице представляют собой тип C-строки с завершающим нулем, представленный 16-битным целым числом, которое мы называемАтом, что похоже на структуру данных карты. Предоставлено в MSDNGlobalAddAtomдля добавления к нему данных используются методы, объявленные следующим образом:
ATOM WINAPI GlobalAddAtom(
_In_ LPCTSTR lpString
);
вlpString— это данные для сохранения, и при успешном вызове метода будет возвращен 16-разрядный целочисленный атом. мы можем пройтиGlobalGetAtomNameЧтобы получить данные, хранящиеся в глобальной таблице атомов, объявите следующим образом:
UINT WINAPI GlobalGetAtomName(
_In_ ATOM nAtom,
_Out_ LPTSTR lpBuffer,
_In_ int nSize
);
пройти черезGlobalAddAtomАтом идентичности, возвращаемый методом add, будет помещен вlpBufferin и возвращает длину строки (не содержитнулевой терминатор).
Атомная бомбардировка работает, заставляя целевой процесс загружать и выполнять код, хранящийся в глобальной таблице атомов, которая зависит от другой ключевой функции,NtQueueApcThread,ОдинQueueUserAPCМетод вызова интерфейса в пользовательском домене. зачем использоватьNtQueueApcThreadвместоQueueUserAPCПричина использования других методов, как было показано ранее,QueueUserAPCизAPCProcМетод может принимать только один параметр, иGlobalGetAtomNameТребуются три параметра[3].
VOID CALLBACK APCProc( UINT WINAPI GlobalGetAtomName(
_In_ ATOM nAtom,
_In_ ULONG_PTR dwParam -> _Out_ LPTSTR lpBuffer,
_In_ int nSize
); );
Однако вNtQueueApcThreadНижележащий слой позволит нам передать три потенциальных параметра:
NTSTATUS NTAPI NtQueueApcThread( UINT WINAPI GlobalGetAtomName(
_In_ HANDLE ThreadHandle, // target process's thread
_In_ PIO_APC_ROUTINE ApcRoutine, // APCProc (GlobalGetAtomName)
_In_opt_ PVOID ApcRoutineContext, -> _In_ ATOM nAtom,
_In_opt_ PIO_STATUS_BLOCK ApcStatusBlock, _Out_ LPTSTR lpBuffer,
_In_opt_ ULONG ApcReserved _In_ int nSize
); );
Вот как мы графически имитируем внедрение кода:
Atom bombing code injection
+--------------------+
| |
+--------------------+
| lpBuffer | <-+
| | |
+--------------------+ |
+---------+ | | | Calls
| Atom | +--------------------+ | GlobalGetAtomName
| Bombing | | Executable | | specifying
| Process | | Image | | arbitrary
+---------+ +--------------------+ | address space
| | | | and loads shellcode
| | | |
| NtQueueApcThread +--------------------+ |
+---------- GlobalGetAtomName ----> | ntdll.dll | --+
+--------------------+
| |
+--------------------+
Это очень упрощенный обзор атомной бомбардировки, но его должно быть достаточно для остальной части этой статьи. Если у Юяо есть дополнительная техническая информация об атомной бомбардировке, пожалуйста, обратитесь к enSilo.AtomBombing: Brand New Code Injection for Windows.
Глава 2: Инструмент UnRunPE
UnRunPE — это инструмент проверки концепции (PoC), написанный для применения теоретических концепций мониторинга API к практическим операциям. Цель инструмента — создать и приостановить выбранный исполняемый файл как процесс, а затем внедрить DLL с перехваченными функциями в процесс посредством очистки процесса.
Обнаружение внедрения кода
Поняв базовые знания о соответствующем внедрении кода, вы можете реализовать метод внедрения технологии марионеточных процессов с помощью следующей цепочки вызовов функций WinAPI:
CreateProcessNtUnmapViewOfSectionVirtualAllocExWriteProcessMemoryGetThreadContextSetThreadContextResumeThread
На самом деле некоторые из них не обязательно выполняются именно в таком порядке, например,GetThreadContextдопустимыйVirtualAllocExзвонил раньше. Однако, поскольку некоторые методы должны полагаться на ранее вызванный API, напримерSetThreadContext долженбыть вGetThreadContextилиCreateProcessВызывается перед вызовом, в противном случае полезная нагрузка не может быть внедрена в целевой процесс. Инструмент попытается обнаружить потенциальные марионеточные процессы, взяв за основу приведенную выше последовательность вызовов.
Следуя теории мониторинга API, мы лучше всего работаем на самом низком уровне вызовов функций.публичныйКогда точка крюка, но когда вторжение вредоносного программного обеспечения, мы должны быть максимально доступны для минимума. Предполагается, что в худшем случае злоумышленник может попытаться обойти функцию WinAPI высокого уровня и направить вызовы функциям самого низкого уровня, которые обычноntdll.dllможно найти в модуле. Следующие функции WinAPI часто вызываются в процессе puppet для выполнения вышеуказанных требований:
NtCreateUserProcessNtUnmapViewOfSectionNtAllocateVirtualMemoryNtWriteVirtualMemoryNtGetContextThreadNtSetContextThreadNtResumeThread
дамп инъекции кода
Как только мы перехватываем нужную функцию, целевой процесс выполняется, и параметры каждой перехваченной функции регистрируются, чтобы мы могли отслеживать текущий прогресс марионеточного процесса, а также основного процесса. В частностиNtWriteVirtualMemoryиNtResumeThreadЭти две функции хука, потому что первый участвует в внедрении кода, а второй его выполняет. В дополнение к параметрам регистрации UnRunPE также пытается создать дамп, используяNtWriteVirtualMemoryбайт записано и при выполненииNtResumeThread, он попытается сбросить всю полезную нагрузку, введенную в основной процесс. Для этого функция должна будет использоватьNtCreateUserProcessПроцесс записи и поток обрабатывает параметры иNtUnmapViewOfSectionБазовый адрес записи и ее размер. Здесь, если использоватьNtAllocateVirtualMemoryможет быть более подходящим, но на практике по какой-то неизвестной причине возникает ошибка при перехвате функции. когда прошлоNtResumeThreadПосле успешного дампа полезной нагрузки он завершает целевой процесс и его хост-процесс, а также предотвращает выполнение внедренного кода.
Пример UnRunPE
Чтобы продемонстрировать это, я решил поэкспериментировать с созданным ранее бинарным троянским файлом. файл содержитPEview.exeа такжеPuTTY.exeкак скрытый исполняемый файл.
Глава 3: Инструменты дредноута
Dreadnought — это PoC-инструмент, построенный на UnRunPE, который обеспечивает более разнообразное обнаружение внедрения кода.Начало работы с внедрением кодавсего содержания. Чтобы сделать приложение более полным для обнаружения внедрения кода, также необходимо усилить функцию инструмента.
Как обнаружить внедрение кода
Существует множество способов реализации внедрения кода, поэтому мы должны понимать разницу между различными методами. Первый способ обнаружения внедрения кода — определение «триггера», вызывающего API, то есть вызывающего API, ответственного за удаленное выполнение полезной нагрузки. Идентифицируя, мы можем определить, как выполняется внедрение кода, и в некоторой степени тип внедрения кода. ТоттипОн подразделяется на следующие четыре типа:
- Блок (раздел): Вставьте код в блок (раздел).
- Процесс: внедрить код в процесс.
- Код: с помощью внедрения кода или переполнения кода (шеллкод).
- DLL: монтирование кода загружается в DLL.
Зависит отKarsten HahnВнедрение созданного кода в графический процесс[4].
Как показано на рисунке выше (если изображение не загружается, перейдите в репозиторий Github, чтобы просмотреть исходный текст), каждый триггер API указан вExecuteВ этом столбце при выполнении любого из этих триггеров инструмент Dreadnought немедленно создаст дамп кода, после чего он идентифицирует код и сопоставит тип внедрения, предполагаемый ранее, аналогично тому, как инструмент UnRunPE обрабатывает марионеточные процессы, но Одного этого недостаточно, потому что любое поведение, запускающее API, может привести к путанице в различных базовых методах вызова, и, наконец, функция, на которую указывает стрелка на приведенном выше рисунке, все еще может быть достигнута.
Эвристическое логическое обнаружение
Алгоритмы эвристической логики позволят нашему инструменту Dreadnought более точно определять методы внедрения кода. Таким образом, в реальной разработке мы используем очень простую эвристическую логику. Из нашей инфографики внедрения процесса каждый раз, когда какой-либо API перехватывается, алгоритм будет увеличивать вес одного или нескольких связанных типов внедрения кода и сохранять их в структуре данных карты. Отслеживая цепочку вызовов для каждого API, он пытается отдать предпочтение определенному типу внедрения. После срабатывания триггера API он идентифицирует и сравнивает веса каждого связанного типа инъекции и предпринимает соответствующие действия.
Пример дредноута
Процесс долбления
SetWindowsHookEx для внедрения DLL
QueueUserAPC для внедрения DLL
Атомная бомбардировка внедрения кода
Суммировать
Эта статья призвана дать читателю некоторый уровень технического понимания внедрения кода и его взаимодействия с WinAPI. Кроме того, концепция мониторинга вызовов API в пользовательском домене также была злонамеренно использована для обхода антивирусного обнаружения. Ниже приводится описание фактического использования инструмента Dreadnought в этой статье.
Недостатки этого документа в практическом применении
В настоящее время в теории метода проектирования обнаружения и эвристического алгоритма инструмента Dreadnought нам действительно достаточно, чтобы продемонстрировать и сообщить читателю соответствующие принципиальные знания, но невозможно быть настолько идеальным в реальной разработке. Потому что при нормальной работе нашей операционной системы очень велика вероятность того, что существуют альтернативы тем API, которые используются для перехвата. И они могут заменить их действия или вызовы, мы не можем сказать, являются ли они вредоносными или нет, и мы не можем определить, участвуют ли они во внедрении кода.
С этой точки зрения инструмент Dreadnought и связанные с ним операции для пользовательского домена не идеальны при борьбе с чрезмерно сложными вредоносными программами, особенно с теми, которые могут напрямую проникать и взаимодействовать с ядром системы или иметь возможность избежать общих вредоносных программ. программы с возможностью перехвата и т. д.
Склад Код POC
использованная литература
- [1] Woohoo.black hat.com/present Вопрос о...
- [2] Project.com/articles/79…
- [3] blog.Нажмите, чтобы умереть.com/atom сумеречный лед…
- [4] Это вход leather.blogspot.com.AU/2017/07/pro…
- ReactOs
- NTAPI Undocumented Functions
- ntcoder
- GitHub - Process Hacker
- YouTube - MalwareAnalysisForHedgehogs
- YouTube - OALabs
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.