Debugging 编写windows调试器,读/写进程内存与文件映射

Debugging 编写windows调试器,读/写进程内存与文件映射,debugging,winapi,file-mapping,Debugging,Winapi,File Mapping,我想知道调试器读取调试对象内存的“正确”方法是什么。由于调试器提供了查看和更改被调试器对象内存的功能,用户可以修改代码。调试器经常读取代码段以生成反汇编,而且我们应该能够检测到跳转到指令中间并再次反汇编 虽然ReadProcessMemory和WriteProcessMemory似乎适合于读写数据,但我认为对代码使用文件映射会更容易,因此调试器将能够像在自己的地址空间中一样处理代码。此外,我们需要在反汇编输出中插入导入/导出的函数名,为此,我们需要遍历图像导入/导出目录(这里的文件映射更方便)

我想知道调试器读取调试对象内存的“正确”方法是什么。由于调试器提供了查看和更改被调试器对象内存的功能,用户可以修改代码。调试器经常读取代码段以生成反汇编,而且我们应该能够检测到跳转到指令中间并再次反汇编

虽然
ReadProcessMemory
WriteProcessMemory
似乎适合于读写数据,但我认为对代码使用文件映射会更容易,因此调试器将能够像在自己的地址空间中一样处理代码。此外,我们需要在反汇编输出中插入导入/导出的函数名,为此,我们需要遍历图像导入/导出目录(这里的文件映射更方便)

但有一个问题
MapViewOffilex(…,ImageBase)
将失败,因为ImageBase由Windows使用(它是加载程序创建的未命名
SEC_IMAGE
部分的开始)。我们可以选择使用
MapViewOfFile
和复制映像,但是,如果可执行文件很大(如100Mb Nvidia驱动程序),它将从一些堆中抢走调试对象

//--------------------------------------------------------------------------
// debugger
//--------------------------------------------------------------------------

struct MY_PROCESS_INFORMATION
{
    HANDLE hProcess;
    DWORD dwProcessId;
};

struct FILEMAP_INFORMATION
{
    HANDLE hFileMap;
    void* MapAddr;
};

struct INJECT_INFORMATION
{
    HMODULE hModule;
    BOOL Success;
};

struct MODULE_INFORMATION
{
    ptrdiff_t BaseDelta;
    void* AddressOfEntryPoint;
};

BOOL DbgLoad(HANDLE hProcess, char* DllName, INJECT_INFORMATION* InjectInfo)
{
    BOOL Loaded;
    size_t Size;
    void* pAlloc;
    HMODULE hModule;
    HANDLE hNewThread;
    LPTHREAD_START_ROUTINE Start;
    HANDLE hEvent;

    if (InjectInfo)
    {
        hEvent = CreateEventA(NULL, FALSE, FALSE, "dbg_event");

        if (!hEvent) return FALSE;
    }

    Loaded = FALSE;
    hModule = GetModuleHandleA("kernel32.dll");

    if (hModule)
    {
        Start = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA");

        if (Start)
        {
            Size = strlen(DllName) + sizeof(char);
            pAlloc = VirtualAllocEx(hProcess, NULL, Size, MEM_COMMIT, PAGE_READWRITE);

            if (pAlloc)
            {
                if (WriteProcessMemory(hProcess, pAlloc, DllName, Size, NULL))
                {
                    hNewThread = CreateRemoteThread(hProcess, NULL, 0, Start, pAlloc, 0, NULL);

                    if (hNewThread)
                    {
                        if (InjectInfo)
                        {
                            if (!WaitForSingleObject(hEvent, INFINITE)) InjectInfo->Success = TRUE;
                            else InjectInfo->Success = FALSE;

                            if (!WaitForSingleObject(hNewThread, INFINITE))
                            {
#ifdef _WIN32
                                if (GetExitCodeThread(hNewThread, (DWORD*)&hModule))
                                {
                                    if (hModule)
                                    {
                                        InjectInfo->hModule = hModule;
                                        Loaded = TRUE;
                                    }
                                }
#else
                                //...
#endif
                            }
                        }
                        else
                        {
                            if (!WaitForSingleObject(hNewThread, INFINITE)) Loaded = TRUE;
                        }

                        CloseHandle(hNewThread);
                    }
                }

                if (InjectInfo) VirtualFreeEx(hProcess, pAlloc, 0, MEM_DECOMMIT);
            }
        }
    }

    if (InjectInfo) CloseHandle(hEvent);

    return Loaded;
}

BOOL DbgMap(MY_PROCESS_INFORMATION* ProcessInfo, FILEMAP_INFORMATION* FileMapInfo)
{
    HANDLE hFileMap;
    void* MapAddr;
    INJECT_INFORMATION InjectInfo;

    if (DbgLoad(ProcessInfo->hProcess, "D:\\Data\\New\\DllMap\\Debug\\DllMap.dll", &InjectInfo))
    {
        if (InjectInfo.Success)
        {
            hFileMap = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, "file_map");

            if (hFileMap)
            {
                MapAddr = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

                if (MapAddr)
                {
                    FileMapInfo->hFileMap = hFileMap;
                    FileMapInfo->MapAddr = MapAddr;

                    return TRUE;
                }

                CloseHandle(hFileMap);
            }
        }

        // unload...
    }

    return FALSE;
}

