Гайд UM хуки и их проблемы

ANTICHEAT_OBFUSCATE_CODEMARKER
Пользователь
Пользователь
Статус
Оффлайн
Регистрация
2 Июл 2020
Сообщения
139
Реакции
283
СОДЕРЖАНИЕ:


Введение
1. Import/Export hook
2. BP hook
3. HWBP hook
4. PG hook
5. Trampoline hook
6. Excepthion callback
Заключение


ВВЕДЕНЕИЕ
⠀⠀
⠀⠀⠀⠀⠀Сегодня мы затронем довольно ежедневную работу для реверсера – хуки и их виды. Здесь будут рассмотрены основные хуки в UM,поговорим об плюсах и недостатках, да и слегка поразмышляем над использованием. Если говорить прямо, то основная часть хуков устроена на обработке исключений, подмена указателей/структуры данных, копирование байтов и патчинге.
⠀⠀⠀⠀⠀Хотя на первый взгляд многое кажется относительно простым и понятным, предлагаю вернуться к основам и проверить, так ли это на самом деле. В той же обработке исключений можно довольно неплохо "пошаманить", но об этом поговорим в другой раз ;).
⠀⠀⠀⠀⠀Целью статьи является написание небольшой библиотеки для хуков, рассмотрение недостатков и ограничений, а также способы обнаружений. Предлагаю не тянуть кота за фантастическое место и сразу начать. Для копирования и фикса некоторых инструкций вам потребуется дизассемблер и ассемблер(в статье используется
Пожалуйста, авторизуйтесь для просмотра ссылки.
&
Пожалуйста, авторизуйтесь для просмотра ссылки.
). Рассматривается больше реализация для архитектуры x64 Windows 7-11.
⠀⠀⠀⠀⠀Автор не претендует на замену существующих библиотек т.к этот код является больше учебным(хотя его можно спокойно применять) и попыткой передать небольшой опыт :).



Подмена указателя/данных в импорте/экспорте

⠀⠀⠀⠀⠀Я предлагаю начать с самого простого, поскольку простая обработка исключений представляется довольно легко (тот же VEH). Однако, проблема связанные с копированием инструкции(rip/jcc) и правильное ассемблирования действительно ставят другую планку (мы поговорим об этом в следующей статье) при копирование инструкций в другую исполняемую память.
⠀⠀⠀⠀⠀Самый простое в реализации - подмена указателя в импорте(IMAGE_DIRECTORY_ENTRY_IMPORT),поскольку это просто изменение данных, которые инициализируются при загрузке PE. Однако, это бесполезно против динамического получения импорта. Нам потребуется сохранить оригинальный интересующийся для нас адрес и сама реализация довольно тривиальна:
C++:
Expand Collapse Copy
auto imp_swap(PVOID mod_addr, CHAR* name_dll, CHAR* name_api, PVOID point) -> BOOLEAN
{
    BOOLEAN is_imp_change = FALSE;
    DWORD old_prot = NULL;
    CHAR* name_imp_dll = NULL;
    uint64_t* orig_first_thunk = NULL;
    uint64_t* first_thunk = NULL;
    PIMAGE_NT_HEADERS headers = NULL;
    PIMAGE_SECTION_HEADER sections = NULL;
    PIMAGE_IMPORT_BY_NAME import_name = NULL;
    PIMAGE_IMPORT_DESCRIPTOR imp_descript = NULL;
   
    headers = reinterpret_cast<PIMAGE_NT_HEADERS>(static_cast<CHAR*>(mod_addr) + static_cast<PIMAGE_DOS_HEADER>(mod_addr)->e_lfanew);
    sections = IMAGE_FIRST_SECTION(headers);

    if (headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)
    {
        imp_descript = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(reinterpret_cast<CHAR*>(name_dll) + headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
        for (; imp_descript->Name; ++imp_descript)
        {
            name_imp_dll = reinterpret_cast<CHAR*>(mod_addr) + imp_descript->Name;
           
            if (!stricmp(name_imp_dll, name_dll))
            {
                orig_first_thunk = reinterpret_cast<uint64_t*>(reinterpret_cast<CHAR*>(mod_addr) + imp_descript->OriginalFirstThunk);
                first_thunk = reinterpret_cast<uint64_t*>(reinterpret_cast<CHAR*>(mod_addr) + imp_descript->FirstThunk);

                if (!orig_first_thunk) //load by index https://stackoverflow.com/questions/42413937/why-pe-need-original-first-thunkoft
                {
                    return is_imp_change;
                }
                for (; *orig_first_thunk; orig_first_thunk++, first_thunk++)
                {
                    import_name = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(reinterpret_cast<CHAR*>(mod_addr) + *orig_first_thunk);
                    if (import_name->Name && !stricmp(import_name->Name, name_api))
                    {
                        if (VirtualProtect(first_thunk,PAGE_SIZE, PAGE_EXECUTE_READWRITE,&old_prot))
                        {
                            *first_thunk = reinterpret_cast<uint64_t>(point);
                            VirtualProtect(first_thunk, PAGE_SIZE, old_prot, &old_prot);

                            is_imp_change = TRUE;
                        }
                    }

                }
            }
           
        }
    }
    return is_imp_change;
}
⠀⠀⠀⠀⠀Для противодействия динамическому получению, можно сломать весь/определённый export в бинарнике, однако, при неправильном использовании можно получить spinlock. Достаточно пройтись по IMAGE_DIRECTORY_ENTRY_EXPORT интересующегося для нас модуля и тут встаёт несколько видов реализации:
1) исключение на основе изменения rva на секцию с правами только на чтение/запись
2) изменение rva на байт, который вызывает исключение(например, 0xCC)
3) изменение rva на рядом расположенный адрес с батутом или получении дельты, чтобы сделать прыжок на расположенный рядом модуль.

