Search code examples
c++windowsdebuggingreverse-engineering

Infinite loop while debugging a thread


I'm trying to attach a Hardware breakpoint to a game process and I succed. Then I'm trying to loop through the Exceptions and wait for the one that I've put in there, which is also working fine. The problem is that after it happens it goes into infinite loop that I cannot brake. Can you advise? The reason for me doing that is that I want to stop the thread at this point, read the EAX value with the use of Context and then proceed with the process.

Header.h includes the functions that are called here and they all work fine, thus Im not including it at this point.

#include "Header.h" #include

int main() {

SetDebugPrivilege(TRUE);

DWORD dwProcessID = 0;
DWORD dwGame = 0;

printf("Looking for game process...\n");

while (dwProcessID == 0) {
    dwProcessID = GetProcessID(L"PathOfExile.exe");
    if (dwProcessID != 0)
        dwGame = 1;

    Sleep(100);
}

printf("dwProcessID = %p\n", dwProcessID);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
MODULEENTRY32 module;
module.dwSize = sizeof(MODULEENTRY32);
Module32First(snapshot, &module);

printf("PoE base address = %p\n", module.modBaseAddr);

hpChangeBreakpoint = (DWORD*)((char *)module.modBaseAddr + 0x1AAD20);

std::cout << hpChangeBreakpoint << std::endl;

//hpChangeBreakpoint = 0x013FB279;


HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessID);

BOOL bDebugging = DebugActiveProcess(dwProcessID);
printf("bDebugging = %d\n", bDebugging);


DWORD dwThreadID = GetProcessThreadID(dwProcessID);

HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadID);

CONTEXT parentCtx;

parentCtx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

if (GetThreadContext(hThread, &parentCtx))
{
    parentCtx.Dr0 = (DWORD)hpChangeBreakpoint;
    parentCtx.Dr7 = 0x00000001;

    std::cout << "GetThreadContext successful" << std::endl;

    SetThreadContext(hThread, &parentCtx);
}


DEBUG_EVENT DebugEvent;
DWORD dbgFlag = DBG_CONTINUE;
DWORD *currentHpPointerAddress = nullptr;
DWORD *maxHpPointerAddress = nullptr;
BOOLEAN bQuit = FALSE;

while (!bQuit && WaitForDebugEvent(&DebugEvent, INFINITE))
{
    dbgFlag = DBG_CONTINUE;

    switch (DebugEvent.dwDebugEventCode)
    {

    case EXCEPTION_DEBUG_EVENT:

        switch (DebugEvent.u.Exception.ExceptionRecord.ExceptionCode)
        {

        case EXCEPTION_SINGLE_STEP:
            if (DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress == (void*)hpChangeBreakpoint)
            {
                #define RESUME_FLAG 0x10000

                CONTEXT childContext;
                childContext.ContextFlags = CONTEXT_FULL;
                if (GetThreadContext(hThread, &childContext))
                {
                    childContext.EFlags |= RESUME_FLAG;
                    SetThreadContext(hThread, &childContext);
                    std::cout << "current HP: " << childContext.Ecx << std::endl;

                    currentHpPointerAddress = (DWORD*)((char *)childContext.Edi + 0x8E0);
                    maxHpPointerAddress = (DWORD*)((char *)childContext.Edi + 0x8E4);

                }

                if (GetThreadContext(hThread, &parentCtx))
                {
                    parentCtx.Dr0 = 0;
                    parentCtx.Dr7 = 0x400;
                    SetThreadContext(hThread, &parentCtx);

                    bQuit = TRUE;

                }

            }
            else
                dbgFlag = DBG_EXCEPTION_NOT_HANDLED;

            break;

        default:
            dbgFlag = DBG_EXCEPTION_NOT_HANDLED;
        }


        break;

    case LOAD_DLL_DEBUG_EVENT:
    {
        CloseHandle(DebugEvent.u.LoadDll.hFile);
        break;
    }
    case CREATE_PROCESS_DEBUG_EVENT:
    {
        CloseHandle(DebugEvent.u.CreateProcessInfo.hFile);
        break;
    }
    case EXIT_PROCESS_DEBUG_EVENT:
        break;
    default:
        __nop();
    }

    if (!ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, dbgFlag))
    {
        break;
    }

    if (bQuit)
        DebugActiveProcessStop(dwProcessID);

}