BOOL DbgGetModuleInfo(FILEMAP_INFORMATION* FileMapInfo, MY_PROCESS_INFORMATION* ProcessInfo, MODULE_INFORMATION* ModuleInfo)
{
    BYTE* ImageBase;
    BYTE* __ImageBase;
    ptrdiff_t BaseDelta;
    BYTE* AddressOfEntryPoint;

    ImageBase = (BYTE*)FileMapInfo->MapAddr;
    assert(((PIMAGE_DOS_HEADER)ImageBase)->e_magic == IMAGE_DOS_SIGNATURE);

    AddressOfEntryPoint = ImageBase + GetImageEntryRVA(ImageBase);

    // BaseDelta:
    // 1) determine whether we have 32bit or 64bit image (using ImageBase)
    // 2) NtQueryInformationProcess to get PEB
    // 3) use PEB32 or PEB64 (depends on 1st step) to get __ImageBase
    // 4) BaseDelta = ImageBase - __ImageBase

    // parse imports and exports (using ImageBase)...

    return TRUE;
}

//--------------------------------------------------------------------------
// DllMap
//--------------------------------------------------------------------------

HANDLE g_hFileMap;
void* g_MapAddr;

DWORD GetImageSize(void* Image)
{
    PIMAGE_NT_HEADERS header;

    header = (PIMAGE_NT_HEADERS)((BYTE*)Image + ((PIMAGE_DOS_HEADER)Image)->e_lfanew);

    return header->OptionalHeader.SizeOfImage;
}

void* HModuleToAddr(HMODULE hModule)
{
    return (void*)((size_t)hModule & ~(HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE));
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    HANDLE hEvent;
    HMODULE hExe;
    void* ImageBase;
    DWORD ImageSize;

    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        hEvent = OpenEventA(EVENT_MODIFY_STATE, FALSE, "dbg_event");

        if (hEvent)
        {
            hExe = GetModuleHandleA(NULL);

            if (hExe)
            {
                ImageBase = HModuleToAddr(hExe);
                ImageSize = GetImageSize(ImageBase);

                g_hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, ImageSize, "file_map");

                if (g_hFileMap)
                {
                    g_MapAddr = MapViewOfFile(g_hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

                    if (g_MapAddr)
                    {
                        memcpy(g_MapAddr, ImageBase, ImageSize);
                        SetEvent(hEvent);
                    }
                }
            }

            CloseHandle(hEvent);
        }

        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        if (!lpReserved)        // FreeLibrary
        {
            if (g_hFileMap)
            {
                if (g_MapAddr)
                {
                    UnmapViewOfFile(g_MapAddr);
                    g_MapAddr = 0;
                }

                CloseHandle(g_hFileMap);
                g_hFileMap = 0;
            }
        }
        break;
    }

    return TRUE;
}
然后我们这样做:

if (DbgMap(&ProcessInfo, &FileMapInfo))
{
    if (DbgAttach(&ProcessInfo))
    {
        if (DbgGetModuleInfo(&FileMapInfo, &ProcessInfo, &ModuleInfo))
        {
            // Good
        }
    }
}
请注意,当我们创建debuggee进程时,我们在没有调试标志的挂起状态下创建它(否则我们将无法运行LoadLibrary线程来执行文件映射)。创建或打开debuggee进程并执行映射后,我们将附加调试器(在创建debuggee进程的情况下,我们还需要在其主线程上调用ResumeThread)


我以前从未写过调试器。所以我的问题是,我们应该使用文件映射还是
ReadProcessMemory/WriteProcessMemory
(用于数据、代码、导入、导出等)?我的方法是合理的还是胡说八道?谢谢。

调试器内置于系统中,从kernel32.dll导出。你为什么不用这个?提供了一个(有些过时)的介绍。文件映射无法工作,因为被调试者没有习惯性地映射其地址空间。本文很有帮助,但没有与此问题相关的信息。我想做一些准备,这样我就可以在debuggee的代码上运行反汇编程序,并询问使用ReadProcessMemory获取代码并反汇编代码是否合理,或者我们应该使用一些文件映射技巧(就像OS loader可能做的那样)。由于映像驻留在加载程序创建的SEC_映像节中,我们可能能够在不支持调试的情况下映射此节的视图。@IInspectable-系统确实有内置调试接口,但不在
kernel32.dll
-在
dbgeng.dll
-ReadProcessMemory具有实际解决问题的明显优势。内存映射没有。调试器内置于系统中,从kernel32.dll导出。你为什么不用这个?提供了一个(有些过时)的介绍。文件映射无法工作,因为被调试者没有习惯性地映射其地址空间。本文很有帮助,但没有与此问题相关的信息。我想做一些准备,这样我就可以在debuggee的代码上运行反汇编程序,并询问使用ReadProcessMemory获取代码并反汇编代码是否合理,或者我们应该使用一些文件映射技巧(就像OS loader可能做的那样)。由于映像驻留在加载程序创建的SEC_映像节中,我们可能能够在不支持调试的情况下映射此节的视图。@IInspectable-系统确实有内置调试接口,但不在
kernel32.dll
-在
dbgeng.dll
-ReadProcessMemory具有实际解决问题的明显优势。内存映射不起作用。