Исходник Сканирование функции на брейкпоинты и хуки

Шалом, это мой первый тред на борде.

И решил немного доработать старый исходник, который проходится по функциям IAT (Import Address Table), проверяя их на опкоды джампа, и брейкпоинтов x32dbg (int3, int 0x3, ud2), Ordinal, кстати, он не проверяет, но можете допилить за меня

Такой приём будет очень полезен против новичков в вашем лоадере, которые любят ставить бряки куда попало без предварительного динамического анализа защиты :)

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

Вот и сам исходник:
IATScan.hpp:
#pragma once

#include <Windows.h>
#include <string>
#include <vector>
#include <iostream>

namespace Engine
{
    struct S_CorruptedFunction
    {
        std::string m_cModuleName;
        std::string m_cFunctionName;
        std::uintptr_t m_pAddress;

        S_CorruptedFunction( std::string cModule, std::string cFunc, std::uintptr_t pAddress ): m_cModuleName( std::move( cModule ) ), m_cFunctionName( std::move( cFunc ) ), m_pAddress( std::move( pAddress ) )
        {
        }
    };

    inline BYTE bInt3Breakpoint = 0xCC;
    inline BYTE bJumpOpcode = 0xE9;

    inline WORD wUd2Breakpoint = 0x0B0F;
    inline WORD wInt3Breakpoint = 0x03CD;

    inline std::vector<S_CorruptedFunction> m_cCorruptedFunctions {};

    void OutputCorruptedFunctions();
    void AddFunction( const S_CorruptedFunction& cCorruptedFunctions );
    bool IATScan();
}

IATScan.cpp:
#include "IATScan.hpp"

void Engine::OutputCorruptedFunctions()
{
    if ( m_cCorruptedFunctions.empty() )
    {
        printf( "[~] No corrupted functions found!\n" );
        return;
    }

    for ( const auto& Iterator : m_cCorruptedFunctions )
        printf( "[!] Module: %s\tFunction: %s\tAddress: 0x%p\n", Iterator.m_cModuleName.c_str(), Iterator.m_cFunctionName.c_str(), Iterator.m_pAddress );
}

void Engine::AddFunction( const S_CorruptedFunction& cCorruptedFunctions )
{
    m_cCorruptedFunctions.push_back( cCorruptedFunctions );
}

bool Engine::IATScan()
{
    LPVOID lpBaseAddress = (LPVOID) GetModuleHandle( NULL );

    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeader;
    IMAGE_OPTIONAL_HEADER pOptionalHeader;
    IMAGE_DATA_DIRECTORY pImportDirectory;
    DWORD dwStartRVA;
    PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;

    pDosHeader = (PIMAGE_DOS_HEADER) lpBaseAddress;

    if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE )
        return false;

    pNtHeader = (PIMAGE_NT_HEADERS) ( (DWORD_PTR) lpBaseAddress + pDosHeader->e_lfanew );

    if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE )
        return false;

    pOptionalHeader = pNtHeader->OptionalHeader;

    pImportDirectory = pOptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];
    dwStartRVA = pImportDirectory.VirtualAddress;

    pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR) ( (DWORD_PTR) lpBaseAddress + dwStartRVA );

    if ( pImportDescriptor == NULL )
        return false;

    DWORD dwIndex = -1;

    while ( pImportDescriptor[ ++dwIndex ].Characteristics != 0 )
    {
        PIMAGE_THUNK_DATA pOriginalFirstThunk;
        PIMAGE_THUNK_DATA pFirstThunk;

        char* pDllName = (char*) ( (DWORD_PTR) lpBaseAddress + pImportDescriptor[ dwIndex ].Name );

        HMODULE hModule = GetModuleHandleA( pDllName );

        pOriginalFirstThunk = (PIMAGE_THUNK_DATA) ( (DWORD_PTR) lpBaseAddress + pImportDescriptor[ dwIndex ].OriginalFirstThunk );
        pFirstThunk = (PIMAGE_THUNK_DATA) ( (DWORD_PTR) lpBaseAddress + pImportDescriptor[ dwIndex ].FirstThunk );

        if ( pOriginalFirstThunk == nullptr || pFirstThunk == nullptr )
            return false;

        while ( pOriginalFirstThunk->u1.AddressOfData )
        {
            if ( !( pOriginalFirstThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG ) )
            {
                PIMAGE_IMPORT_BY_NAME pImageImport = (PIMAGE_IMPORT_BY_NAME) ( (LPBYTE) lpBaseAddress + pOriginalFirstThunk->u1.AddressOfData );

                auto pFn = GetProcAddress( hModule, (LPCSTR) pImageImport->Name );

                if ( ( *(BYTE*) pFn == bInt3Breakpoint || *(BYTE*) pFn == bJumpOpcode )
                     || ( *(WORD*) pFn == wUd2Breakpoint || *(WORD*) pFn == wInt3Breakpoint ) )
                {
                    Engine::AddFunction( S_CorruptedFunction( std::string( pDllName ), std::string( pImageImport->Name ), (std::uintptr_t) pFn ) );
                }
            }

            pFirstThunk++;
            pOriginalFirstThunk++;
        }
    }

    return true;
}