Нам потребуется сохранить первоначальный rva,чтобы сохранить первоначальный rip.
Пример реализации с изменением экспорта модуля(кроме некоторых),чтобы rva указывал на инструкцию, вызывающую исключение:
Код:
Expand Collapse Copy
auto exp_break(PVOID mod_addr) -> BOOLEAN
{

    BOOLEAN is_success = FALSE;
    DWORD old_prot = NULL;
    uint32_t code_cave = NULL;
    CHAR* name_exp = NULL;
    uint8_t* memory_sec = NULL;
    PIMAGE_NT_HEADERS headers = NULL;
    PIMAGE_SECTION_HEADER sections  = NULL;
    PIMAGE_EXPORT_DIRECTORY export_info = NULL;
    POINTER_BAD cur_point_bad = { NULL };


    if (static_cast<PIMAGE_DOS_HEADER>(mod_addr)->e_magic == IMAGE_DOS_SIGNATURE)
    {

        headers = reinterpret_cast<PIMAGE_NT_HEADERS>(static_cast<CHAR*>(mod_addr) + static_cast<PIMAGE_DOS_HEADER>(mod_addr)->e_lfanew);
        sections = IMAGE_FIRST_SECTION(headers);

        if (headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
        {
            export_info = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(reinterpret_cast<CHAR*>(mod_addr) +   headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
            if (sizeof(uint8_t) > export_info->NumberOfFunctions)
            {
                return is_success;
            }

            auto names = (PDWORD)(reinterpret_cast<CHAR*>(mod_addr) +  export_info->AddressOfNames);
            auto ordinals = (PWORD)(reinterpret_cast<CHAR*>(mod_addr) +  export_info->AddressOfNameOrdinals);
            auto functions = (PDWORD)(reinterpret_cast<CHAR*>(mod_addr) +  export_info->AddressOfFunctions);


            for (uint32_t i = NULL; i < export_info->NumberOfFunctions; ++i)
            {
                if (!code_cave)
                    code_cave = get_rva_code_cave_execute(mod_addr, headers, sections);

                if (!code_cave)
                {
                    printf("bad ->\t%s\n", reinterpret_cast<CHAR*>(mod_addr) + names[i]);
                }

               

                if (!is_bad_imp(reinterpret_cast<CHAR*>(mod_addr) + names[i]) &&& code_cave && VirtualProtect(&functions[ordinals[i]], PAGE_SIZE, PAGE_READWRITE, &old_prot))
                {
                    //push addr excep(mod+i)
                    //copy correct address
                    //after change only set fake;
                    cur_point_bad.bad_pointer = reinterpret_cast<CHAR*>(mod_addr) + code_cave;
                    cur_point_bad.correct_pointer = reinterpret_cast<CHAR*>(mod_addr) + functions[ordinals[i]];
                    cur_point_bad.execute_only = TRUE;
                    cur_point_bad.exp_name = reinterpret_cast<CHAR*>(mod_addr) + names[i];
                    cur_point_bad.correct_rva = functions[ordinals[i]];

                    //for remove if need
                    cur_point_bad.swap_pointer = &functions[ordinals[i]];
                    breaked_pointer.push_back(cur_point_bad);
                   
                    functions[ordinals[i]] = code_cave;

                    is_success = TRUE;
                    VirtualProtect(&functions[ordinals[i]], PAGE_SIZE, old_prot, &old_prot);

                    code_cave = NULL;
                }


            }                  
        }

    }
    return is_success;
}
Плюсы:
Возможность легко отслеживать импорт.
Крайне дешёвый хук т.к потребуется изменить для фикса только RIP/EIP потока.

Минусы
Требуется обработчик исключений для экспорта(1/2 вариант реализации)
Относительно легко обнаружить при сравнении с файлом на диске или специфические проверки(функция начинается с 0xCC, export rva находится не в модуле)
Сильно желательно хук на ранней инициализации.
Ничего более, кроме отслеживания импорта



BP hook

⠀⠀⠀⠀⠀Не совсем правильное название, но основная идея в использовании крайне дешёвой по размеру инструкции (обычно 1-2 байта) для вызова исключения. Обычно такой хук используют дебаггеры( тот же x64dbg предоставляет int3, long int3, ud2). Вот лишь пример из некоторых инструкций в 1 байт: hlt, int1(секси icebp), int3.При реализации есть 2 варианта обработки инструкции: сохранить украденные байты до хука и временно восстанавливать с использованием TRAP_FLAG(для восстановления хука и правильной обработки при следующей вызове) или копирование инструкции в новый адрес с прыжком на следующую инструкцию, чтобы не пришлось временно восстанавливать байты. Это, наверное, самая простая реализация, если не брать копирование байтов(об этом в конце):
Код:
Expand Collapse Copy
NO_INLINE auto add_bp(PVOID addr, BP_TYPE_INFO bp_type) -> BOOLEAN
{
    uint32_t copy_size = NULL;
    BP_INFO bp = { NULL };
   
    bp.addr_bp = addr;


    if (bp_type == bp_icebp || bp_type == bp_hlt || bp_type == bp_int3)
    {
        copy_size = 1;
    }
    else
    {
        copy_size = 2;
    }

    //not entirely sure, but this is just an example
    bp.addr_single_step = reinterpret_cast<uint8_t*>(addr) + sizeof(uint8_t);
    bp.type_info = bp_type;
    memcpy(bp.orig_byte, addr,copy_size );
    bp.rip_fixer = cg_util::get_rip_fixer(addr, copy_size);
     
    if (bp.rip_fixer)
    {
        return set_patch(addr, bp_type, &bp);
    }
    return FALSE;
}
Плюсы:
Крайне дешёвый хук (от 1 байта)
Минусы:
Требуется обработчик исключений
Патчинг инструкции в памяти, что позволяет её обнаружить(странное начало функции, сравнение с файлом на диске).



HWBP

⠀⠀⠀⠀⠀Довольно приятно изменить только 2 значения(Dr(0-3) & Dr7) в контексте потока, чтобы получить управление над потоком и не патчить ни 1 инструкцию, но нужно опять регистрировать фильтр для обработки исключений… Данный хук приятно использовать для игнорирования тех же проверок целостности, но мы ограничены только 4 аппаратными точками остановками.

Принцип работы хорошо документирован в Intel manual и AMD64 manual:

Intel:
UqUfRHM.png

PFKlP1A.png


AMD:
b7tsD9V.png

TFKQokd.png


⠀⠀⠀⠀⠀Однако, такой тип хука наиболее удобно использовать на уже существующих потоках, иначе вам потребуется хукнуть,например,RtlUserThreadStart иди другую функция связанная с созданием потоков. Нам нужно установить нужный бит в Dr7 для установки правильных параметров: номер Dr(0-3), длина(длина должна быть выровнена, если размер устанавливается не на 1 байт) и тип обращения( выполнение, запись, чтение/запись) + сам Dr(0-3). Реализацию можно сделать менее грамоткой, но вот пример:
C++:
Expand Collapse Copy
O_INLINE auto set_hwbp(PVOID addr, HWBP_TYPE_ACCESS hwbp_access, HWBP_LEN_ACCESS hwbp_len, uint32_t thread_id = NULL, BOOLEAN set_all_thread = FALSE) -> BOOLEAN
{
    BOOLEAN is_success = FALSE;
    ULONG ret_lenght = NULL;
    NTSTATUS nt_status = STATUS_UNSUCCESSFUL;
    PVOID nt_get_context = NULL;
    PVOID nt_set_context = NULL;

    PVOID nt_query_sys = NULL;

    PVOID buffer = NULL;
    HANDLE acces = NULL;
    PSYSTEM_PROCESS_INFORMATION process_info = NULL;

    HMODULE ntdll_base = NULL;
    CONTEXT ctx = { NULL };
    HWBP_INFO cur_hwbp;

    ntdll_base = GetModuleHandleW(L"ntdll.dll");

    if (ntdll_base)
    {
        nt_get_context = GetProcAddress(ntdll_base, "NtGetContextThread");
        nt_set_context = GetProcAddress(ntdll_base, "NtSetContextThread");

        nt_query_sys = GetProcAddress(ntdll_base, "NtQuerySystemInformation");

        if (nt_get_context && nt_set_context && nt_query_sys)
        {
            if (set_all_thread)
            {

                nt_status = reinterpret_cast<decltype(&NtQuerySystemInformation)>(nt_query_sys)(SystemProcessInformation, &ret_lenght, ret_lenght, &ret_lenght);
                while (nt_status == STATUS_INFO_LENGTH_MISMATCH)
                {
                    if (buffer != NULL)
                        free(buffer);

                    buffer = malloc(ret_lenght);
                    nt_status = reinterpret_cast<decltype(&NtQuerySystemInformation)>(nt_query_sys)(SystemProcessInformation, buffer, ret_lenght, &ret_lenght);
                }

                if (!NT_SUCCESS(nt_status))
                {
                    if (buffer != NULL)
                        free(buffer);
                    return FALSE;
                }
                process_info = reinterpret_cast<PSYSTEM_PROCESS_INFORMATION>(buffer);
                while (process_info->NextEntryOffset) // Loop over the list until we reach the last entry.
                {
                    if (reinterpret_cast<uint32_t>(process_info->UniqueProcessId) == GetCurrentProcessId())
                    {
                        is_success = TRUE;

                        if (hwbp_access == hwbp_execute)
                        {
                            cur_hwbp.rip_fixer = cg_util::get_rip_fixer(addr, MIN_LENGHT_INSTR);
                        }
                        else
                        {
                            cur_hwbp.rip_fixer = NULL;
                        }

                        cur_hwbp.access = hwbp_access;
                        cur_hwbp.addr_bp = addr;
                        cur_hwbp.addr_single_step = reinterpret_cast<uint8_t*>(addr) + sizeof(uint8_t);

                        hwbp_list.push_back(cur_hwbp);

                        for (size_t i = NULL; i < process_info->NumberOfThreads; i++)
                        {
                            acces = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, FALSE, reinterpret_cast<uint32_t>(process_info->Threads[i].ClientId.UniqueThread));
                            if (acces)
                            {
                                ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
                                nt_status = reinterpret_cast<decltype(&NtGetContextThread)>(nt_get_context)(acces, &ctx);
                                if (NT_SUCCESS(nt_status))
                                {
                                    if (set_thread_dr(addr, hwbp_access, hwbp_len, &ctx))
                                    {
                                        ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
                                        nt_status = reinterpret_cast<decltype(&NtSetContextThread)>(nt_set_context)(acces, &ctx);
                                        if (!NT_SUCCESS(nt_status))
                                        {
                                            is_success = FALSE;
                                        }
                                    }
                                    else
                                    {
                                        is_success = FALSE;
                                    }
                                }
                                else
                                {
                                    is_success = FALSE;
                                }
                                CloseHandle(acces);
                            }
                            else
                            {
                                is_success = FALSE;
                            }
                        }
                        break;
                    }
                    process_info = (PSYSTEM_PROCESS_INFORMATION)((LPBYTE)process_info + process_info->NextEntryOffset); // Calculate the address of the next entry.
                }
                free(buffer);
            }
            else
            {
                acces = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, FALSE, thread_id);
                if (acces)
                {
                    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
                    nt_status = reinterpret_cast<decltype(&NtGetContextThread)>(nt_get_context)(acces, &ctx);
                    if (NT_SUCCESS(nt_status))
                    {
                        if (set_thread_dr(addr, hwbp_access, hwbp_len, &ctx))
                        {
                            ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
                            nt_status = reinterpret_cast<decltype(&NtSetContextThread)>(nt_set_context)(acces, &ctx);
                            if (NT_SUCCESS(nt_status))
                            {
                                is_success = TRUE;
                            }
                        }
                    }
                }
            }
        }
    }
    return is_success;
}


Плюсы:
Нет изменения в памяти и меняется в контексте потока только 2 значения(при инициализации)
Довольно удобен из-за возможности нахождения обращения к памяти

Минусы:
Очень часто Dr(0-3) или Dr6/Dr7 будут проверять на инициализацию из-за чего вам понадобится скрыть их присутствие(те же протекторы почти всегда их проверяют). Проверка Dr через SEH/NtGetContextThread или перезапись через NtSetContextThread/ NtContinue
Требуется обработчик исключений



Page Guard

⠀⠀⠀⠀⠀Самые неудобный и медленный хук,но он позволяет находить обращение не к нескольким байтам(как HWBP),а к целой странице в памяти. При установке хука мы устанавливаем флаг PAGE_GUARD в Protect. При обработке исключений мы должны временно убрать PAGE_GUARD и установить TRAP_FLAG,чтобы выполнилась следующая инструкция, чтобы код не находился в spinlock. При выполнении TRAP_FLAG нужно снова вернуть PAGE_GUARD. Из-за такой обработки код будет выполняться очень медленно(выполнение 1 инструкции = обработка TRAP_FLAG и PAGE_GUARD),поэтому рекомендую использовать этот хук очень осторожно. Реализация довольно простая, но я уже упоминал из-за чего этот хук медленный и даже ситуативный:
C++:
Expand Collapse Copy
NO_INLINE auto set_pg_hook(PVOID addr, PAGE_GUARD_TYPE_ACCESS access)
{
    DWORD old_prot = NULL;
    MEMORY_BASIC_INFORMATION mbi = { NULL };
    PAGE_GUARD_INFO cur_pg;

    if(VirtualQuery(addr, &mbi, sizeof(mbi)))
    {
        if (!(mbi.Protect & PAGE_GUARD) && !(mbi.Protect & PAGE_NOACCESS))
        {
            cur_pg.is_single_step = FALSE;
            cur_pg.access = access;
            cur_pg.addr = addr;
            cur_pg.reg_addr = mbi.BaseAddress;
            cur_pg.reg_size = mbi.RegionSize;
           
            if (VirtualProtect(addr, sizeof(PVOID), mbi.Protect | PAGE_GUARD, &old_prot))
            {
                pg_list.push_back(cur_pg);
                return TRUE;
            }
        }
    }
    return FALSE;
}

