Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Статья и плагин x64dbg для обхода антидебага VMP 3.9.X, не требуя драйверов.

кто прочитал тот будет жить
Пользователь
Пользователь
Статус
Онлайн
Регистрация
5 Сен 2022
Сообщения
247
Реакции
64
приношу извинения, если пост не читабелен или где то ошибки я немного пьян, но, вроде, это полезно. :da: :da:


недавно я исследовал методы антидебага в версиях VMP 3.8 и 3.9. несмотря на небольшие различия, в целом они идентичны, и методы их обхода остаются неизменными. ниже приведен общий обзор процесса на примере версии 3.9.

Код:
Expand Collapse Copy
 3.9 логика антидебага

  peb

 NtSetInformationProcess: предотвращение syscall хукинга

 NtOpenFile: обнаружение драйвера ??\TitanHide, обход через C0000034

 NtQueryInformationProcess: запрос DebugPort, установка 0 для обхода

 NtQueryInformationProcess: запрос DebugObjectHandle, rax установлен в C0000353 для обхода

 NtCreateDebugObject--->ZwQueryObject--->NtClose??? я забыл сори

 ZwSetInformationThread: hidefromdebugger, rdx установлен в 0 для обхода


 Проверка целостности


 NtQueryInformationProcess: запрос DebugPort, установка 0 для обхода 19

 ZwSetInformationThread: hidefromdebugger, rdx установлен в 0 для обхода D

 NtClose: закрытие невалидного хэндла, rax установлен в C0000008, прямой ret для обхода

 триггер исключения одиночного шага, обнаружение аппаратных бряков
1776937715340.png

в старших версиях VMP используется инструкция rdtsc для выполнения системных вызовов по случайным путям. для симуляции выполнения я использовал unicorn. после обнаружения инструкции rdtsc я устанавливал брейкпоинт на следующей инструкции, запускал отладчик до этого момента, а затем продолжал симуляцию до достижения системного вызова или апи функции, после чего проверял логи.

как можно заметить, симуляция останавливается при встрече с системным вызовом. в этот момент отладчик доходит до точки останова, и через функцию callback аппаратная точка останова удаляется. это позволяет перехватывать системный вызов. таков общий алгоритм. перейдем к анализу самих методов защиты.
1776938140225.png

для начала рассмотрим простую проверку PEB. обойдите debug flag и heap flag (это уже думаю знают все как кделать) - лично я на этом этапе включил бы scyllahide

еще рекомендую устанавливать брейкпоинт на ntclose (избегайте брейкпоинтов в заголовках или использования аппаратных брейкпоинтов). скрытие дебаггера вместе с брейкпоинтом на ntclose предотвращает прерывания от определенных апи вызовов, которые не влияют на работу дебаггера. если сразу после установки брейкпоинта на ntclose появляется всплывающее окно, следует проследить откуда оно.

анализ (NtSetInformationProcess и NtOpenFile)

в системном вызове регистр rax содержит номер функции. в данном случае он соответствует NtSetInformationProcess, а rdx равен 0x28. согласно документации, эта настройка используется для предотвращения трассировки системных вызовов. если установлен callback, то возврат из ring 3 после вызова произойдет не на следующую инструкцию, а на определенную вами функцию. рекомендую протестировать это самостоятельно.

далее вызывается NtOpenFile для обнаружения драйвера ??\TitanHide. если в системе его нет — беспокоиться не о чем. если есть, можно попробовать вернуть код C0000034 для обхода проверки.
1776942220706.png

в этот момент rax равен 0x33, а строку \??\TitanHide можно найти через r8.

данные проверки (NtOpenFile, PEB) чаще встречаются на этапе инициализации лоадера. В самом виртуализированном коде основной упор идет на syscall-ы.

проверки NtQueryInformationProcess

  • DebugPort: запрашивается через NtQueryInformationProcess. чтобы обойти проверку, измените выходной параметр с -1 (0xffffffffffffffff) на 0.
    1776939761542.png
  • DebugObjectHandle: установите rax в значение C0000353 для обхода.
    1776939861449.png

RtlFreeHeap и самопроверка

после этого вызываются некоторые апи и ставятся точки останова на RtlFreeHeap. симуляция здесь не всегда обязательна (зависит от конкретной программы), но если при установке брейкпоинта на RtlFreeHeap обнаруживается вмешательство используйте симуляцию снова.

ZwSetInformationThread (HideFromDebugger)

1776940023072.png

сначала установите rdx в 0. далее защищенный участок кода выполняет самопроверку. поскольку этот этап длительный, лучше его не симулировать. вместо этого установите постоянный брейкпоинт на первый байт секции с защищенным участком кода.
1776940168293.png

программа остановится. найдите следующую инструкцию jne. она может быть замаскирована через call, jne или jmp, но ваша задача найти верный переход. этот jne обязательно ведет к movzx esi, byte ptr ds:[rcx] или инструкциям над ней.

важно понимать, что из-за мутации VMP код будет отличаться от билда к билду. Мой пример с jne это лишь один из вариантов логического пути, который нужно искать по поведению.

установите аппаратный брейкпоинт на инструкцию, следующую за jne (на схеме это mov eax, 0x88BAC81D). программа остановится здесь несколько раз. чтобы понять точное количество, нажимайте F9, пока не появится окно отладчика. запомните это число и перезапустите процесс. в моем случае хватило 5 остановок перед продолжением симуляции.

этап проверки​

в конце процесс проверяет четыре ключевых момента:

  1. запрос DebugPort через NtQueryInformationProcess.
  2. установка ZwSetInformationThread (HideFromDebugger) в 0.
  3. установка rdx в 0 для обхода NtClose.
  4. закрытие невалидного дескриптора (установите rax в C0000008).(в версии 3.8 для CloseHandle требуется rax = 0). это инициирует прерывание для обнаружения пошаговой отладки.
после завершения выполнения ZwSetInformationThread:

  • версия 3.9 останавливается прямо на ntclose.
  • версия 3.8 останавливается на closehandle.
    1776940422913.png