Пример его использования:
Entry.cpp:
#include "IATScan.hpp"

int main()
{
    printf( "t.me/colby5engineering\n\n" );

    if ( Engine::IATScan() == true )
        Engine::OutputCorruptedFunctions();
    else
        printf( "[-] Failed to scan iat :(\n" );

    std::cin.get();
    return 0;
}

Результат использования функции:
1675872223136.png


Также забыл сказать, что функция _initialize_narrow_environment будет находится в списке, поскольку первый её байт - это 0xE9. Для неё можно спокойно сделать проверку.

Полный исходный код находится на гитхабе: https://github.com/colby57/IAT-Scanner

Всем пока!
 

kin4stat

mq-team
Всефорумный модератор
2,730
4,710
Много чего не учтено. По хорошему тут чуть ли не эмулятор прикручивать надо. Тот же long jmp игнорируется(ff 25).

Можно например сделать вообще что-нибудь типа

Код:
xor eax, eax
jno code

code:
int 3

Но против нубов спасет, да. Только по хорошему сюда еще проверку региона на PAGE GUARD прикрутить
 
  • Bug
  • Вау
Реакции: digitalpurple и Z3roKwq

colby57

Активный
Автор темы
12
147
Можно например сделать вообще что-нибудь типа
Пример неуместный

Если ты ещё не понял, то есть разница между бряком, который вписываешь ты, и бряком который ставит непосредственно сам дебаггер.
В твоём примере я вижу, что бряк вписываешь именно ты

У дебаггера есть 4 вида бряков: хардвейр в виде 4 штук, int3, ud2 и int 0x3, который как раз ты и поставил.

При выполнении данного кода ты поймаешь краш с исключением EXCEPTION_BREAKPOINT, поскольку отладчик будет обрабатывать только опкоды бряков, которые он сам выставил. Можешь конечно сам модифицировать отладчик, и вместо условного int 0x3, который ты показал можешь поменять на тот асм-код, который ты решил предоставить в своём примере, но произойдет нечто страшное))

Да и где тут вообще восстановление регистра eax? Ты просто контекст ломаешь, а если у меня в eax важный аргумент лежит с поинтером, который ты затёр?
Раз уже показываешь пример, то показывай правильный, ака:

Код:
push eax
xor eax, eax
jno code

code:
nop

pop eax

Тот же long jmp игнорируется(ff 25).
Никто не спорит, ведь в теме я упомянул как раз о том, что мой исходник это лишь пример того, как можно обнаруживать бряки или релативный джамп, и ещё упомянул, что ничего не мешает добавлять свои детекты
Но против нубов спасет, да. Только по хорошему сюда еще проверку региона на PAGE GUARD прикрутить
Опять же, ответ выше :)

Можно долго спорить о том, чего у меня не хватает в коде, и какие меры предпринимать по обходу этого. Сам я мог бы это обойти как раз 1 хардвейр бряком на любую функцию из IAT, но тем не менее я уже объяснил, что против новичков такое сработает, да и люди в основном абузят MinHook и в x86 и в x64 в наглую, где как раз в начале хукнутой функции будет установлен угадай что? Релативный джамп, т.е. первый байт у тебя будет 0xE9 :) Поэтому в любом случае даже если не улучшать детекты в моём исходнике, то детектов будет предостаточно, чтобы отсеивать новичков.
 

kin4stat

mq-team
Всефорумный модератор
2,730
4,710
При выполнении данного кода ты поймаешь краш с исключением EXCEPTION_BREAKPOINT, поскольку отладчик будет обрабатывать только опкоды бряков, которые он сам выставил.
От отладчика все же зависит. Ну и кстати этот эксепшен скипается легко
Да и где тут вообще восстановление регистра eax? Ты просто контекст ломаешь, а если у меня в eax важный аргумент лежит с поинтером, который ты затёр?
Раз уже показываешь пример, то показывай правильный, ака:
Это псевдокод все же был, а не реальный пример. Очевидно что регистр не сохранен.
 

colby57

Активный
Автор темы
12
147
От отладчика все же зависит. Ну и кстати этот эксепшен скипается легко
чего?)))
Нет, ну если ты написал свой дебаггер, который по всем параметрам превосходит x64dbg, который обновляют часто, то тут прям моё увожение, однако я повторяю тебе еще раз, x64dbg на сегодняшний момент является самым стабильным и актуальным отладчиком в юзермоде, но тем не менее, хендлить бряки, которые ставят удаленно с дллки или в самом дебаггере прописывая инт3 в ручную он не умеет, что приводит к... о боже мой!