Плюсы:

Происходит изменение только защиты страницы.
Довольно удобен из-за возможности нахождения обращения к памяти.
Редко проверяют данных хук.

Минусы:
Очень медленное выполнение.
Реверсер может лёгкое обнаружить этот хук основываясь на Characteristics секции(если речь про MEM_IMAGE) или просто сделать чтение 1 байта у каждой страницы из-за чего ваше ожидание на определённую логику может быть нарушено.

Trampoline hook

⠀⠀⠀⠀⠀С первого взгляда реализация этого хука лёгкая, пока вы не заденете больную тему: перемещение памяти программы… Основная проблема – обработка любой jcc/rip инструкции.
Пожалуйста, авторизуйтесь для просмотра ссылки.
приведён базовый пример решения с использованием
Пожалуйста, авторизуйтесь для просмотра ссылки.
&
Пожалуйста, авторизуйтесь для просмотра ссылки.
,но не все моменты прописаны. Для установки хука мы должны скопировать размер байтов равные/больше нужного размера батута и установить батут т.е переход к нашему коду. Сама реализация не является сложной, если не учитывать обработку jcc & rip инструкций:
C++:
Expand Collapse Copy
NO_INLINE auto set_hook(PVOID addr, PVOID callback, PVOID* trampline_fun) -> BOOLEAN
{
    DWORD old_prot = NULL;
    PVOID rip_fixer = NULL;
    PVOID copy_pointer = NULL;

    TRAMPLINE_HOOK cur_hook = { NULL };

#ifndef _WIN64
    uint8_t shell_jmp[] =
    {
        0x50, //push eax
        0xC7, 0x04, 0x24, 0x00, 0x00, 0x00, 0x00, // mov [esp+4],NULL
        0xC3  //ret
    };
#else

    uint8_t shell_jmp[] =
    {
        0x50, //push rax
        0x50, //push rax
        0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //mov rax,NULL
        0x48, 0x89, 0x44, 0x24,    0x08, //mpv [rsp+8],rax
        0x58, //pop rax
        0xC3  //ret
    };
#endif // !_WIN64
   

    cur_hook.addr = addr;
    cur_hook.orig_size = sizeof(shell_jmp);

    copy_pointer = callback;

    memcpy(&cur_hook.orig_byte, addr, sizeof(shell_jmp));
    rip_fixer = cg_util::get_rip_fixer(addr, sizeof(shell_jmp));
    if (rip_fixer)
    {
        *trampline_fun = rip_fixer;
        if (VirtualProtect(addr, sizeof(shell_jmp), PAGE_EXECUTE_READWRITE, &old_prot))
        {
            memcpy(reinterpret_cast<uint8_t*>(shell_jmp) + 4, &copy_pointer, sizeof(PVOID));

            memcpy(addr, shell_jmp, sizeof(shell_jmp));

            VirtualProtect(addr, sizeof(shell_jmp), old_prot, &old_prot);

            trampline_list.push_back(cur_hook);
            return TRUE;
        }
        VirtualFree(rip_fixer, NULL, MEM_RELEASE);

    }
    return FALSE;
}
Плюсы:
Быстрое выполнение кода(как и при подмене указателя).
Не требуется обработчик исключений.

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


Жизнь до и после исключений(callback)

KiUserExceptionDispatcher

⠀⠀⠀⠀⠀Основная часть популярных хуков в UM построена на исключениях(export,HWBP,PG hook,bad pointer и можно добавить PAGE hook). Нас интересует быть максимально сильно в начале обработки исключений, чтобы была возможность манипулировать контекстом потока. Это обусловлено из-за обнаружения или неправильного поведения(например, программа использует VEH или SEH,но наш хук выполняется на уровне unhandled excepthion(при регистрации с помощью SetUnhandledExceptionFilter,например) из-за чего с большим шансом выполнится тот же VEH или SEH(если RIP попадёт в адресное пространство самого обработчика). Мы должны передать выполнение на оригинальные инструкции или на свой код, который сохраняет логику выполнения кода программы.
⠀⠀⠀⠀⠀Важно помнить об ограничениях: мы не сможем установить, например, на наш callback KiUserExceptionDispatcher для обработки исключений и туда же впихнуть любой хук на основе исключения т.к мы получим spinlock(т.е никаких хуков на код, который будет выполняться до передачи выполнение на наш обработчик исключений).

Важным моментом всей обработки исключений в UM является KiUserExceptionDispatcher.

В Windows 7,8,10,10 x64 она одинаковы и выглядит следующим образом:
qF67bpo.png


x32(WoW64):
kMpxbWo.png

Из-за хранения аргументов слегка не в стандартном виде в x64, потребуется написать небольшую оболочку для переходу к вашему. Для x32 мы должны сохранить параметры в стеке, чтобы при вызове оригинальной функции они не исчезли. Вот небольшая часть реализации кода:
KiUserExceptionDispatcher_hook:
Expand Collapse Copy
else if (type == excep_ki_dispatcher_callback)
{
    ntdll_base = GetModuleHandleW(L"ntdll.dll");
    if (ntdll_base)
    {
        //just hook KiUserExceptionDispatcher
        api_addr = reinterpret_cast<uint8_t*>(GetProcAddress(ntdll_base, "KiUserExceptionDispatcher"));

        if (api_addr)
        {
            rip_fixer = get_rip_fixer(api_addr, callback_ki_excep, sizeof(shell_jmp));
            if (rip_fixer)
            {
                copy_pointer = rip_fixer;
                if (VirtualProtect(api_addr, sizeof(shell_jmp), PAGE_EXECUTE_READWRITE, &old_prot))
                {
                    callback_info.type = excep_ki_dispatcher_callback;
                    callback_info.user_callback = addr_filter;
                    callback_info.orig_callback = NULL;


#ifndef _WIN64
                    memcpy(reinterpret_cast<uint8_t*>(shell_jmp) + 1, &copy_pointer, sizeof(PVOID));
#else
                    memcpy(reinterpret_cast<uint8_t*>(shell_jmp) + 2, &copy_pointer, sizeof(PVOID));

#endif // !_WIN64

                    memcpy(api_addr, shell_jmp, sizeof(shell_jmp));

                    VirtualProtect(api_addr, sizeof(shell_jmp), old_prot, &old_prot);

                    return TRUE;
                }
                VirtualFree(rip_fixer, NULL, MEM_RELEASE);

            }
        }
    }
    return callback_info.orig_callback != NULL;
}

Плюсы такого обработчика исключения:
Обработка исключения начинается с самого передачи выполнения в UM.
Избежание любых хуков, которые расположены ниже по выполнению(тот же VEH),
Минусы:


Wow64PrepareForException

Данный обработчик можно легко зарегистрировать т.к это подмена указателя:
7soUeEO.png



Нам нужно только сохранить и заменить указатель(Wow64PrepareForException). Этот хук можно реализовтаь только в x64 архитектуре или в x32,но под выполнением WoW64.Однако, здесь рассматривается только x64 архитектура т.к иначе это будет специфический хук.

Сама реализация довольно максимально простая:
C++:
Expand Collapse Copy
    ntdll_base = GetModuleHandleW(L"ntdll.dll");
    if (ntdll_base)
    {
        api_addr = reinterpret_cast<uint8_t*>(GetProcAddress(ntdll_base, "KiUserExceptionDispatcher"));
        if (api_addr && !memcmp(api_addr, ki_sig_check, sizeof(ki_sig_check)))
        {
            //memcpy(&offset, api_addr + sizeof(ki_sig_check), sizeof(offset));
            //addr_hook = api_addr + sizeof(uint8_t) + 7 + offset; //7 - size instructhion, offset - the difference in values between the target value, sizeof(uint8_t) - targer instr next
            if (ZYAN_SUCCESS(dis::get_dis(&dis_instr, api_addr + sizeof(uint8_t))))
            {
                addr_hook = dis::get_absolute_address(&dis_instr, api_addr + sizeof(uint8_t));
                /*
                    mov     rcx, rsp
                    add     rcx, 4F0h
                    mov     rdx, rsp
                    call    rax ; Wow64PrepareForException
                    ;Some code
                    mov     rcx, rsp
                    add     rcx, 4F0h
                    mov     rdx, rsp
                    call    RtlDispatchException
                */
                if (addr_hook && VirtualProtect(addr_hook, sizeof(PVOID), PAGE_READWRITE, &old_prot))
                {

                    callback_info.type = excep_wow_prepare_callback;
                    callback_info.user_callback = addr_filter;
                    callback_info.orig_callback = *reinterpret_cast<PVOID*>(addr_hook);

                    *reinterpret_cast<PVOID*>(addr_hook) = callback_ki_excep;

                    VirtualProtect(addr_hook, sizeof(PVOID), old_prot, &old_prot);
                    return TRUE;
                }
            }
        }
    }

}

Плюсы такого обработчика исключения:
Довольно скрытый т.к его редко проверяют на инициализацию и указывает ли он на ожидаемые данные(в x64 он будет 0).
Выполняется в прямом смысле после KiUserExceptionDispatcher и довольно удобен в использовании т.к нам не нужнл думать над исправлением передачей параметров.