установите rax в C0000008 и измените rip на ret. затем удалите все аппаратные точки останова, нажмите F9, и программа успешно запустится.



код плагина:
plugin bypa$$ vmp 3.9:
Expand Collapse Copy
#include "pch.h"
#include <windows.h>
#include <vector>
#include <string>
#include "pluginsdk/bridgemain.h"
#include "pluginsdk/_plugins.h"
#include "pluginsdk/_scriptapi_module.h"
#include "pluginsdk/_scriptapi_memory.h"
#include "pluginsdk/_scriptapi_debug.h"
#include "unicorn/unicorn.h"

#pragma comment(lib, "unicorn.lib")
#pragma comment(lib, "x64dbg.lib")
#pragma comment(lib, "x64bridge.lib")

#define MENU_UNICORN_ID 1

int pluginHandle;
duint mainModuleBase = 0;
duint mainModuleSize = 0;
int hMenu;


HANDLE g_hSimulateEvent = NULL;       // для начала рабочего потока
HANDLE g_hSimulateThread = NULL;       // хендл рабочего потока
BOOL   g_bStopSimulation = FALSE;      // флаг для остановки рабочего потока
BOOL   g_bWaitingForBreakpoint = FALSE;// флаг ожидания аппаратной точки останова (rdtsc)
duint  g_rdtscNextRip = 0;             // адрес следующей инструкции после rdtsc
int    g_stop_reason = 0;              // причина остановки: 1=rdtsc, 2=syscall, 3=вне_модуля
BOOL   gsreg = FALSE;
BOOL   api = FALSE;