1676102773360.png


Первая строка это то, как x64dbg сам ставит дебаггер по кнопочке F2, а другие строчки это попытки дебаггера обработать инт3, который пренадлежит не ему

Да и что значит твоё "легко скипается"? Понятное дело, что если ты свой вех поставишь, то сможешь сам проскипать, только я не понимаю к чему это мне было писать)

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

Бля, мужик..
Если пытаешься меня чему-то учить (будучи зная меньше чем я), то хотя бы выписывай правильно примеры, отговариваться по типу "это просто псевдокод" не нужно, если бы в дебаггере вместо инт3 юзался твой код, бедные реверсеры бы каждый день тебе отписывали с баг-репортом, что контекст их программы каким-то чудом умудрился сломаться...

Найдешь к чему ещё прикопаться, или закончим наш чудесный диалог?
 
  • Нравится
Реакции: F0RQU1N and

kin4stat

mq-team
Всефорумный модератор
2,730
4,710
чего?)))
Нет, ну если ты написал свой дебаггер, который по всем параметрам превосходит x64dbg, который обновляют часто, то тут прям моё увожение, однако я повторяю тебе еще раз, x64dbg на сегодняшний момент является самым стабильным и актуальным отладчиком в юзермоде, но тем не менее, хендлить бряки, которые ставят удаленно с дллки или в самом дебаггере прописывая инт3 в ручную он не умеет, что приводит к... о боже мой!

1676102773360.png


Первая строка это то, как x64dbg сам ставит дебаггер по кнопочке F2, а другие строчки это попытки дебаггера обработать инт3, который пренадлежит не ему

Да и что значит твоё "легко скипается"? Понятное дело, что если ты свой вех поставишь, то сможешь сам проскипать, только я не понимаю к чему это мне было писать)

Или можно просто Ctrl+F8 нажать. Тоже работает.

Думаю вопрос исчерпан.
Ну, раз ты решил докапываться до моей идеи, то и я буду преследовать ту же цель в твоих ответах мне

Бля, мужик..
Если пытаешься меня чему-то учить (будучи зная меньше чем я)
Давай не будем прибегать к оценке чужих знаний. Это как минимум некультурно.
Я тоже мог доебаться до каждой строки кода у тебя, но как ты видишь я этого не сделал.
если бы в дебаггере вместо инт3 юзался твой код, бедные реверсеры бы каждый день тебе отписывали с баг-репортом, что контекст их программы каким-то чудом умудрился сломаться...
Я тебе просто указал на неполноценность твоего решения. Это не general-purpose решение. В этом моя основная претензия.
Как минимум сканер сломается если брейкпоинт ставить не на первую инструкцию функции. Ну и хотя бы на ff25 проверку добавил бы.
 

Ahora57

Участник
4
29
Я тебе просто указал на неполноценность твоего решения. Это не general-purpose решение. В этом моя основная претензия.
Как минимум сканер сломается если брейкпоинт ставить не на первую инструкцию функции. Ну и хотя бы на ff25 проверку добавил бы.
Давай не будем прибегать к оценке чужих знаний -> Много чего не учтено. По хорошему тут чуть ли не эмулятор прикручивать надо.
Тот же long jmp игнорируется(ff 25) -> Ну и хотя бы на ff25 проверку добавил бы.
Простите, но это больше напоминает: "Ты должен добавить, но у меня точно нет претензий и из-за этого я прошу 2 раза добавить проверку на ff 25".

Или можно просто Ctrl+F8 нажать. Тоже работает.

Думаю вопрос исчерпан.
Я не совсем понимаю, зачем вы показали в одношаговой трассировке обработку INT3 для программы (параметры-> ядро -> пропускать INT3 при пошаговом выполнении). Да, некоторые ошибки можно пропустить (проглотить исключения/пропустить исключения).
Опять же смотрим, как тот же x64dbg обрабатывает это.
Я не хочу смотреть и продолжать этот бессмысленный спор. Давайте его закончим со следующими мыслями:
1)Это просто пример.
Давайте не будем утверждать, что те же некоторые отсталые AV хукают в DLL Api/NtApi или что нужно больше детектов.
Это сделано для новичков и мы не будем говорить 1000 раз НО.
2)Не надо доказывать, что я умнее тебя и продолжать из-за этого спор + при этом докапываться при этом к любому слову.
Как-то Frostiest сказал умную, но простую мысль ~его слова: "Вам не нужно быть гением, чтобы общаться из-за этого с гениями".