lyyyuna 的小花园

动静中之动, by

RSS

Writing Your Own Windows Debugger - Overview

发表于 2017-04

Introduction

Debuggers are the apple of the hacker's eye. We benefit a lot from the debugger, but few of us know the principle of it.

In the book Gray Hat Python , the author has constructed a simple debugger. However, it is too simple, it is only a machine language level debugger, and can only set basic breakpoints and show CPU register information. We also want to know how to

In this Chinese blog Zplutor's, I find a excellent series which has covered most above topics. I decide to write a English blog about it, and I will turn his code into a C++ version.

Before getting started, let's make some limitations:

The modified debugger can be found here. It is only tested under Windows 10 + Visual Studio 2013.

To Start the Debuggee Program

The so-called user mode debugger is to debug the program in user mode. Windows has provided a series of open API for debugging, and they can be devided into three categories:

The first thing to do before debugging a program is to start it. On Windows, we use CreateProcess to start to program:

STARTUPINFO startupinfo = { 0 };
startupinfo.cb = sizeof(startupinfo);
PROCESS_INFORMATION processinfo = { 0 };
unsigned int creationflags = DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE;

if (CreateProcess(
    "L:\\git_up\\anotherDebugger\\anotherDebugger\\Debug\\test.exe",
    //path,
    NULL,
    NULL,
    NULL,
    FALSE,
    creationflags,
    NULL,
    NULL,
    &startupinfo,
    &processinfo) == FALSE)
{
    std::cout << "CreateProcess failed: " << GetLastError() << std::endl;
    return;
}

Debugger loop

The debugger loop is a bit like Windows GUI message loop, some operations and exceptions will stop the debuggee and send event to the debugger. We always use

DEBUG_EVENT debugEvent;
WaitForDebugEvent(&debugEvent, INFINITE)

to capture the debug event.

There are 9 debug event in total:

If the debug event has been handled correctly, then

ContinueDebugEvent(debuggeeprocessID, debuggeethreadID, DBG_CONTINUE);

to continue the debuggee process. Let's combine the above to construct the debug loop:

while (WaitForDebugEvent(&debugEvent, INFINITE) == TRUE)
{
    debuggeeprocessID = debugEvent.dwProcessId;
    debuggeethreadID = debugEvent.dwThreadId;
    if (dispatchDebugEvent(debugEvent) == TRUE)
    {
        ContinueDebugEvent(debuggeeprocessID, debuggeethreadID, FLAG.continueStatus);
    }
    else {
        break;
    }
}

bool dispatchDebugEvent(const DEBUG_EVENT & debugEvent)
{
    switch (debugEvent.dwDebugEventCode)
    {
    case CREATE_PROCESS_DEBUG_EVENT:
        // TBD
        break;

    case CREATE_THREAD_DEBUG_EVENT:
        // TBD
        break;

    case EXCEPTION_DEBUG_EVENT:
        // TBD
        break;

    case EXIT_PROCESS_DEBUG_EVENT:
        // TBD
        break;

    case EXIT_THREAD_DEBUG_EVENT:
        // TBD
        break;

    case LOAD_DLL_DEBUG_EVENT:
        // TBD
        break;

    case UNLOAD_DLL_DEBUG_EVENT:
        // TBD
        break;

    case OUTPUT_DEBUG_STRING_EVENT:
        // TBD
        break;

    case RIP_EVENT:
        // TBD
        break;

    default:
        cout << "Unknown debug event." << endl;
        return false;
        break;
    }
}

In the next part of the series, I intend to give a brief introduction about the 9 debug events.