static void hook_code(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {

    WORD code = 0;
    uc_mem_read(uc, address, &code, 2);
    if (code == 0x310f) {  // инструкция rdtsc
        _plugin_logprintf("rdtsc по адресу: %llx\n", address);
        g_stop_reason = 1;
        g_rdtscNextRip = address + size;  // адрес следующей инструкции
        uc_emu_stop(uc);
        return;
    }
    if (code == 0x50f) {   // инструкция syscall
        _plugin_logprintf("syscall по адресу: %llx\n", address);
        g_stop_reason = 2;
        uc_emu_stop(uc);
        return;
    }
    // если адрес инструкции вне диапазона основного модуля, останавливаем эмуляцию
    if (address < mainModuleBase || address >= (mainModuleBase + mainModuleSize)) {
        _plugin_logprintf("[Unicorn] остановка эмуляции, возврат управления в x64dbg. Адрес API: %llx\n", address);
        g_stop_reason = 3;
        uc_emu_stop(uc);
    }
}

static bool hook_mem_unmapped(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data) {
    uint64_t current_rip;
    uc_reg_read(uc, UC_X86_REG_RIP, &current_rip);
    _plugin_logprintf("[Unicorn] MEM_UNMAPPED на RIP=0x%llX: тип=%d, адрес=0x%llX, размер=%d\n",
        current_rip, type, address, size);

    duint pageBase = (duint)address & ~0xFFF;
    if (!Script::Memory::IsValidPtr(pageBase)) {
        _plugin_logprintf("[Unicorn] ошибка: попытка доступа по невалидному указателю 0x%llX\n", pageBase);
        uc_emu_stop(uc);
        return false;
    }

    _plugin_logprintf("[Unicorn] сработал Page fault, мапирование страницы памяти: 0x%llX\n", pageBase);

    uc_err err = uc_mem_map(uc, pageBase, 0x1000, UC_PROT_ALL);
    if (err != UC_ERR_OK && err != UC_ERR_MAP) {
        _plugin_logprintf("[Unicorn] ошибка мапирования памяти: %s\n", uc_strerror(err));
        return false;
    }

    unsigned char pageBuf[0x1000] = { 0 };
    if (Script::Memory::Read(pageBase, pageBuf, 0x1000, nullptr)) {
        uc_mem_write(uc, pageBase, pageBuf, 0x1000);
        return true;
    }
    return false;
}

// эмуляция
DWORD WINAPI SimulationWorker(LPVOID lpParam) {
    while (!g_bStopSimulation) {
        // ожидание срабатывания события
        WaitForSingleObject(g_hSimulateEvent, INFINITE);
        ResetEvent(g_hSimulateEvent);

        // если поток проснулся из за точки останова, удаляем старую аппаратную точку останова
        if (g_bWaitingForBreakpoint) {
            Script::Debug::DeleteHardwareBreakpoint(g_rdtscNextRip);
            g_bWaitingForBreakpoint = FALSE;
        }

        // повторная инициализация движка Unicorn
        uc_engine* uc;
        uc_err err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc);
        if (err != UC_ERR_OK) {
            _plugin_logprintf("uc_open неудачно: %u\n", err);
            continue;
        }

        // получаем диапазон основного модуля (каждый раз заново, т.к. отладчик мог загрузить новые модули)
        mainModuleBase = Script::Module::GetMainModuleBase();
        mainModuleSize = Script::Module::SizeFromAddr(mainModuleBase);

        // мапируем все выделенные страницы памяти в unicorn
        MEMMAP memoryMap = { 0 };
        DbgMemMap(&memoryMap);
        for (int i = 0; i < memoryMap.count; i++) {
            auto& page = memoryMap.page[i];
            if (page.mbi.State == MEM_COMMIT) {
                duint base = (duint)page.mbi.BaseAddress;
                SIZE_T size = page.mbi.RegionSize;
                duint alignedBase = base & ~0xFFF;
                duint alignedSize = (size + 0xFFF) & ~0xFFF;
                uc_mem_map(uc, alignedBase, alignedSize, UC_PROT_ALL);
                std::vector<uint8_t> buffer(size);
                if (Script::Memory::Read(base, buffer.data(), size, nullptr)) {
                    uc_mem_write(uc, base, buffer.data(), size);
                }
            }
        }

        // синхронизация состояния регистров (64 бит)
        REGDUMP_AVX512 regdump = { 0 };
        if (!DbgGetRegDumpEx(&regdump, sizeof(regdump))) {
            _plugin_logprintf("[Unicorn] не удалось получить регистры, эмуляция пропущена\n");
            uc_close(uc);
            continue;
        }

        uint64_t rax = regdump.regcontext.cax;
        uint64_t rbx = regdump.regcontext.cbx;
        uint64_t rcx = regdump.regcontext.ccx;
        uint64_t rdx = regdump.regcontext.cdx;
        uint64_t rsi = regdump.regcontext.csi;
        uint64_t rdi = regdump.regcontext.cdi;
        uint64_t rsp = regdump.regcontext.csp;
        uint64_t rbp = regdump.regcontext.cbp;
        uint64_t rip = regdump.regcontext.cip;
        uint64_t rflags = regdump.regcontext.eflags;
        rflags &= ~0x100;  // очищаем флаг трассировки TF

        uint64_t r8 = regdump.regcontext.r8;
        uint64_t r9 = regdump.regcontext.r9;
        uint64_t r10 = regdump.regcontext.r10;
        uint64_t r11 = regdump.regcontext.r11;
        uint64_t r12 = regdump.regcontext.r12;
        uint64_t r13 = regdump.regcontext.r13;
        uint64_t r14 = regdump.regcontext.r14;
        uint64_t r15 = regdump.regcontext.r15;

        uc_reg_write(uc, UC_X86_REG_RAX, &rax);
        uc_reg_write(uc, UC_X86_REG_RBX, &rbx);
        uc_reg_write(uc, UC_X86_REG_RCX, &rcx);
        uc_reg_write(uc, UC_X86_REG_RDX, &rdx);
        uc_reg_write(uc, UC_X86_REG_RSI, &rsi);
        uc_reg_write(uc, UC_X86_REG_RDI, &rdi);
        uc_reg_write(uc, UC_X86_REG_RSP, &rsp);
        uc_reg_write(uc, UC_X86_REG_RBP, &rbp);
        uc_reg_write(uc, UC_X86_REG_RIP, &rip);
        uc_reg_write(uc, UC_X86_REG_RFLAGS, &rflags);
        uc_reg_write(uc, UC_X86_REG_R8, &r8);
        uc_reg_write(uc, UC_X86_REG_R9, &r9);
        uc_reg_write(uc, UC_X86_REG_R10, &r10);
        uc_reg_write(uc, UC_X86_REG_R11, &r11);
        uc_reg_write(uc, UC_X86_REG_R12, &r12);
        uc_reg_write(uc, UC_X86_REG_R13, &r13);
        uc_reg_write(uc, UC_X86_REG_R14, &r14);
        uc_reg_write(uc, UC_X86_REG_R15, &r15);

        // настройка хуков
        uc_hook trace_code, trace_mem;
        uc_hook_add(uc, &trace_code, UC_HOOK_CODE, hook_code, NULL, 1, 0);
        uc_hook_add(uc, &trace_mem, UC_HOOK_MEM_UNMAPPED, hook_mem_unmapped, NULL, 1, 0);

        // запуск эмуляции
        _plugin_logprintf("[Unicorn] запуск эмуляции, начальный RIP: 0x%llX\n", rip);
        g_stop_reason = 0;   // сброс причины остановки
        err = uc_emu_start(uc, rip, UINT64_MAX, 0, 0);

        if (err != UC_ERR_OK) {
            _plugin_logprintf("[Unicorn] эмуляция остановлена, причина: %s\n", uc_strerror(err));
        }
        else {
            _plugin_logprintf("[Unicorn] эмуляция успешно завершена.\n");
        }

        // получаем значение RIP на момент остановки
        uint64_t stop_rip = 0;
        uc_reg_read(uc, UC_X86_REG_RIP, &stop_rip);
        _plugin_logprintf("[Unicorn] RIP остановки: 0x%llX, причина остановки: %d\n", stop_rip, g_stop_reason);

        if (g_stop_reason == 1) {   // rdtsc
            g_rdtscNextRip = stop_rip + 2;  // stop_rip адрес rdtsc, но g_rdtscNextRip уже был сохранен в hook_code
            Script::Debug::SetHardwareBreakpoint(g_rdtscNextRip);
            g_bWaitingForBreakpoint = TRUE;
            // даем добро дебаггеру продолжить выполнение до брейкпоинта
            DbgCmdExec("go");
        }
        else if (g_stop_reason == 2) {   // syscall
            _plugin_logprintf("[Unicorn] обнаружен syscall, завершение эмуляции.\n");
            Script::Debug::SetHardwareBreakpoint(stop_rip);
            DbgCmdExec("go");
            // дальнейшая эмуляция прекращается, поток переходит в режим ожидания (пока юзер не запустит снова)
        }
        else if (g_stop_reason == 3) {   // вне диапазона модуля
            _plugin_logprintf("[Unicorn] выход за пределы основного модуля, завершение эмуляции.\n");
            api = TRUE;
            Script::Debug::SetHardwareBreakpoint(stop_rip);
            DbgCmdExec("go");
            // эмуляция завершена, возврат в режим ожидания
        }
        else {
            // другая неизвестная причина остановки (возможно ошибка uc_emu_start), тоже не продолжаем
            gsreg = TRUE;
            BASIC_INSTRUCTION_INFO info;
            DbgDisasmFastAt(stop_rip, &info);
            Script::Debug::SetHardwareBreakpoint(stop_rip + info.size);
            DbgCmdExec("go");

            _plugin_logprintf("[Unicorn] эмуляция остановлена по неизвестной причине.\n");
        }

        uc_close(uc);
    }
    return 0;
}

// callback
BOOL g_bDebugMenuActive = FALSE;  // флаг активного режима отладки (после нажатия меню DEBUG_ID)

