Thread Hijacking в Windows: управление потоком и признаки, полезные для расследования

Thread Execution Hijacking (T1055.003 по MITRE ATT&CK) — техника внедрения кода, при которой атакующий перенаправляет выполнение существующего потока на произвольный адрес. В отличие от Process Hollowing, где создаётся новый процесс в приостановленном состоянии, здесь целью становится уже работающий поток — в собственном или чужом процессе. Результат одинаков: вредоносный код выполняется в контексте легитимного процесса, что затрудняет обнаружение.

Базовая механика захвата потока​


Суть техники сводится к пяти шагам:

  1. Получение дескриптора потока. Для собственного процесса используется CreateThread с флагом CREATE_SUSPENDED или SuspendThread для уже работающего потока. Для чужого процесса — OpenThread с правами THREAD_ALL_ACCESS.
  2. Выделение памяти под payload. VirtualAlloc (локально) или VirtualAllocEx (удалённо) с флагами MEM_COMMIT | MEM_RESERVE и начальными правами PAGE_READWRITE.
  3. Запись кода. memcpy (локально) или WriteProcessMemory (удалённо).
  4. Смена прав памяти. VirtualProtect / VirtualProtectEx переводит регион в PAGE_EXECUTE_READWRITE.
  5. Перенаправление исполнения. 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_SUSPENDEDSysmon Event ID 1Флаг создания в командной строке или логе
CreateRemoteThread / OpenThreadSysmon Event ID 8 / EDRМежпроцессный доступ к потоку
Выделение памяти с PAGE_EXECUTE_READWRITEEDR memory eventsRWX-регион в чужом процессе
SetThreadContextEDR / 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.

Практический чек-лист для расследования​


  1. Проверьте наличие процессов, созданных с CREATE_SUSPENDED, которые затем были возобновлены с изменённым контекстом.
  2. Ищите VirtualAllocEx + WriteProcessMemory в паре с SetThreadContext в одном временном окне (обычно < 1 секунды).
  3. Сканируйте память процессов на RWX-регионы, не привязанные к загруженным модулям.
  4. Сравнивайте StartAddress потока с фактическим Rip после возобновления — расхождение указывает на подмену.
  5. Обращайте внимание на OpenThread с THREAD_ALL_ACCESS из процесса, не являющегося родительским для целевого.
  6. Коррелируйте события с известными TTP: Gazer, Pikabot, Waterbear, Karagany используют именно эту технику.

Для более глубокого понимания контекста внедрения кода в Windows см. Process Injection в Windows: механизм на уровне концепции и артефакты для детектирования, а для анализа in-memory артефактов — Детект в памяти Windows: почему in-memory активность оставляет признаки и как их анализируют защитники.

Источники​


 

Похожие темы

Назад
Верх Низ