Минусы:
Лёгкое обнаружение и выполняется после KiUserExceptionDispatcher
Использование только в x64


Ntdll32KiUserExceptionDispatcher

⠀⠀⠀⠀⠀Нет ничего интереснее, чем изменить адрес KiUserExceptionDispatcher т.е заставить выполнять ваш код после исключения(речь про x32 code под WoW64) ,а не обработчика и тем более при установленном вражеского хука. Это можно реализовать только в x64 системе под управлением WoW64,подменив указатель в wow64.dll. В x64 не получится реализовать простым способом подмену в ядре указатель KeUserExceptionDispatcher т.к адрес загрузки ntdll.dll(x64) фиксирован, что портит все планы(в теории можно добиться этого, но встаёт вопрос о хуке и о PatchGuard.
qYuq1hW.png

Для реализация этого творения потребуется получить адрес ntdll.dll(x64,но можно заменить manual syscall), wow64.dll(x64) и заставить выполнять код под x64,а не x32 под WoW64(можно добиться копированием памяти в буфер и перезаписать исправленный код,но я решил идти по простому пути). В коде
Пожалуйста, авторизуйтесь для просмотра ссылки.
с использованием библиотеки
Пожалуйста, авторизуйтесь для просмотра ссылки.
.

Нам так же нужно будет пройтись по секциям и составить паттерн для нахождения указателя Ntdll32KiUserExceptionDispatcher.
Код:
Expand Collapse Copy
/*

NTSTATUS
NTAPI
NtProtectVirtualMemory
(
     HANDLE ProcessHandle,
     PVOID* BaseAddress,
     PSIZE_T RegionSize,
     ULONG NewProtect,
     PULONG OldProtect
);

#pragma optimize( "", off )
NO_INLINE auto change_callback2() -> BOOLEAN
{
    uint32_t offset = NULL;
    DWORD old_prot = NULL;
    SIZE_T size_prot = NULL;

    uint64_t mod_addr = 0X111111111111111;
    uint64_t nt_protect_mem = 0X2222222222222222;
    uint64_t hook = 0X3333333333333333;
    uint64_t orig_callback = 0X4444444444444;

    uint8_t* sec_addr = NULL;
    uint8_t* targer_addr = NULL;
    PIMAGE_NT_HEADERS headers = NULL;
    PIMAGE_SECTION_HEADER sections = NULL;

    if (reinterpret_cast<PIMAGE_DOS_HEADER>(mod_addr)->e_magic == IMAGE_DOS_SIGNATURE)
    {

        headers = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(mod_addr) + reinterpret_cast<PIMAGE_DOS_HEADER>(mod_addr)->e_lfanew);
        sections = IMAGE_FIRST_SECTION(headers);

        for (size_t i = 0; i < headers->FileHeader.NumberOfSections; i++)
        {
            sec_addr = reinterpret_cast<uint8_t*>(mod_addr) + sections[i].VirtualAddress;
            if ((sections[i].Characteristics & IMAGE_SCN_MEM_READ) && (sections[i].Characteristics & IMAGE_SCN_MEM_EXECUTE))
            {
                if (sections[i].Misc.VirtualSize > 20)
                {
                    for (size_t j = 0; j < sections[i].Misc.VirtualSize - 20; j++)
                    {
                        //Wow64pSetupExceptionDispatch 8b ?? 24 ?? e8 ?? ?? ?? ?? BA 01 00 00 00 8B win 10-11
                        //Wow64SetupExceptionDispatch  C7 ?? 02 00 01 00 8B 05 ?? ?? ?? ?? 89 ?? AC 00 00 00 win7-8.1
                        if
                            (
                                (
                                    *(sec_addr + j) == 0x8B &&
                                    *(sec_addr + j + 2) == 0x24 &&
                                    *(sec_addr + j + 4) == 0xE8 &&
                                    *(sec_addr + j + 9) == 0xBA &&
                                    *(sec_addr + j + 10) == 0x01 &&
                                    *(sec_addr + j + 11) == NULL &&
                                    *(sec_addr + j + 12) == NULL &&
                                    *(sec_addr + j + 13) == NULL &&
                                    *(sec_addr + j + 14) == 0x8B
                                ) ||
                                (
                                    *(sec_addr + j) == 0xC7 &&
                                    *(sec_addr + j + 2) == 0x02 &&
                                    *(sec_addr + j + 3) == NULL &&                                  
                                    *(sec_addr + j + 4) == 0x01 &&
                                     *(sec_addr + j + 5) == NULL &&
                                    *(sec_addr + j + 6) == 0x8B &&
                                    *(sec_addr + j + 7) == 0x05 &&
                                    *(sec_addr + j + 12) == 0x89 &&
                                    *(sec_addr + j + 14) == 0xAC &&
                                    *(sec_addr + j + 15) == NULL &&
                                    *(sec_addr + j + 16) == NULL &&
                                    *(sec_addr + j + 17) == NULL

                                )
                            )
                        {
                            if(*(sec_addr + j) == 0x8B)
                            {
                                memcpy(&offset, sec_addr + j + 16, sizeof(uint32_t));
                                targer_addr = sec_addr + j + 14 + 6 + offset;
                            }
                            else if(*(sec_addr + j) == 0xC7))
                            {
                                memcpy(&offset, sec_addr + j + 8, sizeof(uint32_t));
                                targer_addr = sec_addr + j + 6 + 6 + offset;
                            }
                            size_prot = sizeof(uint32_t);


                            //hear some problem
                            if (reinterpret_cast<uint64_t>(targer_addr) >= mod_addr && (reinterpret_cast<uint8_t*>(mod_addr) + headers->OptionalHeader.SizeOfImage - sizeof(PVOID)) > targer_addr)
                            {
                                if (NT_SUCCESS(reinterpret_cast<decltype(&NtProtectVirtualMemory)>(nt_protect_mem)(NtCurrentProcess, reinterpret_cast<PVOID*>(&targer_addr), &size_prot, PAGE_READWRITE, &old_prot)))
                                {
                                    if(*(sec_addr + j) == 0x8B)
                                    {
                                        targer_addr = sec_addr + j + 14 + 6 + offset;
                                    }
                                    else
                                    {
                                        targer_addr = sec_addr + j + 6 + 6 + offset;
                                    }

                                    *reinterpret_cast<uint32_t*>(orig_callback) = *reinterpret_cast<uint32_t*>(targer_addr);
                                    *reinterpret_cast<uint32_t*>(targer_addr) = hook;

                                    size_prot = sizeof(uint32_t);

                                    if(*(sec_addr + j) == 0x8B)
                                    {
                                        targer_addr = sec_addr + j + 14 + 6 + offset;
                                    }
                                    else
                                    {
                                        targer_addr = sec_addr + j + 6 + 6 + offset;
                                    }

                                    reinterpret_cast<decltype(&NtProtectVirtualMemory)>(nt_protect_mem)(NtCurrentProcess, reinterpret_cast<PVOID*>(&targer_addr), &size_prot, old_prot, &old_prot);
                                    return TRUE;
                                }
                            }
                        }
                    }
                }
            }
        }

    }
    return FALSE;
}
#pragma optimize( "", on )
*/
Плюсы такого обработчика исключения:
Очень скрытый способ перехват исключения. Я пока не видел публичного примера использования, но, возможно, плохо искал.
Просо подмена указателя.

Минусы:
Использование только в x32 под управлением WoW64.
Нужно быть уверенным в нахождении данного указателя.



VEH(Vectored Exception Handler)

⠀⠀⠀⠀⠀Очень распространённый callback т.к установку предоставляет функция WINAPI AddVectoredExceptionHandler и callback давно известен. Мы должны зарегистрировать обработчик первым, чтобы игнорировать проблемы при обработке исключений. Я не вижу смысл говорить т.к реализация довольно простая:
C++:
Expand Collapse Copy
//some code
callback_info.handle_veh = AddVectoredExceptionHandler(TRUE, reinterpret_cast<PVECTORED_EXCEPTION_HANDLER>(callback_pointer));
//some code


Плюсы такого обработчика исключения:
Очень прост в использовании(поскольку это просто WINAPI).
Можно написать код на кражу чужого VEH обработчика (если не хотим регистрировать свой).

Минусы:
Легко можно обнаружить обработчик, если зарегистрировать свой обработчик первым или просто выключить(убрать нужный бит в CrossProcessFlags).
Выполняется обработка не сразу.

Заключительные слова

⠀⠀⠀⠀⠀С одной стороны реализация и идея является лёгкой, а с другой позволяет задуматься и поиграться со многими моментами с другой стороны. Здесь продемонстрирована лишь часть хуков т.к можно сделать хук на основе PAGE_HOOK(изменение защиты страницы на PAGE_READONL), Wow64Transition(syscall hook WoW64), TurboDispatchJumpAddressStart(syscall hook WoW64(SharpOD использует их для перехода кода в x64 ntdll,например)) и пару ещё других видов хуков.
⠀⠀⠀⠀⠀
Для уже существующих хуков можете просто реализовать свою логику со своим excepthion callback, некоторые хуки не совсем удобно использовать или нужно писать свою логику(например, для обфусцированного кода). Здесь приходит много мыслей, поэтому оглянитесь и подумайте над своими новыми идеями.
⠀⠀⠀⠀⠀Это лишь небольшой список и существует более агрессивное использование хуков, поэтому рекомендую посмотреть на промахи протекторов или атичитов(счастье любит тишину, поэтому приводить примеры, а тем более готовый PoC не буду). Спасибо за чтение статьи и всем удачи :).

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

⠀⠀⠀
 