extern "C" __declspec(dllexport) void CBBREAKPOINT(CBTYPE cbType, PLUG_CB_BREAKPOINT * info) {
    // обработка аппаратных точек останова в режиме отладки
    if (g_bDebugMenuActive && info->breakpoint->type == bp_hardware) {
        // если это точка останова, установленная на инструкцию после rdtsc
        if (info->breakpoint->addr == g_rdtscNextRip) {
            _plugin_logprintf("[Unicorn] Сработала точка останова после rdtsc: 0x%llX\n", info->breakpoint->addr);
            Script::Debug::DeleteHardwareBreakpoint(g_rdtscNextRip);
            SetEvent(g_hSimulateEvent);

            // дебаггер приостанавливается при срабатывании брейкпоинта,
            // рабочий поток заново получит регистры и продолжит эмуляцию.
            // не выполняйте здесь "go", иначе дебаггер пойдет дальше до готовности рабочего потока.
            return;
        }

        WORD op = 0;
        if (DbgMemRead(info->breakpoint->addr, &op, 2)) {
            if (op == 0x50f) {
                _plugin_logprintf("syscall: %llx\n", info->breakpoint->addr);
                Script::Debug::DeleteHardwareBreakpoint(info->breakpoint->addr);
                return;
            }
        }
    }

    if (gsreg && info->breakpoint->type == bp_hardware) {
        gsreg = FALSE;
        Script::Debug::DeleteHardwareBreakpoint(info->breakpoint->addr);
        SetEvent(g_hSimulateEvent);
        return;
    }

    if (api && info->breakpoint->type == bp_hardware) {
        api = FALSE;
        Script::Debug::DeleteHardwareBreakpoint(info->breakpoint->addr);
        return;
    }
}

// callback меню
extern "C" __declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY * info) {
    if (info->hEntry == MENU_UNICORN_ID) {
        // запуск эмуляции
        g_bDebugMenuActive = TRUE;
        g_bWaitingForBreakpoint = FALSE;     // сброс флага ожидания брейкпоинта
        SetEvent(g_hSimulateEvent);
        return;
    }
}

// вход плагина
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT * initStruct) {
    initStruct->pluginVersion = 1;
    initStruct->sdkVersion = PLUG_SDKVERSION;
    strcpy_s(initStruct->pluginName, "UnicornPlugin");
    pluginHandle = initStruct->pluginHandle;
    return true;
}

extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT * setupStruct) {
    hMenu = setupStruct->hMenu;
    _plugin_menuaddentry(hMenu, MENU_UNICORN_ID, "sys_trace");

    // создание события и постоянного рабочего потока
    g_hSimulateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    g_hSimulateThread = CreateThread(NULL, 0, SimulationWorker, NULL, 0, NULL);
    _plugin_logputs("[UnicornPlugin] plugsetup выполнен, рабочий поток запущен.");
}

extern "C" __declspec(dllexport) bool plugstop() {
    // остановка рабочего потока
    if (g_hSimulateEvent) {
        g_bStopSimulation = TRUE;
        SetEvent(g_hSimulateEvent);
        if (g_hSimulateThread) {
            WaitForSingleObject(g_hSimulateThread, INFINITE);
            CloseHandle(g_hSimulateThread);
            g_hSimulateThread = NULL;
        }
        CloseHandle(g_hSimulateEvent);
        g_hSimulateEvent = NULL;
    }
    _plugin_logputs("[UnicornPlugin] plugstop выполнен, рабочий поток остановлен.");
    return true;
}

// dllmain
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

всем спасибо кто прочитал:peperead:
 
Последнее редактирование:
Выходит, что это не гайд, а просто набор текста. По типу, нууу вот тут возвращаем вот так, а здесь вот так, нууууу потому что! Никакого внятного объяснения нет.
в этот момент rax равен 0x33, а строку \??\TitanHide можно найти через r8.
И на твоём скрине в отладчике нигде нет даже этой строчки. Если бы VMP проверял наличие титанхайда таким дурацким способом, то он бы не давал запуститься, когда драйвер не подцеплён к процессу. Также не очень понятно почему ты пишешь про peb->HeapFlags, если проверок на антиотладку оттуда у VMP сроду не было. Денис Колбович меня осадил. Такие проверки есть, но только на этапе лоадера, в API функции VMPIsDebuggerPresent, такого нет, вышло недопонимание. Также это касается и проверки через NtOpenFile и строки "\??\TitanHide".

проверки NtQueryInformationProcess
Почему C0000353 должно проканать ? А как же проверки добавленные в январском обновлении 25 года, где добавилась фишка с невыровненным адресом аргумента и антидебаг ожидает другого результата.

программа остановится. найдите следующую инструкцию jne. она может быть замаскирована через call, jne или jmp, но ваша задача найти верный переход. этот jne обязательно ведет к movzx esi, byte ptr ds:[rcx] или инструкциям над ней.
Ты же понимаешь, что у тебя после мутации будут совершенно разный код и регистры.... Да и VMP не аллоцирует никакие отдельные шеллкоды (если я правильно понял из того, что ты написал).

Я выделил тезисы, которые мне лично непонятны. Не собирался наезжать, новым статьям всегда рады, но нужно понимать, что новичок, который зайдёт сюда и будет это читать, не то что не поймёт, а у него может сложиться ложное впечатление, которое предстоит ещё развеять парочкой вечеров, просиженных за дебагером.
 
Последнее редактирование:
Выходит, что это не гайд, а просто набор текста. По типу, нууу вот тут возвращаем вот так, а здесь вот так, нууууу потому что! Никакого внятного объяснения нет.

И на твоём скрине в отладчике нигде нет даже этой строчки. Если бы VMP проверял наличие титанхайда таким дурацким способом, то он бы не давал запуститься, когда драйвер не подцеплён к процессу. Также не очень понятно почему ты пишешь про peb->HeapFlags, если проверок на антиотладку оттуда у VMP сроду не было.


Почему C0000353 должно проканать ? А как же проверки добавленные в январском обновлении 25 года, где добавилась фишка с невыровненным адресом аргумента и антидебаг ожидает другого результата.


Ты же понимаешь, что у тебя после мутации будут совершенно разный код и регистры.... Да и VMP не аллоцирует никакие отдельные шеллкоды (если я правильно понял из того, что ты написал).