while (1)
{
    WORD currentHP = 0;
    WORD maxHP = 0;
    if (
        ReadProcessMemory(hProcess, currentHpPointerAddress, &currentHP, sizeof(currentHP), NULL) == 0
        || ReadProcessMemory(hProcess, maxHpPointerAddress, &maxHP, sizeof(maxHP), NULL) == 0
        )
    {
        printf("Failed to read memory: %u\n", GetLastError());
    }
    else {
        std::cout << "HP: " << currentHP << " / " << maxHP << std::endl;
    }

    Sleep(1000);
}

system("pause>nul");
return 0;

}

When I run it, the game works fine until the breakpoint happens, when it does, i get infinite "breakpoint" cout's spam and when I debug it, this line: if (DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress == (void*)hpChangeBreakpoint)

is always true, but the dwFirstChance flag is 1, so its always a new exception? The only thing that changes in this infinite loop is the hThread, is always different. I feel like im missing something because of my lack of knowledge, thus would appreciate any help or hints to right direction. thank you!


Solution

  • are you listen/know about Resume Flag (RF) ? you need set it for step over DrX brealpoint

    so code must be next

    #define RESUME_FLAG 0x10000
    
    CONTEXT Context = {};
    Context.ContextFlags = CONTEXT_CONTROL;// not need CONTEXT_FULL here;
    if (GetThreadContext(hThread, &Context))
    {
        Context.EFlags |= RESUME_FLAG; // !!! this line is key point
        SetThreadContext(hThread, &Context);
    }
    

    this will be work begin from win2003 or windows vista. unfortunately XP not let you set this flag in CONTEXT. so here you need remove Dr0 breakpoint for step over it ( or patch XP kernel - search for 0x003E0DD7 DWORD in ntoskrnl code and replace it to 0x003F0DD7 - this is Eflags mask - different in RESUME_FLAG )

    also let some optimization advice - you not need call OpenThread every time when EXCEPTION_DEBUG_EVENT.

    at first you already have this thread handle

    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadID);
    

    simply not close it, after you call SetThreadContext

    and exception can occur only in context of this thread, all other threads not affected by this.

    at second you never close thread handle, opened in EXCEPTION_DEBUG_EVENT - so you already have resource leaks.

    debugger got thread handles on CREATE_THREAD_DEBUG_EVENT and CREATE_PROCESS_DEBUG_EVENT and MUST close it (or just or usual maintain it and close on EXIT_THREAD_DEBUG_EVENT and EXIT_PROCESS_DEBUG_EVENT )

    you not handle LOAD_DLL_DEBUG_EVENT and as result not close file handle.

    your code have HUGE handle leaks !

    SuspendThread / ResumeThread - for what ?! absolute senseless - thread (and all threads in process) already suspended in this point


    struct CThread : LIST_ENTRY
    {
        HANDLE _hThread;
        ULONG _dwThreadId;
    
        CThread(HANDLE hThread, ULONG dwThreadId)
        {
            _hThread = hThread;
            _dwThreadId = dwThreadId;
        }
    
        ~CThread()
        {
            //CloseHandle(_hThread);// will be closed in ContinueDebugEvent
        }
    
        static CThread* get(ULONG dwThreadId, PLIST_ENTRY ThreadListHead, CThread* pHintThread)
        {
            if (pHintThread && pHintThread->_dwThreadId == dwThreadId)
            {
                return pHintThread;
            }
    
            PLIST_ENTRY entry = ThreadListHead;
    
            while ((entry = entry->Flink) != ThreadListHead)
            {
                pHintThread = static_cast<CThread*>(entry);
    
                if (pHintThread->_dwThreadId == dwThreadId)
                {
                    return pHintThread;
                }
            }
    
            return 0;//??
        }
    
        static void DeleteAll(PLIST_ENTRY ThreadListHead)
        {
            PLIST_ENTRY entry = ThreadListHead->Flink;
    
            while (entry != ThreadListHead)
            {
                CThread* pThread = static_cast<CThread*>(entry);
    
                entry = entry->Flink;
    
                delete pThread;
            }
        }
    };
    
    
    void RunDebuggerLoop()
    {
        BOOL bQuit = FALSE;
    
        LIST_ENTRY ThreadListHead = { &ThreadListHead, &ThreadListHead };
    
        CThread* pThread = 0;
    
        DEBUG_EVENT de;
    
        BOOLEAN bFirst = TRUE;
    
        while (!bQuit && WaitForDebugEvent(&de, INFINITE))
        {
            NTSTATUS status = DBG_CONTINUE;
    
            switch(de.dwDebugEventCode)
            {
            case EXCEPTION_DEBUG_EVENT:
                if (
                    !de.u.Exception.dwFirstChance 
                    || 
                    !(pThread = CThread::get(de.dwThreadId, &ThreadListHead, pThread))
                    )
                {
                    bQuit = TRUE;
                    continue;
                }
    
                status = DBG_EXCEPTION_NOT_HANDLED;
    
                switch (de.u.Exception.ExceptionRecord.ExceptionCode)
                {
                case STATUS_BREAKPOINT:
                case STATUS_WX86_BREAKPOINT:
                    if (bFirst)
                    {
                        bFirst = FALSE;
                        status = DBG_CONTINUE;
                    }
                    break;
    
                case STATUS_SINGLE_STEP:
                case STATUS_WX86_SINGLE_STEP:
                    {
                        ::CONTEXT ctx = {};
                        ctx.ContextFlags = CONTEXT_CONTROL;
                        if (GetThreadContext(pThread->_hThread, &ctx))
                        {
                            ctx.EFlags |= RESUME_FLAG;
                            SetThreadContext(pThread->_hThread, &ctx);
                        }
                    }
                    break;
    
                case STATUS_ACCESS_VIOLATION:
                    if (de.u.Exception.ExceptionRecord.NumberParameters > 1)
                    {
                        ULONG_PTR ptr = de.u.Exception.ExceptionRecord.ExceptionInformation[1];
                    }
    
                    break;                              
                }
    
                break;
    
            case CREATE_PROCESS_DEBUG_EVENT:
                CloseHandle(de.u.CreateProcessInfo.hFile);
                //CloseHandle(de.u.CreateProcessInfo.hProcess);// will be auto closed in ContinueDebugEvent
                de.u.CreateThread.hThread = de.u.CreateProcessInfo.hThread;
    
            case CREATE_THREAD_DEBUG_EVENT:
                if (pThread = new CThread(de.u.CreateThread.hThread, de.dwThreadId))
                {
                    InsertHeadList(&ThreadListHead, pThread);
                }
                break;
    
            case EXIT_THREAD_DEBUG_EVENT:
                if (pThread = CThread::get(de.dwThreadId, &ThreadListHead, pThread))
                {
                    RemoveEntryList(pThread);
                    delete pThread;
                    pThread = 0;
                }
                break;
    
            case LOAD_DLL_DEBUG_EVENT:
                CloseHandle(de.u.LoadDll.hFile);
                break;
    
            case EXIT_PROCESS_DEBUG_EVENT:
                bQuit = TRUE;
                break;
    
            case OUTPUT_DEBUG_STRING_EVENT:
            case UNLOAD_DLL_DEBUG_EVENT:
                __nop();
                break;
            default:
                __nop();
            }
    
            if (!ContinueDebugEvent(de.dwProcessId, de.dwThreadId, status))
            {
                break;
            }
        }
    
        CThread::DeleteAll(&ThreadListHead);
    }
    void Ep()
    {
        // tag by * in begin of CommandLine
        PWSTR CommandLine = GetCommandLine();
    
        if (!CommandLine || *CommandLine != '*')
        {
            // debugger case
    
            WCHAR FileName[MAX_PATH];
            if (ULONG n = GetModuleFileName(0, FileName, RTL_NUMBER_OF(FileName)))
            {
                if (n < MAX_PATH)
                {
                    PROCESS_INFORMATION pi;
                    STARTUPINFO si = { sizeof(si) };
                    PWSTR newCommandLine = (PWSTR)alloca((wcslen(CommandLine) + 2)*sizeof(WCHAR));
                    *newCommandLine = '*';
                    wcscpy(newCommandLine + 1, CommandLine);
    
                    if (CreateProcessW(FileName, newCommandLine, 0, 0, 0, DEBUG_PROCESS, 0, 0, &si, &pi))
                    {
                        CloseHandle(pi.hThread);
                        CloseHandle(pi.hProcess);
    
                        RunDebuggerLoop();
                    }
                }
            }
            ExitProcess(0);
        }
        else
        {
            // main case
    
            wcscpy(CommandLine, CommandLine + 1);
    
            OutputDebugStringA("AAAAAA\n");
    
            if (MessageBoxW(0, L"xxx", CommandLine, MB_YESNO) == IDYES)
            {
                OutputDebugStringW(L"WWWWWWWW\n");
            }
            ExitProcess(0);
        }
    }