Dream Of Flea

如何让OutputDebugString绕过调试器

DebugView是Windows下一个非常有用的调试工具,它能捕获程序通过OutputDebugString输出的调试信息。然而这是有条件的:程序不能处于被调试状态,否则调试信息会输出到调试器。有没有办法可以让OutputDebugString总是输出到DebugView,而不管程序是不是正在被调试呢?当然有。

在早期的Windows版本中有一个叫DBWin的程序,它的作用跟DebugView一样,可见在Windows中已经内置了一套OutputDebugString与DBWin通信的机制。后来不知道是什么原因,DBWin被移除了,但是那套机制还保留着,所有像DebugView那样的程序都是依赖它来工作的(下文把这类程序称为监视器)。

这套机制以四个内核对象为核心,分别是:
Mutex内核对象DBWinMutex;
FileMapping内核对象DBWIN_BUFFER;
Event内核对象DBWIN_BUFFER_READY和DBWIN_DATA_READY。
DBWinMutex是由Windows创建的,总是存在于系统中;而其它三个内核对象是由监视器创建的。

在OutputDebugString一端,首先要打开DBWinMutex并取得所有权,这一步确保了同一时刻只能有一个程序与监视器通信。然后分别打开DBWIN_BUFFER,DBWIN_BUFFER_READY和DBWIN_DATA_READY,如果有任何一个打开失败,就视为监视器不存在,不再往下执行。接下来,等待DBWIN_BUFFER_READY事件,等待成功后开始向DBWIN_BUFFER写入数据。

DBWIN_BUFFER是一个struct:
struct DBWinBuffer {
    DWORD ProcessId;
    char Data[4096 - sizeof(DWORD)];
};
Data字段就是要输出的调试信息,可见它是有长度限制的,而且字符类型并不是wchar_t,而是char。另外要注意字符串必须以0结尾。

写入完成之后触发DBWIN_DATA_READY,最后把这四个内核对象都关闭,输出就完成了。

在监视器那端,启动的时候先创建DBWIN_BUFFER,DBWIN_BUFFER_READY和DBWIN_DATA_READY,如果有任何一个创建失败,就视为已经有别的监视器启动了,退出。然后触发DBWIN_BUFFER_READY事件,接下来就在DBWIN_DATA_READY上等待,等待成功后从DBWIN_BUFFER读出调试信息。如此循环。

OutputDebugString发现程序正在被调试时不会使用这套机制,所以我们要做的就是自己去实现它。机制并不复杂,实现起来很容易,下面直接贴上示例代码了。

#include <Windows.h>

struct DBWinBuffer {
    DWORD ProcessId;
    char Data[4096 - sizeof(DWORD)];
};

void wmain() {

    //打开DBWinMutex并获取所有权
    HANDLE mutex = OpenMutex(SYNCHRONIZE, FALSE, L"DBWinMutex");

    //打开DBWIN_BUFFER
    HANDLE fileMapping = OpenFileMapping(FILE_MAP_WRITE, FALSE, L"DBWIN_BUFFER");

    //打开DBWIN_BUFFER_READY
    HANDLE bufferReadyEvent = OpenEvent(SYNCHRONIZE, FALSE, L"DBWIN_BUFFER_READY");

    //打开DBWIN_DATA_READY
    HANDLE dataReadyEvent = OpenEvent(EVENT_MODIFY_STATE , FALSE, L"DBWIN_DATA_READY");
 
    //等待DBWIN_BUFFER就绪
    WaitForSingleObject(bufferReadyEvent, INFINITE);

    //把DBWIN_BUFFER映射到某个地址
    DBWinBuffer* buffer = (DBWinBuffer*)MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0);

    //向DBWIN_BUFFER写入数据
    buffer->ProcessId = GetCurrentProcessId();
    strcpy(buffer->Data, "This is a test debug string.");

    //释放和关闭DBWIN_BUFFER
    FlushViewOfFile(buffer, 0);
    UnmapViewOfFile(buffer);
    CloseHandle(fileMapping);

    //触发DBWIN_DATA_READY
    SetEvent(dataReadyEvent);
 
    //清理
    CloseHandle(dataReadyEvent);
    CloseHandle(bufferReadyEvent);
    ReleaseMutex(mutex);
    CloseHandle(mutex);
}