Я выделил тезисы, которые мне лично непонятны. Не собирался наезжать, новым статьям всегда рады, но нужно понимать, что новичок, который зайдёт сюда и будет это читать, не то что не поймёт, а у него может сложиться ложное впечатление, которое предстоит ещё развеять парочкой вечеров, просиженных за дебагером.
написал в самом начале статьи что я писал это пьяным буквально. когда отойду от этого то исправлю нюансы про которые ты написал.
 
красавчик критику воспринимай принимай как ступеньку чтобы стать выше и умнее и допускать меньше ошибок даже если ее писали чтобы нагадить специально)
сам для себя много новых техник исходя ТОЛЬКО из этой статьи придумал
 
красавчик критику воспринимай принимай как ступеньку чтобы стать выше и умнее и допускать меньше ошибок даже если ее писали чтобы нагадить специально)
сам для себя много новых техник исходя ТОЛЬКО из этой статьи придумал
мхалайдер не хейтер он крутой мужик добрый и люблю его и тебя и даж хейтеров люблю я же парень без негатива дружелюбный и тд
 
мхалайдер не хейтер он крутой мужик добрый и люблю его и тебя и даж хейтеров люблю я же парень без негатива дружелюбный и тд
да там конструктивная критика была.
просто еще не пришли гении ревёрс инжиниринга экей типы которые ковыряют вмп с рождения)
 
И на твоём скрине в отладчике нигде нет даже этой строчки. Если бы VMP проверял наличие титанхайда таким дурацким способом, то он бы не давал запуститься, когда драйвер не подцеплён к процессу. Также не очень понятно почему ты пишешь про peb->HeapFlags, если проверок на антиотладку оттуда у VMP сроду не было. Денис Колбович меня осадил. Такие проверки есть, но только на этапе лоадера, в API функции VMPIsDebuggerPresent, такого нет, вышло недопонимание. Также это касается и проверки через NtOpenFile и строки "\??\TitanHide".
ток про хипфлаги не уверен, а чек на титанхайд по имени давно уже появился, так что зря ты на малого бочку катил.......
 
Выходит, что это не гайд, а просто набор текста. По типу, нууу вот тут возвращаем вот так, а здесь вот так, нууууу потому что! Никакого внятного объяснения нет.

И на твоём скрине в отладчике нигде нет даже этой строчки. Если бы VMP проверял наличие титанхайда таким дурацким способом, то он бы не давал запуститься, когда драйвер не подцеплён к процессу. Также не очень понятно почему ты пишешь про peb->HeapFlags, если проверок на антиотладку оттуда у VMP сроду не было. Денис Колбович меня осадил. Такие проверки есть, но только на этапе лоадера, в API функции VMPIsDebuggerPresent, такого нет, вышло недопонимание. Также это касается и проверки через NtOpenFile и строки "\??\TitanHide".


Почему C0000353 должно проканать ? А как же проверки добавленные в январском обновлении 25 года, где добавилась фишка с невыровненным адресом аргумента и антидебаг ожидает другого результата.


Ты же понимаешь, что у тебя после мутации будут совершенно разный код и регистры.... Да и VMP не аллоцирует никакие отдельные шеллкоды (если я правильно понял из того, что ты написал).

Я выделил тезисы, которые мне лично непонятны. Не собирался наезжать, новым статьям всегда рады, но нужно понимать, что новичок, который зайдёт сюда и будет это читать, не то что не поймёт, а у него может сложиться ложное впечатление, которое предстоит ещё развеять парочкой вечеров, просиженных за дебагером.
ток про хипфлаги не уверен, а чек на титанхайд по имени давно уже появился, так что зря ты на малого бочку катил.......
спасибо, подправил пост
 
приношу извинения, если пост не читабелен или где то ошибки я немного пьян, но, вроде, это полезно. :da: :da:


недавно я исследовал методы антидебага в версиях VMP 3.8 и 3.9. несмотря на небольшие различия, в целом они идентичны, и методы их обхода остаются неизменными. ниже приведен общий обзор процесса на примере версии 3.9.

Код:
Expand Collapse Copy
 3.9 логика антидебага

  peb

 NtSetInformationProcess: предотвращение syscall хукинга

 NtOpenFile: обнаружение драйвера ??\TitanHide, обход через C0000034

 NtQueryInformationProcess: запрос DebugPort, установка 0 для обхода

 NtQueryInformationProcess: запрос DebugObjectHandle, rax установлен в C0000353 для обхода

 NtCreateDebugObject--->ZwQueryObject--->NtClose??? я забыл сори

 ZwSetInformationThread: hidefromdebugger, rdx установлен в 0 для обхода


 Проверка целостности


 NtQueryInformationProcess: запрос DebugPort, установка 0 для обхода 19

 ZwSetInformationThread: hidefromdebugger, rdx установлен в 0 для обхода D

 NtClose: закрытие невалидного хэндла, rax установлен в C0000008, прямой ret для обхода

 триггер исключения одиночного шага, обнаружение аппаратных бряков
Посмотреть вложение 333967
в старших версиях VMP используется инструкция rdtsc для выполнения системных вызовов по случайным путям. для симуляции выполнения я использовал unicorn. после обнаружения инструкции rdtsc я устанавливал брейкпоинт на следующей инструкции, запускал отладчик до этого момента, а затем продолжал симуляцию до достижения системного вызова или апи функции, после чего проверял логи.

как можно заметить, симуляция останавливается при встрече с системным вызовом. в этот момент отладчик доходит до точки останова, и через функцию callback аппаратная точка останова удаляется. это позволяет перехватывать системный вызов. таков общий алгоритм. перейдем к анализу самих методов защиты.
Посмотреть вложение 333968
для начала рассмотрим простую проверку PEB. обойдите debug flag и heap flag (это уже думаю знают все как кделать) - лично я на этом этапе включил бы scyllahide

еще рекомендую устанавливать брейкпоинт на ntclose (избегайте брейкпоинтов в заголовках или использования аппаратных брейкпоинтов). скрытие дебаггера вместе с брейкпоинтом на ntclose предотвращает прерывания от определенных апи вызовов, которые не влияют на работу дебаггера. если сразу после установки брейкпоинта на ntclose появляется всплывающее окно, следует проследить откуда оно.