Последнее редактирование:
И мини бонус т.к не уместилось в статью из-за огранечений символов(манипуляция/перечесление/установка VEH | VCH)
C++:
Expand Collapse Copy
#ifndef VEH_UTIL
#define VEH_UTIL
#include <cstdint>

#ifndef PROCESS_USE_VEH
#define PROCESS_USE_VEH 0x4
#endif // !PROCESS_USE_VEH

#ifndef PROCESS_USE_VCH
#define PROCESS_USE_VCH 0x8
#endif // !PROCESS_USE_VCH

#define WINDOWS_NUMBER_2000 0x1
#define WINDOWS_NUMBER_XP 0x2
#define WINDOWS_VISTA 0x4
#define WINDOWS_NUMBER_7 0x8
#define WINDOWS_NUMBER_8 0x10
#define WINDOWS_NUMBER_8_1 0x20
#define WINDOWS_NUMBER_10 0x40
#define WINDOWS_NUMBER_11 0x80



namespace veh_help
{
    static INT windows_number;
    static PVOID veh_list_addr;
 
    typedef struct _VEH_LIST_INFO
    {
        PVOID unk;
        LIST_ENTRY veh_list;
        LIST_ENTRY vch_list;
    }VEH_LIST_INFO;
    //Test Windows 7 & 8.1
    typedef struct _VEH_HANDLER_OLD
    {
        LIST_ENTRY veh_list;
        PVOID one;//DWORD write
        PVOID veh_filter;
    }VEH_HANDLER_OLD;

    //Test Windows 10-11
    typedef struct _VEH_HANDLER
    {
        LIST_ENTRY veh_list;
        PVOID pointer_one;
        PVOID unk;
        PVOID veh_filter;
    }VEH_HANDLER;

    class veh_util
    {
 
    private:
        NO_INLINE static auto get_list_vector
        (

        )    -> BOOLEAN
        {
            uint32_t offset_call = NULL;
            uint32_t offset_vector_except = NULL;

            HMODULE ntdll_base = NULL;
            uint8_t* remove_vector_except_vector = NULL;
            uint8_t* remove_except_vectored = NULL;
            if (veh_list_addr)
            {
                return TRUE;
            }
            ntdll_base = GetModuleHandleW(L"ntdll.dll");
            if (ntdll_base)
            {
                remove_vector_except_vector = reinterpret_cast<uint8_t*>(GetProcAddress(ntdll_base, "RtlRemoveVectoredExceptionHandler"));
                if (remove_vector_except_vector)
                {
                    for (uint8_t i = NULL; i < 0x50; i++)
                    {
                        if (*(remove_vector_except_vector + i) == 0xC3 || *(remove_vector_except_vector + i) == 0xC2 || *(remove_vector_except_vector + i) == 0xCC)//x64 ret - opcode 0XC3,x32 ret - opcode C2 ret imm,0XCC - code cave
                        {
                            return FALSE;
                        }
                        else if (*(remove_vector_except_vector + i) == 0xE8 || *(remove_vector_except_vector + i) == 0XE9) //opcode jmp/call imm
                        {
                            memcpy(&offset_call, remove_vector_except_vector + i + 1, sizeof(offset_call));
                            if (offset_call >= INT_MAX)
                            {
                                offset_call = ~offset_call;
                                remove_except_vectored = remove_vector_except_vector + i - (offset_call - sizeof(offset_call));
                            }
                            else
                            {
                                remove_except_vectored = remove_vector_except_vector + i + offset_call + 5;
                            }

                            break;
                        }
                    }
                    if (offset_call)
                    {
                        for (uint16_t i = 0; i < 0X100; i++)
                        {
                            if (*(remove_except_vectored + i) == 0xCC || *(remove_except_vectored + i) == 0xE8)//0xCC - code cave,0xE8 - call RtlAcquireSRWLockExclusiv
                            {
                                return FALSE;
                            }
#ifndef _WIN64
                            /*
                            81 C6 24 47 F7 7D  add     esi, offset _LdrpVectorHandlerList
                            81 C7 E4 BC 37 6B  add     edi, offset _LdrpVectorHandlerList
                            FF B3 44 13 3B 4B  push    ds:_LdrpVectorHandlerList[ebx]
                            */
                            if (
                                (*(remove_except_vectored + i) == 0xFF && ((*(remove_except_vectored + i + 1) >= 0xB0 && 0xB7 > *(remove_except_vectored + i + 1)) || *(remove_except_vectored + i) == 0x35)) || //push    ds:[reg_32]
                                (*(remove_except_vectored + i) == 0x81 && (*(remove_except_vectored + i + 1) >= 0xC0 && 0xC8 > *(remove_except_vectored + i + 1)))//add reg,offset


                                )
                            {
                                memcpy(&offset_vector_except, remove_except_vectored + i + 2, sizeof(offset_vector_except));
                                veh_list_addr = reinterpret_cast<PVOID>(offset_vector_except);//To-do:it's reloce(can wolk in IMAGE_DIRECTORY_ENTRY_BASERELOC by min addr reloce && reloce >= RtlpRemoveVectoredHandler(remove_except_vectored)
                                return TRUE;
                            }
#else
                            if (
                                (*(remove_except_vectored + i) == 0x48 || *(remove_except_vectored + i) == 0x4C) &&
                                *(remove_except_vectored + i + 1) == 0x8D
                                )
                            {
                                /*
                                WIN7    48 8D 05 5D C3 06 00 lea     rax, LdrpVectorHandlerList
                                WIN 8.1 48 8D 05 B9 22 0B 00 lea     rax, LdrpVectorHandlerList
                                WIN 11  4C 8D 25 46 4E 11 00 lea     r12, LdrpVectorHandlerList

                                */
                                memcpy(&offset_vector_except, remove_except_vectored + i + 3, sizeof(offset_vector_except));
                                if (offset_call >= INT_MAX)
                                {
                                    offset_call = ~offset_call;
                                    veh_list_addr = remove_except_vectored + i - (offset_vector_except - sizeof(offset_vector_except));
                                }
                                else
                                {
                                    veh_list_addr = remove_except_vectored + i + offset_vector_except + 7;
                                }
                                return TRUE;
                            }
#endif // !_WIN64

                        }
                    }
                }
            }
            return FALSE;
        }

        //Windows 7-8.1
        auto static add_vectored_handler_win7
        (
            ULONG First,
            PVECTORED_EXCEPTION_HANDLER Handler,
            BOOLEAN is_continue_handler = FALSE
        ) -> PVOID
        {
            BOOLEAN is_init = NULL;
            VEH_HANDLER_OLD* veh_handler_info = NULL;
            LIST_ENTRY* list_veh = NULL;
            DWORD old_prot = NULL;
            
            if (!get_list_vector())
            {
                return NULL;
            }

            if (is_continue_handler)
            {
                list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->vch_list;
            }
            else
            {
                list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->veh_list;
            }
            is_init = list_veh->Flink == list_veh;

            veh_handler_info = reinterpret_cast<VEH_HANDLER_OLD*>(VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE));


            *reinterpret_cast<uint32_t*>(&veh_handler_info->one) = TRUE;
            veh_handler_info->veh_filter = EncodePointer(Handler);

            if (is_init || First)
            {
                if (is_init)
                {
                    if (is_continue_handler)
                    {
                        NtCurrentPeb()->u2.CrossProcessFlags |= PROCESS_USE_VCH;
                    }
                    else
                    {
                        NtCurrentPeb()->u2.CrossProcessFlags |= PROCESS_USE_VEH;
                    }

                    veh_handler_info->veh_list.Flink = reinterpret_cast<LIST_ENTRY*>(list_veh);
                    veh_handler_info->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(list_veh);
                    VirtualProtect(list_veh, 0x1000, PAGE_READWRITE, &old_prot);
                    list_veh->Flink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    list_veh->Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    VirtualProtect(list_veh, 0x1000, old_prot, &old_prot);

                }
                else
                {
                    veh_handler_info->veh_list.Flink = list_veh->Flink;
                    veh_handler_info->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(list_veh);

                    //reinterpret_cast<VEH_HANDLER_OLD*>(veh_handler_info->veh_list.Flink)->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    reinterpret_cast<VEH_HANDLER_OLD*>(list_veh->Flink)->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);

                    VirtualProtect(list_veh, 0x1000, PAGE_READWRITE, &old_prot);
                    list_veh->Flink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    VirtualProtect(list_veh, 0x1000, old_prot, &old_prot);
                }
            }
            else
            {
                veh_handler_info->veh_list.Flink = reinterpret_cast<LIST_ENTRY*>(list_veh);
                veh_handler_info->veh_list.Blink = list_veh->Blink;

                reinterpret_cast<VEH_HANDLER_OLD*>(list_veh->Blink)->veh_list.Flink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);

