Thread Execution Hijacking (T1055.003 по MITRE ATT&CK) — техника внедрения кода, при которой атакующий перенаправляет выполнение существующего потока на произвольный адрес. В отличие от Process Hollowing, где создаётся новый процесс в приостановленном состоянии, здесь целью становится уже работающий поток — в собственном или чужом процессе. Результат одинаков: вредоносный код выполняется в контексте легитимного процесса, что затрудняет обнаружение.
Суть техники сводится к пяти шагам:
Ключевой момент: модифицируется именно
Самый простой вариант. Процесс создаёт поток в приостановленном состоянии, указывает ему «безобидную» стартовую функцию, а затем подменяет
Здесь нет межпроцессного взаимодействия, поэтому телеметрия минимальна: EDR видит только
Атакующий создаёт целевой процесс (например,
Именно эту цепочку фиксируют поведенческие детекторы. Каждый вызов по отдельности легитимен, но их комбинация в короткий промежуток времени — сильный индикатор.
Более сложный вариант: атакующий находит уже работающий поток в целевом процессе через
Для перечисления потоков используется структура
Основной индикатор — цепочка
После
Сочетание этих трёх факторов — надёжный сигнал для memory-сканера.
При захвате существующего потока в работающем процессе можно зафиксировать:
Thread Hijacking не создаёт новый поток и не выгружает оригинальный образ — он перенаправляет уже существующий поток. Это делает его ближе к Process Hollowing по эффекту (код выполняется в контексте легитимного процесса), но без характерного для hollowing
Для более глубокого понимания контекста внедрения кода в Windows см. Process Injection в Windows: механизм на уровне концепции и артефакты для детектирования, а для анализа in-memory артефактов — Детект в памяти Windows: почему in-memory активность оставляет признаки и как их анализируют защитники.
Базовая механика захвата потока
Суть техники сводится к пяти шагам:
- Получение дескриптора потока. Для собственного процесса используется
CreateThreadс флагомCREATE_SUSPENDEDилиSuspendThreadдля уже работающего потока. Для чужого процесса —OpenThreadс правамиTHREAD_ALL_ACCESS.
- Выделение памяти под payload.
VirtualAlloc(локально) илиVirtualAllocEx(удалённо) с флагамиMEM_COMMIT | MEM_RESERVEи начальными правамиPAGE_READWRITE.
- Запись кода.
memcpy(локально) илиWriteProcessMemory(удалённо).
- Смена прав памяти.
VirtualProtect/VirtualProtectExпереводит регион вPAGE_EXECUTE_READWRITE.
- Перенаправление исполнения.
GetThreadContextчитает текущее состояние регистров, полеRip(x86-64) заменяется на адрес payload,SetThreadContextзаписывает изменённый контекст обратно. ПослеResumeThreadпоток начинает выполнять внедрённый код.
Ключевой момент: модифицируется именно
Rip — указатель на следующую инструкцию. Для минимального захвата достаточно установить флаг CONTEXT_CONTROL в поле ContextFlags структуры CONTEXT, что ограничивает область изменения регистрами управления (на x86-64 это Rip, Rsp и флаги процессора).Три сценария применения
Локальный захват собственного потока
Самый простой вариант. Процесс создаёт поток в приостановленном состоянии, указывает ему «безобидную» стартовую функцию, а затем подменяет
Rip до вызова ResumeThread:
C:
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&DummyFunction,
NULL, CREATE_SUSPENDED, NULL);
// ... выделение памяти, запись payload, смена прав ...
CONTEXT ctx = { .ContextFlags = CONTEXT_CONTROL };
GetThreadContext(hThread, &ctx);
ctx.Rip = (DWORD64)pPayload;
SetThreadContext(hThread, &ctx);
ResumeThread(hThread);
Здесь нет межпроцессного взаимодействия, поэтому телеметрия минимальна: EDR видит только
CreateThread с CREATE_SUSPENDED и последующий ResumeThread.Захват потока в удалённом процессе через CreateProcess
Атакующий создаёт целевой процесс (например,
notepad.exe или ctfmon.exe) с флагом CREATE_SUSPENDED, получает hThread из структуры PROCESS_INFORMATION, выделяет память через VirtualAllocEx, записывает shellcode через WriteProcessMemory, меняет контекст и возобновляет поток. Последовательность API-вызовов:
Код:
CreateProcessA(..., CREATE_SUSPENDED, ...)
VirtualAllocEx(hProcess, ...)
WriteProcessMemory(hProcess, ...)
VirtualProtectEx(hProcess, ..., PAGE_EXECUTE_READWRITE, ...)
GetThreadContext(hThread, ...)
SetThreadContext(hThread, ...) // Rip = адрес shellcode
ResumeThread(hThread)
Именно эту цепочку фиксируют поведенческие детекторы. Каждый вызов по отдельности легитимен, но их комбинация в короткий промежуток времени — сильный индикатор.
Захват существующего потока в работающем процессе
Более сложный вариант: атакующий находит уже работающий поток в целевом процессе через
CreateToolhelp32Snapshot с флагом TH32CS_SNAPTHREAD, перебирает потоки через Thread32First / Thread32Next, открывает нужный через OpenThread, приостанавливает его через SuspendThread, модифицирует контекст и возобновляет. Этот сценарий не создаёт новых процессов и потоков, что делает его менее заметным для детекторов, ориентированных на CreateProcess / CreateThread.Для перечисления потоков используется структура
THREADENTRY32, содержащая поля th32ThreadID (идентификатор потока) и th32OwnerProcessID (PID процесса-владельца). Атакующий фильтрует потоки по th32OwnerProcessID, чтобы найти принадлежащие целевому процессу.Признаки в телеметрии
Последовательность API-вызовов
Основной индикатор — цепочка
SuspendThread → VirtualAllocEx → WriteProcessMemory → SetThreadContext → ResumeThread в адресном пространстве другого процесса. EDR-решения, работающие через kernel callbacks, способны отследить эту последовательность.Аномалии контекста потока
После
SetThreadContext значение Rip указывает на регион памяти, который:- не принадлежит ни одному загруженному модулю (адрес не попадает в диапазон какого-либо отображённого образа);
- имеет права
PAGE_EXECUTE_READWRITE(редко встречается у легитимного кода);
- был выделен незадолго до изменения контекста.
Сочетание этих трёх факторов — надёжный сигнал для memory-сканера.
Межпроцессный доступ к потоку
При захвате существующего потока в работающем процессе можно зафиксировать:
SuspendThreadвызван из другого процесса (межпроцессный вызов);
- после
ResumeThreadпоток начинает выполнять код из региона, не привязанного к загруженному модулю;
OpenThreadзапрошен с правамиTHREAD_ALL_ACCESSиз процесса, не являющегося родительским для целевого.
Артефакты в ETW и Sysmon
| Событие | Источник | Что искать |
|---|---|---|
CreateProcess с CREATE_SUSPENDED | Sysmon Event ID 1 | Флаг создания в командной строке или логе |
CreateRemoteThread / OpenThread | Sysmon Event ID 8 / EDR | Межпроцессный доступ к потоку |
Выделение памяти с PAGE_EXECUTE_READWRITE | EDR memory events | RWX-регион в чужом процессе |
SetThreadContext | EDR / kernel-level мониторинг | Изменение Rip на немодульный адрес |
Поведенческие паттерны известных семейств
- Gazer (Turla) — внедряет оркестратор в работающий поток удалённого процесса через Thread Execution Hijacking.
- Pikabot — создаёт приостановленный
ctfmon.exe, выделяет память под основной модуль, перенаправляет поток черезSetThreadContext.
- Waterbear — использует thread injection для внедрения shellcode в процессы security-продуктов.
- Trojan.Karagany — внедряет приостановленный поток собственного процесса в новый процесс и инициирует выполнение через
ResumeThread.
Отличия от смежных техник
| Техника | Цель | Создание процесса |
|---|---|---|
| Thread Execution Hijacking | Существующий поток | Не требуется (или CREATE_SUSPENDED) |
| Process Hollowing | Новый процесс в suspended-состоянии | Да, с последующим unmap оригинального образа |
| APC Injection | Очередь APC целевого потока | Не требуется |
| CreateRemoteThread | Новый поток в чужом процессе | Не требуется, но создаётся новый поток |
Thread Hijacking не создаёт новый поток и не выгружает оригинальный образ — он перенаправляет уже существующий поток. Это делает его ближе к Process Hollowing по эффекту (код выполняется в контексте легитимного процесса), но без характерного для hollowing
NtUnmapViewOfSection.Ограничения и нюансы для защитника
- CONTEXT_CONTROL vs CONTEXT_ALL. Если атакующий использует
CONTEXT_CONTROL, изменяются только регистры управления (Rip,Rsp, флаги). ЕслиCONTEXT_ALL— все регистры. Для детектирования это не принципиально: важен сам фактSetThreadContextс немодульным адресом.
- 32-битные процессы. На x86 вместо
RipмодифицируетсяEip. СтруктураCONTEXTи флагиContextFlagsаналогичны, но размер указателя 4 байта.
- Отсутствие нового потока. В отличие от
CreateRemoteThread, Thread Hijacking не порождает новый поток. Детекторы, настроенные исключительно на создание потоков в чужих процессах, пропустят эту технику.
- Легитимное использование. Отладчики и некоторые системные утилиты используют
SetThreadContextв штатном режиме. При расследовании важно учитывать контекст: какой процесс вызывает, какой поток модифицируется, и куда указывает новыйRip.
Практический чек-лист для расследования
- Проверьте наличие процессов, созданных с
CREATE_SUSPENDED, которые затем были возобновлены с изменённым контекстом.
- Ищите
VirtualAllocEx+WriteProcessMemoryв паре сSetThreadContextв одном временном окне (обычно < 1 секунды).
- Сканируйте память процессов на RWX-регионы, не привязанные к загруженным модулям.
- Сравнивайте
StartAddressпотока с фактическимRipпосле возобновления — расхождение указывает на подмену.
- Обращайте внимание на
OpenThreadсTHREAD_ALL_ACCESSиз процесса, не являющегося родительским для целевого.
- Коррелируйте события с известными TTP: Gazer, Pikabot, Waterbear, Karagany используют именно эту технику.
Для более глубокого понимания контекста внедрения кода в Windows см. Process Injection в Windows: механизм на уровне концепции и артефакты для детектирования, а для анализа in-memory артефактов — Детект в памяти Windows: почему in-memory активность оставляет признаки и как их анализируют защитники.
Источники
- Цикл статей "Изучение вредоносных программ" | Изучаем технику Thread Hijacking | Osint42.Org - Безопасность и код
- Process Injection: Thread Execution Hijacking, Sub-technique T1055.003 - Enterprise | MITRE ATT&CK®
- The Linux Kernel documentation — The Linux Kernel documentation
- Welcome to QEMU’s documentation! — QEMU documentation