анализ (NtSetInformationProcess и NtOpenFile)

в системном вызове регистр rax содержит номер функции. в данном случае он соответствует NtSetInformationProcess, а rdx равен 0x28. согласно документации, эта настройка используется для предотвращения трассировки системных вызовов. если установлен callback, то возврат из ring 3 после вызова произойдет не на следующую инструкцию, а на определенную вами функцию. рекомендую протестировать это самостоятельно.

далее вызывается NtOpenFile для обнаружения драйвера ??\TitanHide. если в системе его нет — беспокоиться не о чем. если есть, можно попробовать вернуть код C0000034 для обхода проверки.
Посмотреть вложение 333980
в этот момент rax равен 0x33, а строку \??\TitanHide можно найти через r8.

данные проверки (NtOpenFile, PEB) чаще встречаются на этапе инициализации лоадера. В самом виртуализированном коде основной упор идет на syscall-ы.

проверки NtQueryInformationProcess

RtlFreeHeap и самопроверка

после этого вызываются некоторые апи и ставятся точки останова на RtlFreeHeap. симуляция здесь не всегда обязательна (зависит от конкретной программы), но если при установке брейкпоинта на RtlFreeHeap обнаруживается вмешательство используйте симуляцию снова.

ZwSetInformationThread (HideFromDebugger)

Посмотреть вложение 333974

сначала установите rdx в 0. далее защищенный участок кода выполняет самопроверку. поскольку этот этап длительный, лучше его не симулировать. вместо этого установите постоянный брейкпоинт на первый байт секции с защищенным участком кода.
Посмотреть вложение 333975
программа остановится. найдите следующую инструкцию jne. она может быть замаскирована через call, jne или jmp, но ваша задача найти верный переход. этот jne обязательно ведет к movzx esi, byte ptr ds:[rcx] или инструкциям над ней.

важно понимать, что из-за мутации VMP код будет отличаться от билда к билду. Мой пример с jne это лишь один из вариантов логического пути, который нужно искать по поведению.

установите аппаратный брейкпоинт на инструкцию, следующую за jne (на схеме это mov eax, 0x88BAC81D). программа остановится здесь несколько раз. чтобы понять точное количество, нажимайте F9, пока не появится окно отладчика. запомните это число и перезапустите процесс. в моем случае хватило 5 остановок перед продолжением симуляции.

этап проверки​

в конце процесс проверяет четыре ключевых момента:

  1. запрос DebugPort через NtQueryInformationProcess.
  2. установка ZwSetInformationThread (HideFromDebugger) в 0.
  3. установка rdx в 0 для обхода NtClose.
  4. закрытие невалидного дескриптора (установите rax в C0000008).(в версии 3.8 для CloseHandle требуется rax = 0). это инициирует прерывание для обнаружения пошаговой отладки.
после завершения выполнения ZwSetInformationThread:

установите rax в C0000008 и измените rip на ret. затем удалите все аппаратные точки останова, нажмите F9, и программа успешно запустится.



код плагина:
plugin bypa$$ vmp 3.9:
Expand Collapse Copy
#include "pch.h"
#include <windows.h>
#include <vector>
#include <string>
#include "pluginsdk/bridgemain.h"
#include "pluginsdk/_plugins.h"
#include "pluginsdk/_scriptapi_module.h"
#include "pluginsdk/_scriptapi_memory.h"
#include "pluginsdk/_scriptapi_debug.h"
#include "unicorn/unicorn.h"

#pragma comment(lib, "unicorn.lib")
#pragma comment(lib, "x64dbg.lib")
#pragma comment(lib, "x64bridge.lib")

#define MENU_UNICORN_ID 1

int pluginHandle;
duint mainModuleBase = 0;
duint mainModuleSize = 0;
int hMenu;


HANDLE g_hSimulateEvent = NULL;       // для начала рабочего потока
HANDLE g_hSimulateThread = NULL;       // хендл рабочего потока
BOOL   g_bStopSimulation = FALSE;      // флаг для остановки рабочего потока
BOOL   g_bWaitingForBreakpoint = FALSE;// флаг ожидания аппаратной точки останова (rdtsc)
duint  g_rdtscNextRip = 0;             // адрес следующей инструкции после rdtsc
int    g_stop_reason = 0;              // причина остановки: 1=rdtsc, 2=syscall, 3=вне_модуля
BOOL   gsreg = FALSE;
BOOL   api = FALSE;