                VirtualProtect(list_veh, 0x1000, PAGE_READWRITE, &old_prot);
                list_veh->Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                VirtualProtect(list_veh, 0x1000, old_prot, &old_prot);
            }
            return veh_handler_info->veh_filter;
        }

        //Windows 10-11
        auto static add_vectored_handler_win10
        (
            ULONG First,
            PVECTORED_EXCEPTION_HANDLER Handler,
            BOOLEAN is_continue_handler = FALSE
        ) -> PVOID
        {
            BOOLEAN is_init = NULL;
            VEH_HANDLER* veh_handler_info = NULL;
            PVOID pointer_one = NULL;

            LIST_ENTRY* list_veh = NULL;
            DWORD old_prot = NULL;

            if (!get_list_vector())
            {
                return NULL;
            }

            if (is_continue_handler)
            {
                list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->vch_list;
            }
            else
            {
                list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->veh_list;
            }
            is_init = list_veh->Flink == list_veh;

            veh_handler_info = reinterpret_cast<VEH_HANDLER*>(VirtualAlloc(NULL, 0X1000, MEM_COMMIT, PAGE_READWRITE));
            pointer_one = VirtualAlloc(NULL, 0X1000, MEM_COMMIT, PAGE_READWRITE);
            *reinterpret_cast<uint64_t*>(pointer_one) = TRUE;


            veh_handler_info->unk = NULL;
            veh_handler_info->pointer_one = pointer_one;
            veh_handler_info->veh_filter = EncodePointer(Handler);

            if (is_init || First)
            {
                if (is_init)
                {
                    if (is_continue_handler)
                    {
                        NtCurrentPeb()->u2.CrossProcessFlags |= PROCESS_USE_VCH;
                    }
                    else
                    {
                        NtCurrentPeb()->u2.CrossProcessFlags |= PROCESS_USE_VEH;
                    }
                    veh_handler_info->veh_list.Flink = reinterpret_cast<LIST_ENTRY*>(list_veh);
                    veh_handler_info->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(list_veh);
                    VirtualProtect(list_veh, 0x1000, PAGE_READWRITE, &old_prot);
                    list_veh->Flink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    list_veh->Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    VirtualProtect(list_veh, 0x1000, old_prot, &old_prot);

                }
                else
                {
                    veh_handler_info->veh_list.Flink = list_veh->Flink;
                    veh_handler_info->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(list_veh);

                    //reinterpret_cast<VEH_HANDLER*>(veh_handler_info->veh_list.Flink)->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    reinterpret_cast<VEH_HANDLER*>(list_veh->Flink)->veh_list.Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);

                    VirtualProtect(list_veh, 0x1000, PAGE_READWRITE, &old_prot);
                    list_veh->Flink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                    VirtualProtect(list_veh, 0x1000, old_prot, &old_prot);
                }
            }
            else
            {
                veh_handler_info->veh_list.Flink = reinterpret_cast<LIST_ENTRY*>(list_veh);
                veh_handler_info->veh_list.Blink = list_veh->Blink;

                reinterpret_cast<VEH_HANDLER*>(list_veh->Blink)->veh_list.Flink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);

                VirtualProtect(list_veh, 0x1000, PAGE_READWRITE, &old_prot);
                list_veh->Blink = reinterpret_cast<LIST_ENTRY*>(veh_handler_info);
                VirtualProtect(list_veh, 0x1000, old_prot, &old_prot);
            }
            return veh_handler_info->veh_filter;
        }

        auto static get_windows_number
        (

        )    -> INT
        {
            uint8_t nt_majorVersion = NULL;
            USHORT build_number = NULL;
            INT nt_build_number = NULL;

            if (windows_number)
            {
                return windows_number;
            }

            build_number = NtCurrentPeb()->OSBuildNumber;
            nt_majorVersion = *reinterpret_cast<uint8_t*>(0x7FFE026C);
            if (nt_majorVersion == 10)
            {
                nt_build_number = *reinterpret_cast<INT*>(0x7FFE0260);//NtBuildNumber
                if (nt_build_number >= 22000)
                {
                    windows_number = WINDOWS_NUMBER_11;
                    return WINDOWS_NUMBER_11;
                }
                windows_number = WINDOWS_NUMBER_10;
                return WINDOWS_NUMBER_10;
            }
            else if (nt_majorVersion == 5)
            {
                windows_number = WINDOWS_NUMBER_XP;
                return WINDOWS_NUMBER_XP;//Windows XP
            }
            else if (nt_majorVersion == 6)
            {
                /*
                https://www.godeye.club/2021/06/03/002-mhyprot-insider-callbacks.html
                */
                switch (*reinterpret_cast<uint8_t*>(0x7FFE0270))  //0x7FFE0270 NtMinorVersion
                {
                case 1:
                {
                    windows_number = WINDOWS_NUMBER_7;
                    return WINDOWS_NUMBER_7;//windows 7
                }
                case 2:
                {
                    windows_number = WINDOWS_NUMBER_8;
                    return WINDOWS_NUMBER_8; //window 8
                }
                {
                    windows_number = WINDOWS_NUMBER_8_1;
                    return WINDOWS_NUMBER_8_1; //windows 8.1
                }
                default:
                    break;
                }

            }
            if (build_number < 7600 &&
                build_number > 3800
                )
            {
                windows_number = WINDOWS_VISTA;
                return WINDOWS_VISTA;
            }

            if (build_number <= 2195)
            {
                windows_number = WINDOWS_NUMBER_2000;
                return WINDOWS_NUMBER_2000;
            }
            if (build_number <= 22621 && build_number >= 10240)
            {
                windows_number = WINDOWS_NUMBER_10;
                return WINDOWS_NUMBER_10;
            }
            return NULL;
        }

        public:

        auto static enum_list_veh
        (
            BOOLEAN is_continue_handler = FALSE
        ) -> VOID
        {
            INT windows_numb = get_windows_number();
            LIST_ENTRY* list_veh = NULL;
            
            if (!get_list_vector())
            {
                return;
            }

            if (is_continue_handler)
            {
                list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->vch_list;
            }
            else
            {
                list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->veh_list;
            }
            
            for (auto curr = list_veh->Flink, last = list_veh; curr != last; curr = curr->Flink)
            {
                if (windows_numb >= WINDOWS_NUMBER_10)
                {
                    printf("veh_filter ->\t%p\n", DecodePointer(reinterpret_cast<VEH_HANDLER*>(curr)->veh_filter));
                }
                else if (windows_numb >= WINDOWS_NUMBER_7)
                {
                    printf("veh_filter ->\t%p\n", DecodePointer(reinterpret_cast<VEH_HANDLER_OLD*>(curr)->veh_filter));
                }
            }
        }

        //return old filter
        auto static vectored_except_hijacking
        (
            ULONG                       First,
            PVECTORED_EXCEPTION_HANDLER Handler

        ) -> PVOID
        {
            INT windows_numb = get_windows_number();
            PVOID old_filter = NULL;
            LIST_ENTRY* list_veh =  NULL ;
             VEH_HANDLER* veh_handler = NULL;
            VEH_HANDLER_OLD* veh_handler_old = NULL;

            if (!get_list_vector())
            {
                return NULL;
            }
            list_veh = &reinterpret_cast<VEH_LIST_INFO*>(veh_list_addr)->veh_list;
            
            if (First)
            {
                if (windows_numb >= WINDOWS_NUMBER_10)
                {
                    veh_handler = reinterpret_cast<VEH_HANDLER*>(list_veh->Flink);
                    old_filter = DecodePointer(veh_handler->veh_filter);
                    veh_handler->veh_filter = EncodePointer(Handler);

                }
                else if (windows_numb >= WINDOWS_NUMBER_7)
                {
                    veh_handler_old = reinterpret_cast<VEH_HANDLER_OLD*>(list_veh->Flink);
                    old_filter = DecodePointer(veh_handler_old->veh_filter);
                    veh_handler_old->veh_filter = EncodePointer(Handler);
                }
            }
            else
            {
                if (windows_numb >= WINDOWS_NUMBER_10)
                {
                    veh_handler = reinterpret_cast<VEH_HANDLER*>(list_veh->Flink);
                    old_filter = DecodePointer(veh_handler->veh_filter);
                    veh_handler->veh_filter = EncodePointer(Handler);

                }
                else if (windows_numb >= WINDOWS_NUMBER_7)
                {
                    veh_handler_old = reinterpret_cast<VEH_HANDLER_OLD*>(list_veh->Flink);
                    old_filter = DecodePointer(veh_handler_old->veh_filter);
                    veh_handler_old->veh_filter = EncodePointer(Handler);
                }
            }
            return old_filter;
        }

        auto static add_vectored_exception_handler
        (
            ULONG                       First,
            PVECTORED_EXCEPTION_HANDLER Handler
        ) -> PVOID
        {
            INT windows_numb = get_windows_number();
            if (windows_numb >= WINDOWS_NUMBER_10)
            {
                return add_vectored_handler_win10(First, Handler);
            }
            else if (windows_numb >= WINDOWS_NUMBER_7)
            {
                return add_vectored_handler_win7(First, Handler);
            }
            return NULL;
        }

        auto static add_vectored_continue_handler
        (
            ULONG                       First,
            PVECTORED_EXCEPTION_HANDLER Handler
        ) -> PVOID
        {
            INT windows_numb = get_windows_number();
            if (windows_numb >= WINDOWS_NUMBER_10)
            {
                return add_vectored_handler_win10(First, Handler,TRUE);
            }
            else if (windows_numb >= WINDOWS_NUMBER_7)
            {
                return add_vectored_handler_win7(First, Handler, TRUE);
            }
            return NULL;
        }
    };
}
#endif // !VEH_UTIL
 
Ебать харош
 
Жесть ты выдал
 
  • Мне нравится