static void hook_code(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {

    WORD code = 0;
    uc_mem_read(uc, address, &code, 2);
    if (code == 0x310f) {  // инструкция rdtsc
        _plugin_logprintf("rdtsc по адресу: %llx\n", address);
        g_stop_reason = 1;
        g_rdtscNextRip = address + size;  // адрес следующей инструкции
        uc_emu_stop(uc);
        return;
    }
    if (code == 0x50f) {   // инструкция syscall
        _plugin_logprintf("syscall по адресу: %llx\n", address);
        g_stop_reason = 2;
        uc_emu_stop(uc);
        return;
    }
    // если адрес инструкции вне диапазона основного модуля, останавливаем эмуляцию
    if (address < mainModuleBase || address >= (mainModuleBase + mainModuleSize)) {
        _plugin_logprintf("[Unicorn] остановка эмуляции, возврат управления в x64dbg. Адрес API: %llx\n", address);
        g_stop_reason = 3;
        uc_emu_stop(uc);
    }
}

static bool hook_mem_unmapped(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data) {
    uint64_t current_rip;
    uc_reg_read(uc, UC_X86_REG_RIP, &current_rip);
    _plugin_logprintf("[Unicorn] MEM_UNMAPPED на RIP=0x%llX: тип=%d, адрес=0x%llX, размер=%d\n",
        current_rip, type, address, size);

    duint pageBase = (duint)address & ~0xFFF;
    if (!Script::Memory::IsValidPtr(pageBase)) {
        _plugin_logprintf("[Unicorn] ошибка: попытка доступа по невалидному указателю 0x%llX\n", pageBase);
        uc_emu_stop(uc);
        return false;
    }

    _plugin_logprintf("[Unicorn] сработал Page fault, мапирование страницы памяти: 0x%llX\n", pageBase);

    uc_err err = uc_mem_map(uc, pageBase, 0x1000, UC_PROT_ALL);
    if (err != UC_ERR_OK && err != UC_ERR_MAP) {
        _plugin_logprintf("[Unicorn] ошибка мапирования памяти: %s\n", uc_strerror(err));
        return false;
    }

    unsigned char pageBuf[0x1000] = { 0 };
    if (Script::Memory::Read(pageBase, pageBuf, 0x1000, nullptr)) {
        uc_mem_write(uc, pageBase, pageBuf, 0x1000);
        return true;
    }
    return false;
}

// эмуляция
DWORD WINAPI SimulationWorker(LPVOID lpParam) {
    while (!g_bStopSimulation) {
        // ожидание срабатывания события
        WaitForSingleObject(g_hSimulateEvent, INFINITE);
        ResetEvent(g_hSimulateEvent);

        // если поток проснулся из за точки останова, удаляем старую аппаратную точку останова
        if (g_bWaitingForBreakpoint) {
            Script::Debug::DeleteHardwareBreakpoint(g_rdtscNextRip);
            g_bWaitingForBreakpoint = FALSE;
        }

        // повторная инициализация движка Unicorn
        uc_engine* uc;
        uc_err err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc);
        if (err != UC_ERR_OK) {
            _plugin_logprintf("uc_open неудачно: %u\n", err);
            continue;
        }

        // получаем диапазон основного модуля (каждый раз заново, т.к. отладчик мог загрузить новые модули)
        mainModuleBase = Script::Module::GetMainModuleBase();
        mainModuleSize = Script::Module::SizeFromAddr(mainModuleBase);

        // мапируем все выделенные страницы памяти в unicorn
        MEMMAP memoryMap = { 0 };
        DbgMemMap(&memoryMap);
        for (int i = 0; i < memoryMap.count; i++) {
            auto& page = memoryMap.page[i];
            if (page.mbi.State == MEM_COMMIT) {
                duint base = (duint)page.mbi.BaseAddress;
                SIZE_T size = page.mbi.RegionSize;
                duint alignedBase = base & ~0xFFF;
                duint alignedSize = (size + 0xFFF) & ~0xFFF;
                uc_mem_map(uc, alignedBase, alignedSize, UC_PROT_ALL);
                std::vector<uint8_t> buffer(size);
                if (Script::Memory::Read(base, buffer.data(), size, nullptr)) {
                    uc_mem_write(uc, base, buffer.data(), size);
                }
            }
        }

        // синхронизация состояния регистров (64 бит)
        REGDUMP_AVX512 regdump = { 0 };
        if (!DbgGetRegDumpEx(&regdump, sizeof(regdump))) {
            _plugin_logprintf("[Unicorn] не удалось получить регистры, эмуляция пропущена\n");
            uc_close(uc);
            continue;
        }

        uint64_t rax = regdump.regcontext.cax;
        uint64_t rbx = regdump.regcontext.cbx;
        uint64_t rcx = regdump.regcontext.ccx;
        uint64_t rdx = regdump.regcontext.cdx;
        uint64_t rsi = regdump.regcontext.csi;
        uint64_t rdi = regdump.regcontext.cdi;
        uint64_t rsp = regdump.regcontext.csp;
        uint64_t rbp = regdump.regcontext.cbp;
        uint64_t rip = regdump.regcontext.cip;
        uint64_t rflags = regdump.regcontext.eflags;
        rflags &= ~0x100;  // очищаем флаг трассировки TF

        uint64_t r8 = regdump.regcontext.r8;
        uint64_t r9 = regdump.regcontext.r9;
        uint64_t r10 = regdump.regcontext.r10;
        uint64_t r11 = regdump.regcontext.r11;
        uint64_t r12 = regdump.regcontext.r12;
        uint64_t r13 = regdump.regcontext.r13;
        uint64_t r14 = regdump.regcontext.r14;
        uint64_t r15 = regdump.regcontext.r15;

        uc_reg_write(uc, UC_X86_REG_RAX, &rax);
        uc_reg_write(uc, UC_X86_REG_RBX, &rbx);
        uc_reg_write(uc, UC_X86_REG_RCX, &rcx);
        uc_reg_write(uc, UC_X86_REG_RDX, &rdx);
        uc_reg_write(uc, UC_X86_REG_RSI, &rsi);
        uc_reg_write(uc, UC_X86_REG_RDI, &rdi);
        uc_reg_write(uc, UC_X86_REG_RSP, &rsp);
        uc_reg_write(uc, UC_X86_REG_RBP, &rbp);
        uc_reg_write(uc, UC_X86_REG_RIP, &rip);
        uc_reg_write(uc, UC_X86_REG_RFLAGS, &rflags);
        uc_reg_write(uc, UC_X86_REG_R8, &r8);
        uc_reg_write(uc, UC_X86_REG_R9, &r9);
        uc_reg_write(uc, UC_X86_REG_R10, &r10);
        uc_reg_write(uc, UC_X86_REG_R11, &r11);
        uc_reg_write(uc, UC_X86_REG_R12, &r12);
        uc_reg_write(uc, UC_X86_REG_R13, &r13);
        uc_reg_write(uc, UC_X86_REG_R14, &r14);
        uc_reg_write(uc, UC_X86_REG_R15, &r15);

        // настройка хуков
        uc_hook trace_code, trace_mem;
        uc_hook_add(uc, &trace_code, UC_HOOK_CODE, hook_code, NULL, 1, 0);
        uc_hook_add(uc, &trace_mem, UC_HOOK_MEM_UNMAPPED, hook_mem_unmapped, NULL, 1, 0);

        // запуск эмуляции
        _plugin_logprintf("[Unicorn] запуск эмуляции, начальный RIP: 0x%llX\n", rip);
        g_stop_reason = 0;   // сброс причины остановки
        err = uc_emu_start(uc, rip, UINT64_MAX, 0, 0);

        if (err != UC_ERR_OK) {
            _plugin_logprintf("[Unicorn] эмуляция остановлена, причина: %s\n", uc_strerror(err));
        }
        else {
            _plugin_logprintf("[Unicorn] эмуляция успешно завершена.\n");
        }

        // получаем значение RIP на момент остановки
        uint64_t stop_rip = 0;
        uc_reg_read(uc, UC_X86_REG_RIP, &stop_rip);
        _plugin_logprintf("[Unicorn] RIP остановки: 0x%llX, причина остановки: %d\n", stop_rip, g_stop_reason);

        if (g_stop_reason == 1) {   // rdtsc
            g_rdtscNextRip = stop_rip + 2;  // stop_rip адрес rdtsc, но g_rdtscNextRip уже был сохранен в hook_code
            Script::Debug::SetHardwareBreakpoint(g_rdtscNextRip);
            g_bWaitingForBreakpoint = TRUE;
            // даем добро дебаггеру продолжить выполнение до брейкпоинта
            DbgCmdExec("go");
        }
        else if (g_stop_reason == 2) {   // syscall
            _plugin_logprintf("[Unicorn] обнаружен syscall, завершение эмуляции.\n");
            Script::Debug::SetHardwareBreakpoint(stop_rip);
            DbgCmdExec("go");
            // дальнейшая эмуляция прекращается, поток переходит в режим ожидания (пока юзер не запустит снова)
        }
        else if (g_stop_reason == 3) {   // вне диапазона модуля
            _plugin_logprintf("[Unicorn] выход за пределы основного модуля, завершение эмуляции.\n");
            api = TRUE;
            Script::Debug::SetHardwareBreakpoint(stop_rip);
            DbgCmdExec("go");
            // эмуляция завершена, возврат в режим ожидания
        }
        else {
            // другая неизвестная причина остановки (возможно ошибка uc_emu_start), тоже не продолжаем
            gsreg = TRUE;
            BASIC_INSTRUCTION_INFO info;
            DbgDisasmFastAt(stop_rip, &info);
            Script::Debug::SetHardwareBreakpoint(stop_rip + info.size);
            DbgCmdExec("go");

            _plugin_logprintf("[Unicorn] эмуляция остановлена по неизвестной причине.\n");
        }

        uc_close(uc);
    }
    return 0;
}

// callback
BOOL g_bDebugMenuActive = FALSE;  // флаг активного режима отладки (после нажатия меню DEBUG_ID)

extern "C" __declspec(dllexport) void CBBREAKPOINT(CBTYPE cbType, PLUG_CB_BREAKPOINT * info) {
    // обработка аппаратных точек останова в режиме отладки
    if (g_bDebugMenuActive && info->breakpoint->type == bp_hardware) {
        // если это точка останова, установленная на инструкцию после rdtsc
        if (info->breakpoint->addr == g_rdtscNextRip) {
            _plugin_logprintf("[Unicorn] Сработала точка останова после rdtsc: 0x%llX\n", info->breakpoint->addr);
            Script::Debug::DeleteHardwareBreakpoint(g_rdtscNextRip);
            SetEvent(g_hSimulateEvent);

            // дебаггер приостанавливается при срабатывании брейкпоинта,
            // рабочий поток заново получит регистры и продолжит эмуляцию.
            // не выполняйте здесь "go", иначе дебаггер пойдет дальше до готовности рабочего потока.
            return;
        }

        WORD op = 0;
        if (DbgMemRead(info->breakpoint->addr, &op, 2)) {
            if (op == 0x50f) {
                _plugin_logprintf("syscall: %llx\n", info->breakpoint->addr);
                Script::Debug::DeleteHardwareBreakpoint(info->breakpoint->addr);
                return;
            }
        }
    }

    if (gsreg && info->breakpoint->type == bp_hardware) {
        gsreg = FALSE;
        Script::Debug::DeleteHardwareBreakpoint(info->breakpoint->addr);
        SetEvent(g_hSimulateEvent);
        return;
    }

    if (api && info->breakpoint->type == bp_hardware) {
        api = FALSE;
        Script::Debug::DeleteHardwareBreakpoint(info->breakpoint->addr);
        return;
    }
}

// callback меню
extern "C" __declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY * info) {
    if (info->hEntry == MENU_UNICORN_ID) {
        // запуск эмуляции
        g_bDebugMenuActive = TRUE;
        g_bWaitingForBreakpoint = FALSE;     // сброс флага ожидания брейкпоинта
        SetEvent(g_hSimulateEvent);
        return;
    }
}

// вход плагина
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT * initStruct) {
    initStruct->pluginVersion = 1;
    initStruct->sdkVersion = PLUG_SDKVERSION;
    strcpy_s(initStruct->pluginName, "UnicornPlugin");
    pluginHandle = initStruct->pluginHandle;
    return true;
}

extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT * setupStruct) {
    hMenu = setupStruct->hMenu;
    _plugin_menuaddentry(hMenu, MENU_UNICORN_ID, "sys_trace");

    // создание события и постоянного рабочего потока
    g_hSimulateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    g_hSimulateThread = CreateThread(NULL, 0, SimulationWorker, NULL, 0, NULL);
    _plugin_logputs("[UnicornPlugin] plugsetup выполнен, рабочий поток запущен.");
}

extern "C" __declspec(dllexport) bool plugstop() {
    // остановка рабочего потока
    if (g_hSimulateEvent) {
        g_bStopSimulation = TRUE;
        SetEvent(g_hSimulateEvent);
        if (g_hSimulateThread) {
            WaitForSingleObject(g_hSimulateThread, INFINITE);
            CloseHandle(g_hSimulateThread);
            g_hSimulateThread = NULL;
        }
        CloseHandle(g_hSimulateEvent);
        g_hSimulateEvent = NULL;
    }
    _plugin_logputs("[UnicornPlugin] plugstop выполнен, рабочий поток остановлен.");
    return true;
}

// dllmain
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

всем спасибо кто прочитал:peperead:
А разве ScyllaHide не прокатит?
 
Назад
Сверху Снизу