Реакции: mj12
VEH(Vectored Exception Handler)
Было бы славно если ТС упомянул, что он имеет под:" Выполняется обработка не сразу. ", по сравнению с чем? Если сравнивать с SEH - это неправильно. VEH имеет больший приоритет, чем Structed Exception Handling и вызывается ДО SEH обработчика. SEH работает на уровне потока, когда VEH на уровне процесса, поэтому Обработчик -> VEH -> SEH. Но я не думаю, что ты о таком не знаешь, скорее всего ты говоришь из-за того, что нужен перебор всех обработчиков в LdrpVectorHandlerList (?)
Легко можно обнаружить обработчик
Тут согласен(а), но не совсем) Существует список VEH ( LdrpVectorHandlerList ) и скорее всего ты говоришь про него в ntdll, можно избегать использование API функций и использовать напрямую его ( не помню точно где такую логику видел, репозиторий был удален( )
Если зарегистрировать свой обработчик первым или просто выключить(убрать нужный бит в CrossProcessFlags).
То, что ты пользуешься уязвимостями обусловленными Windows ( я имею ввиду защиту ) это прекрасно! Но это все очень устарело, мне интересно, что будет если я заблокирую тебе модификацию VEH списка, будет очень интересно как ты будешь использовать столь изрядный способ, это гораздо лучше, чем CrossProcessFlags ( я сейчас говорю в сторону тебя, но ты почему-то используешь устаревшие детекты ) ( MitigationPolicy->DisableExtensionPoints ). ( Credits: CozyDuke ). Да и далеко бегать не нужно, совсем недавно ( 2024 ) был слит код одного из малварей, который не давал тебе использовать CrossProcessFlags путем обычного просмотра флагов на процессе, для своей работы он изменял флаги, а после успешной работы - бэкапил их ( я лишь предоставляю пример о чем я говорю, не более того ):

Код:
Expand Collapse Copy
#include "shared.h"

int main() {
    PPEB peb = reinterpret_cast<PPEB>(__readgsqword(0x60)); // lazy, it's for x64
    DWORD crossProcessFlags = *(DWORD*)(peb->u2.CrossProcessFlags);
    if (crossProcessFlags & PROCESS_USE_VEH) {
        crossProcessFlags &= ~PROCESS_USE_VEH;
        *(DWORD*)(peb->u2.CrossProcessFlags) = crossProcessFlags;
    }
    AddVectoredExceptionHandler(TRUE, 
    [](PEXCEPTION_POINTERS ExceptionInfo) -> LONG {
        return EXCEPTION_CONTINUE_EXECUTION;
    });
    crossProcessFlags |= PROCESS_USE_VEH;
    *(DWORD*)(peb->u2.CrossProcessFlags) = crossProcessFlags;
    return 0;
}
UPD:
Я прочитал твой код и увидел это ( я про свой второй ответ ):
Код:
Expand Collapse Copy
BOOLEAN is_init = list_veh->Flink == list_veh;
veh_handler_info = reinterpret_cast<VEH_HANDLER_OLD*>(VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE));
veh_handler_info->veh_filter = EncodePointer(Handler);
В общем такое чувство, что времени на исследование глубокое VEH - не хватило( Все, что было предоставление очень уж старое, такое чувство, что это слегка обновленный
Пожалуйста, авторизуйтесь для просмотра ссылки.

Тот код, что ты предоставил - изменение списка LdrpVectorHandlerList в памяти без API, это тебе поможет против crossProcessFlags, а вот DisableExtensionPoints и другие детекты к сожалению - нет. То, что ты делаешь в памяти - круто, а если посмотреть изменения в этом списке, что я обнаружу?) Уже давно есть более новые подходы ( а если то, что ты предоставил(а) это только пример, то я ничего не имею против ). VEH требует более тщательного исследования перед использованием ( первым же делом как правило, все "тру" SECURITY_MAIN_BOSS проверяют исключения и обработчики, которые создает малварь и, чтобы усложнить этот процесс твой код подходит, но реализация - устаревшая.
 
Было бы славно если ТС упомянул, что он имеет под:" Выполняется обработка не сразу. ", по сравнению с чем?
Я не расписывал всё до мельчайших подробностей из-за нехватки используемых символов + это не цель данной статьи, хотя я планировал расписать в будущем другой статье это.
Если супер коротко: VEH -> SEH -> UEF(UnhandledExceptionFilter) -> VCH(если всё правильно помню ).
Расписывать как всё вызывается(пример Windows 10 19045(возможно не всё тут т.к делал анализ быстро тогда) всё добро мало смысла т.к это начнёт загромождать текст и уход не туда:
SetUnhandledExceptionFilter:
KiUserExceptionDispatcher -> RtlDispatchException -> RtlpCallVectoredHandlers(FALSE and ret) -> RtlpGetStackLimits(ret) -> RtlpSanitizeContext(ret) ->
RtlGetExtendedContextLength2(ret) -> __chkstk(ret) -> RtlInitializeExtendedContext2(ret) -> RtlpCopyContext(ret) -> RtlLookupFunctionEntry(ret) ->
-> RtlVirtualUnwind(ret) -> RtlpIsFrameInBounds(ret) -> RtlpIsFrameInBounds(ret) -> RtlpExecuteHandlerForException and return before -> RtlRestoreContext

RtlRestoreContext -> ZwContinue

RtlpExecuteHandlerForException:

call rax -> __C_specific_handler(PE call from/ntdll.dll) -> vcruntime140.dll -> RtlUserThreadStart or `__scrt_common_main_seh'::`1'::filt$0 -> _seh_filter_exe->
ucrtbase.dll and rep

RtlUserThreadStart ->jmp rax -> UnhandledExceptionFilter -> Handler

AddVectoredExceptionHandler:
KiUserExceptionDispatcher -> RtlDispatchException -> RtlpCallVectoredHandlers(retrun TRUE) -> call all Handler(break if EXCEPTION_CONTINUE_EXECUTION) ->
RtlpCallVectoredHandlers(retrun FALSE) ->RtlRestoreContext

RtlpCallVectoredHandlers -> call rax -> Handler

RtlRestoreContext -> ZwContinue

SEH:

RtlRestoreContext

KiUserExceptionDispatcher -> RtlDispatchException -> RtlpCallVectoredHandlers(FALSE and ret) -> RtlpGetStackLimits(ret) -> RtlpSanitizeContext(ret) ->
RtlGetExtendedContextLength2(ret) -> __chkstk(ret) -> RtlInitializeExtendedContext2(ret) -> RtlpCopyContext(ret) -> RtlLookupFunctionEntry(ret) ->
-> RtlVirtualUnwind(ret) -> RtlpIsFrameInBounds(ret) -> RtlpIsFrameInBounds(ret) -> RtlpExecuteHandlerForException and handler execute

RtlRestoreContext -> ZwContinue

RtlpExecuteHandlerForException:
__C_specific_handler(PE bin) -> vcruntime140.dll -> RtlUnwindEx(ntdll) -> RtlpGuardSynchronizeRestorePc and RtlRestoreContext(ntdll & not in KiUserExceptionDispatcher)

То, что ты пользуешься уязвимостями обусловленными Windows
Тут уже на вкус и цвет. Это не уязвимость,а так устроена реализация работы в самой системе.
Пожалуйста, дайте пример для понимания т.к я не совсем понял контекст и не хочу устраивать недопонимания. Если речь про RtlQueryProtectedPolicy(RtlpAddVectoredHandler) ,то тогда могу ответить сразу. Реализовать момент с (PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY ) DisableExtensionPoints = TRUE у меня не получилось(если правильно понял вас),поэтому пример повторить не могу.

В общем такое чувство, что времени на исследование глубокое VEH - не хватило( Все, что было предоставление очень уж старое, такое чувство, что это слегка обновленный
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
Меня не интересует отвечать в стиле высокомерного железного лорда, чтобы не казаться смешным. Если считаете,что я украл откуда-то код и не анализировал работу VEH в Windows 7,8.1,10,11 - считайте так дальше. Я и не говорю,что мы прячем или что-то в этом роде. Мы спокойно можем просто пройтись по этому же списку,чтобы обнаружить зарегистрированный обработчик(логично т.к мы повторяем саму логику для NTAPI практический,но без мусора).
Является ли реализация устаревшей - не знаю т.к тут нужно смотреть про что имеем ввиду. Я вас не понял,поэтому распишите более подробно и особенно контекст.
P.S с уважением, @Ahora_technology.
 
Есть ли проблемы у хука пуджа?
 
Если супер коротко: VEH -> SEH -> UEF(UnhandledExceptionFilter) -> VCH(если всё правильно помню ).
Одно и тоже, что и я написал, просто ты решил(а) дополнить это ( если мы будем говорить о порядке вызовов обработчика ):
KiDispatchException - ring0 -> KiUserExceptionDispatcher - ring3 -> RtlDispatchException -> RtlCallVectoredHandlers ( RltpVectoredExceptionList ) AND calling too RtlCallVectoredContinueHandlers ( RtlpVectoredCONTINUELIST ) ( Ты как раз таки в коде exception_call рассматриваешь, что-то близкое к этому - RtlRestoreContext -> ZwContinue )
C++:
Expand Collapse Copy
VECTORED_HANDLER_LIST->VECTORED_HANDLER_ENTRY->VECTORED_HANDLER_ENTRY(And again to VECTORED_HANDLER_LIST)->RtlDecodePointer(RtlEncodePointer)->VehHandler
https://learn.microsoft.com/en-us/previous-versions/bb432242(v%3Dvs.85) ( no ad)
https://learn.microsoft.com/en-us/previous-versions/dn877133(v=vs.85) (no ad)
Расписывать как всё вызывается(пример Windows 10 19045(возможно не всё тут т.к делал анализ быстро тогда) всё добро мало смысла т.к это начнёт загромождать текст и уход не туда:
Хорошо, я тебя понял(а), для меня эта тема наиболее интересная была и услышать о ней настолько мало и примитивно было даже обидно.

Тут уже на вкус и цвет. Это не уязвимость,а так устроена реализация работы в самой системе.
Пожалуйста, дайте пример для понимания т.к я не совсем понял контекст и не хочу устраивать недопонимания. Если речь про RtlQueryProtectedPolicy(RtlpAddVectoredHandler) ,то тогда могу ответить сразу. Реализовать момент с (PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY ) DisableExtensionPoints = TRUE у меня не получилось(если правильно понял вас),поэтому пример повторить не могу.
Я называю это дырой для AC) Не всегда "уязвимость" имеется в этом контексте. Про то, что у тебя не получилось - возможно ваша версия Windows не поддерживается.

C++:
Expand Collapse Copy
#define PHNT_MODE PHNT_MODE_USER
#define PHNT_VERSION PHNT_20H2

const BYTE pattern_list[] = {
    0x89, 0x46, 0x0c,          // mov [esi+0Ch], eax
    0x81, 0xc3, 0,    0, 0, 0  // add ebx, offset LdrpVectorHandlerList
};
const char mask_list[] = "xxxxx????";

//
// https://github.com/winsiderss/phnt ( no ad )
//

#include <phnt_windows.h>
#include <phnt.h>
#include <iostream>

//
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-process_mitigation_extension_point_disable_policy ( no ad )
//

HANDLE processHandle;

//bool CheckVer() {
//    OSVERSIONINFOEX osvi = { sizeof(OSVERSIONINFOEX) };
//    osvi.dwMajorVersion = 10;
//    DWORDLONG mask = 0;
//    VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
//    return VerifyVersionInfo(&osvi, VER_MAJORVERSION, mask) != FALSE;
//}

bool DisablePoints() {
    //if (!CheckVer()) {
    //    std::cerr << "here your problem - ahora57" << std::endl;
    //    return false;
    //}

    PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY policy = { 0 };
    policy.DisableExtensionPoints = 1;

    PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY currentPolicy = { 0 };
    SIZE_T returnLength = 0;

    if (!GetProcessMitigationPolicy(
        processHandle,
        ProcessExtensionPointDisablePolicy,
        &currentPolicy,
        sizeof(currentPolicy))) {
        DWORD error = GetLastError();
        std::cerr << "failed to get process mitigation policy: " << error << std::endl;
        return false;
    }

    if (currentPolicy.DisableExtensionPoints) {
        std::cout << "already disablerd" << std::endl;
        return true;
    }

    if (!SetProcessMitigationPolicy(
        ProcessExtensionPointDisablePolicy,
        &policy,
        sizeof(policy))) {
        DWORD error = GetLastError();
        std::cerr << "failed to set mitigation" << error << std::endl;
        return false;
    }

    PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY verifyPolicy = { 0 };
    if (!GetProcessMitigationPolicy(
        processHandle,
        ProcessExtensionPointDisablePolicy,
        &verifyPolicy,
        sizeof(verifyPolicy))) {
        std::cerr << "?????" << std::endl;
        return false;
    }

    if (!verifyPolicy.DisableExtensionPoints) {
        std::cerr << "failed to apply" << std::endl;
        return false;
    }

    std::cout << "Successfully" << std::endl;
    return true;
}

int main()
{
    //PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY policy = { 0 };
    //policy.RaiseExceptionOnInvalidHandleReference = true;
    //policy.HandleExceptionsPermanentlyEnabled = true;

    //BOOL result = SetProcessMitigationPolicy(ProcessStrictHandleCheckPolicy, &policy, sizeof(policy));
    //if (!result) {
    //    DWORD errorCode = GetLastError();
    //    std::cerr << "???: " << errorCode << std::endl;
    //    return 1;
    //}

    SetConsoleTitle(L"Yougame");

    processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId());
    if (processHandle == NULL) {
        DWORD error = GetLastError();
        std::cerr << "failed to open: " << error << std::endl;
        return 1;
    }

    if (!DisablePoints()) {
        std::cerr << "Failed to disabler" << std::endl;
        return 1;
    }

    return 0;
}


Меня не интересует отвечать в стиле высокомерного железного лорда, чтобы не казаться смешным. Если считаете,что я украл откуда-то код и не анализировал работу VEH в Windows 7,8.1,10,11 - считайте так дальше. Я и не говорю,что мы прячем или что-то в этом роде. Мы спокойно можем просто пройтись по этому же списку,чтобы обнаружить зарегистрированный обработчик(логично т.к мы повторяем саму логику для NTAPI практический,но без мусора).
Является ли реализация устаревшей - не знаю т.к тут нужно смотреть про что имеем ввиду. Я вас не понял,поэтому распишите более подробно и особенно контекст.
P.S с уважением, Ahora_technology.
Извини за столь "высокомерные" ответы если тебе они такими показались( У меня нет времени разбирать подробно эту тему и писать на югейм. Я лишь хотел сказать, что дан не полные ответы насчет темы про "VEH".
 
Хорошо, я тебя понял(а), для меня эта тема наиболее интересная была и услышать о ней настолько мало и примитивно было даже обидно.
Как скажете,хотя я отвечал на этот вопрос косвенно(Это не цель статьи и ответа. Если устроит такой ответ - лень расписывать больше чем на 2000 символов ответ).
Я называю это дырой для AC) Не всегда "уязвимость" имеется в этом контексте. Про то, что у тебя не получилось - возможно ваша версия Windows не поддерживается.
У меня не получилось заставить нормально работать этот код на:Windows 8.1,10,11(с учётом, что мы ещё в конце добавляем просто VEH обработчик и простой вызов с __debugbreak для проверки вызова VEH),поэтому предположу, что это специфический метод(на мой взгляд) или здесь есть недосказанное. Всё-равно данный механизм можно будет отключить при надобности, но это уже начинаются догонялки и спорить не буду.
Извини за столь "высокомерные" ответы если тебе они такими показались( У меня нет времени разбирать подробно эту тему и писать на югейм. Я лишь хотел сказать, что дан не полные ответы насчет темы про "VEH".
Это и не была цель статьи/ответа т.к здесь рассматривались не чисто исключения/работа NTAPI и механизмы Windows. Однако, благодарю за дополнительную информацию и ваши ответы.
P.S с уважением, Ahora_technology.
 
Было бы славно если ТС упомянул, что он имеет под:" Выполняется обработка не сразу. ", по сравнению с чем? Если сравнивать с SEH - это неправильно. VEH имеет больший приоритет, чем Structed Exception Handling и вызывается ДО SEH обработчика. SEH работает на уровне потока, когда VEH на уровне процесса, поэтому Обработчик -> VEH -> SEH. Но я не думаю, что ты о таком не знаешь, скорее всего ты говоришь из-за того, что нужен перебор всех обработчиков в LdrpVectorHandlerList (?)

Тут согласен(а), но не совсем) Существует список VEH ( LdrpVectorHandlerList ) и скорее всего ты говоришь про него в ntdll, можно избегать использование API функций и использовать напрямую его ( не помню точно где такую логику видел, репозиторий был удален( )

То, что ты пользуешься уязвимостями обусловленными Windows ( я имею ввиду защиту ) это прекрасно! Но это все очень устарело, мне интересно, что будет если я заблокирую тебе модификацию VEH списка, будет очень интересно как ты будешь использовать столь изрядный способ, это гораздо лучше, чем CrossProcessFlags ( я сейчас говорю в сторону тебя, но ты почему-то используешь устаревшие детекты ) ( MitigationPolicy->DisableExtensionPoints ). ( Credits: CozyDuke ). Да и далеко бегать не нужно, совсем недавно ( 2024 ) был слит код одного из малварей, который не давал тебе использовать CrossProcessFlags путем обычного просмотра флагов на процессе, для своей работы он изменял флаги, а после успешной работы - бэкапил их ( я лишь предоставляю пример о чем я говорю, не более того ):

Код:
Expand Collapse Copy
#include "shared.h"

int main() {
    PPEB peb = reinterpret_cast<PPEB>(__readgsqword(0x60)); // lazy, it's for x64
    DWORD crossProcessFlags = *(DWORD*)(peb->u2.CrossProcessFlags);
    if (crossProcessFlags & PROCESS_USE_VEH) {
        crossProcessFlags &= ~PROCESS_USE_VEH;
        *(DWORD*)(peb->u2.CrossProcessFlags) = crossProcessFlags;
    }
    AddVectoredExceptionHandler(TRUE,
    [](PEXCEPTION_POINTERS ExceptionInfo) -> LONG {
        return EXCEPTION_CONTINUE_EXECUTION;
    });
    crossProcessFlags |= PROCESS_USE_VEH;
    *(DWORD*)(peb->u2.CrossProcessFlags) = crossProcessFlags;
    return 0;
}
UPD:
Я прочитал твой код и увидел это ( я про свой второй ответ ):
Код:
Expand Collapse Copy
BOOLEAN is_init = list_veh->Flink == list_veh;
veh_handler_info = reinterpret_cast<VEH_HANDLER_OLD*>(VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE));
veh_handler_info->veh_filter = EncodePointer(Handler);
В общем такое чувство, что времени на исследование глубокое VEH - не хватило( Все, что было предоставление очень уж старое, такое чувство, что это слегка обновленный
Пожалуйста, авторизуйтесь для просмотра ссылки.

Тот код, что ты предоставил - изменение списка LdrpVectorHandlerList в памяти без API, это тебе поможет против crossProcessFlags, а вот DisableExtensionPoints и другие детекты к сожалению - нет. То, что ты делаешь в памяти - круто, а если посмотреть изменения в этом списке, что я обнаружу?) Уже давно есть более новые подходы ( а если то, что ты предоставил(а) это только пример, то я ничего не имею против ). VEH требует более тщательного исследования перед использованием ( первым же делом как правило, все "тру" SECURITY_MAIN_BOSS проверяют исключения и обработчики, которые создает малварь и, чтобы усложнить этот процесс твой код подходит, но реализация - устаревшая.
Красавчик, туда его ?
 
Назад
Сверху Снизу