Debugging 编写调试器。如何调试通过LoadLibrary访问的DLL?

Debugging 编写调试器。如何调试通过LoadLibrary访问的DLL?,debugging,winapi,assembly,breakpoints,createprocess,Debugging,Winapi,Assembly,Breakpoints,Createprocess,我主要使用CreateProcess编写自己的调试器,并相应地访问DEBUG\u事件结构,以加载DLL、异常、线程等设置断点(从源代码) 到目前为止,调试器还可以。当我在.EXE文件上设置断点时,以及调试调用主机作为进程目标的DLL时(类似于IDAPro所做的),一切都很好 例如:DLL包含一个名为“random”的导出,其伪代码如下: DLL名称:RND.DLL: Proc random:: mov eax 1 ; (return 1) <---- I set a breakpoi

我主要使用
CreateProcess
编写自己的调试器,并相应地访问
DEBUG\u事件
结构,以加载DLL、异常、线程等设置断点(从源代码)

到目前为止,调试器还可以。当我在.EXE文件上设置断点时,以及调试调用主机作为进程目标的DLL时(类似于IDAPro所做的),一切都很好

例如:DLL包含一个名为“random”的导出,其伪代码如下:

DLL名称:
RND.DLL

Proc random::
   mov eax 1 ; (return 1) <---- I set a breakpoint here on the dll.
EndP

因此,当加载
RND.dll
并激活调试器时,会打开一个
OpenDialog
,告诉用户选择要加载它的主机(EXE)。在这种情况下,
test.exe

因此,当打开我在“随机”导出函数上设置断点的DLL时,调试器确实会在DLL上执行时正确停止

但是……如果我的主机包含
加载库
,则调试器上的断点不会被激活。
像这样:

案例2)
不工作

EXE(主机)现在具有此伪代码。
例如:
test2.exe

Main:

call 'RND.Random' ; On a regular call to IAT the debug stops nicelly, since RND dll is part of the IAT table on the executable.
call 'KERNEL32.FreeLibrary' D$hLib
call 'Kernel32.ExitProcess' 0
Main:

call 'KERNEL32.LoadLibraryA' {'RND.DLL',0} | mov D$hLib eax
call 'kernel32.GetProcAddress' eax, { B$ "Random", 0}
call eax
call 'KERNEL32.FreeLibrary' D$hLib
call 'Kernel32.ExitProcess' 0

当我打开DLL并将断点设置为“随机”函数时,调试器不工作,因为导出的函数不是主机IAT的一部分

如何将DLL附加到主机,使调试器可以“看到”正在间接调用的DLL函数上的断点

我试图将DLL注入进程,但没有成功。
创建流程的主要功能具有以下设置:

call 'KERNEL32.CreateProcessA' DebuggeeExe,
                               CommandLineString, &NULL, &NULL, &FALSE,
                               &CREATE_DEFAULT_ERROR_MODE+&NORMAL_PRIORITY_CLASS+&DEBUG_PROCESS+&DEBUG_ONLY_THIS_PROCESS,
                               &NULL, DebuggeePath, STARTUPINFO, PROCESS_INFORMATION
如何解决这个问题?
在IDAPro上,它具有相同的功能。我的意思是我可以打开DLL,在地址上设置断点并调试它。
但在这种情况下,会打开一个对话框,告诉我选择主机(EXE)

IDAPro在这两种情况下都可以正常工作

  • 当主机(EXE)直接调用DLL时,意味着它是IAT的一部分
  • 当主机间接调用通过
    LoadLibrary
    访问的DLL时
  • 我的调试器只能执行上述第一种情况。
    如何解决这个问题

    注意:我习惯于在汇编中编写代码,这部分代码来自我正在开发的名为
    RosAsm
    的汇编程序。但我无法使调试器在这些情况下工作。
    如果有人能使用WinAPI在C中提供此类功能的示例,我们将不胜感激。(不是在C++或.NET中,因为我可以读C,但是我不能用.NET或C++来复制它,因为我不能读它)


    很多人提前感谢。

    如果用户试图为未加载的DLL设置断点,只需记下即可。稍后,当被调试者加载DLL时,调试器循环将收到模块加载的通知。当时,它可以从它的注释中看到,它有一个需要在该模块中设置的断点,并且它在恢复调试之前完成了该工作。

    我只使用MS detours库中的DetourCreateProcessWithDll函数成功地使其工作。该api似乎工作正常,只是在内部保存的内存分配指针上有一些小问题。 原始Microsoft源代码上的函数DetourUpdateProcessWithDll中的某些结构(DETOUR_CLR_标头)指向错误

    原始源代码乱七八糟,速度有点慢。因此,我必须重写整个DetourCreateProcessWithDll,以使其在我的调试器上工作

    到目前为止,除了源代码和调试数据的同步上的一些小问题外,它工作正常,但这似乎更容易修复

    如果有人在您自己的调试器上遇到同样的问题,即无法直接在主机通过Loadlibrary(或其他方法)调用其函数的DLL上设置断点,我建议尝试使用MS Detours库

    我还不确定是否可以用另一种方法克服这个问题,但是,迂回Api似乎正在按预期工作。

    如何在加载了LoadLibrary的DLL中设置真正的断点 下面的代码演示如何在使用LoadLibrary加载的DLL中使用x86断点指令INT 3设置实际断点。它处理LOAD_DLL_DEBUG_事件,并将断点指令写入加载的DLL。该命令接受两个参数,即DLL的名称和该DLL中导出的函数的名称,以在开始时设置断点。DLL的名称必须包含扩展名,但不能包含目录或驱动器号。如果程序运行,它将打印到达断点的

    #include <stdio.h>
    #include <windows.h>
    
    int
    winperror(char const *prefix) {
        DWORD errid = GetLastError();
        PVOID *buf;
        fprintf(stderr, "%s: ", prefix);
        if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM 
                  | FORMAT_MESSAGE_IGNORE_INSERTS
                  | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                  NULL, errid, 0, (LPTSTR) &buf, 0, NULL) != 0) {
            fprintf(stderr, "%s\n", (TCHAR *) buf);
        } else {
            fprintf(stderr, "unknown windows error %08lx\n", errid);
        }
        return -1;
    }
    
    static int 
    install_breakpoint(HANDLE process, DWORD_PTR addr) {
        static char const int3 = 0xcc;
        if (WriteProcessMemory(process, (LPVOID) addr, &int3, 1, NULL) == 0) {
            return winperror("WriteProcessMemory");
        }
        printf("breakpoint set at address %p\n", (void *) addr);
        return 0;
    }
    
    static int
    install_dll_breakpoint(HANDLE process, HMODULE module,
                   char const *dll, char const *function) {
        HMODULE lmodule = LoadLibrary(dll);
        if (lmodule == NULL) {
            return winperror("LoadLibrary");
        }
        void *lproc = GetProcAddress(lmodule, function);
        if (lproc == NULL) {
            return winperror("GetProcAddress");
        }
        FreeLibrary(lmodule);
        /* The debugged process might load the DLL at a different
           address than the DLL in this process, but the offset of the
           function from base of the DLL remains the same in both
           processes.
        */
        DWORD_PTR offset = (DWORD_PTR) lproc - (DWORD_PTR) lmodule;
        DWORD_PTR proc = (DWORD_PTR) module + offset;
    
        return install_breakpoint(process, proc);
    }
    
    static int
    get_file_name_from_handle(HANDLE file, char *buf, size_t len) {
        DWORD tmp[1 + 1024 / 2];
        if (GetFileInformationByHandleEx(file, FileNameInfo,
                         tmp, sizeof tmp) == 0) {
            return winperror("GetFileInformationByHandleEx");
        }
        FILE_NAME_INFO *info = (FILE_NAME_INFO *) tmp;
        int n = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
                        info->FileName, info->FileNameLength / 2,
                        buf, len - 1, NULL, NULL);
        if (n == 0) {
            return winperror("WideCharToMultiByte");
        }
        buf[n] = '\0';
        return 0;
    }
    
    int 
    main(int argc, char **argv) {
        if (argc != 3) {
            fprintf(stderr, "usage: %s dll function\n", argv[0]);
            return 1;
        }
    
        static STARTUPINFO startup;
        PROCESS_INFORMATION process_info;
    
        startup.cb = sizeof startup;
        startup.lpReserved = NULL;
        startup.lpDesktop = NULL;
        startup.lpTitle = NULL;
        startup.dwFlags = 0;
        startup.cbReserved2 = 0;
        startup.lpReserved2 = NULL;
    
        static char const rundll32[] = "rundll32";
        char buf[1024];
        if (sizeof rundll32 + 1 + strlen(argv[1]) + 1 + strlen(argv[2])
            > sizeof buf) {
            fprintf(stderr, "DLL and/or function name too long\n");
            return 1;
        }
        strcpy(buf, rundll32);
        strcat(buf, " ");
        strcat(buf, argv[1]);
        strcat(buf, ",");
        strcat(buf, argv[2]);
    
        if (CreateProcess(NULL, buf, NULL, NULL, TRUE,
                  DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, 0, NULL, 
                  &startup, &process_info) == 0) {
            winperror("CreateProcess");
            return 1;
        }
    
        HANDLE process = process_info.hProcess;
        int first_breakpoint = 1;
        while(1) {
            DWORD continue_flag = DBG_EXCEPTION_NOT_HANDLED;
            DEBUG_EVENT event;
    
            if (WaitForDebugEvent(&event, INFINITE) == 0) {
                winperror("WaitForDebugEvent");
                return 1;
            }
    
            continue_flag = DBG_EXCEPTION_NOT_HANDLED;
            switch(event.dwDebugEventCode) {
            case EXCEPTION_DEBUG_EVENT:
                EXCEPTION_DEBUG_INFO *info = &event.u.Exception;
                EXCEPTION_RECORD *exp = &info->ExceptionRecord;
                if (exp->ExceptionCode == EXCEPTION_BREAKPOINT) {
                    if (first_breakpoint) {
                        printf("PROCESS STARTED\n");
                        first_breakpoint = 0;
                        continue_flag = DBG_CONTINUE;
                    } else {
                        printf("BREAKPOINT REACHED %p\n",
                               exp->ExceptionAddress);
                        TerminateProcess(process, 0);
                        return 0;
                    }
                } 
                break;
    
            case CREATE_PROCESS_DEBUG_EVENT:
                CloseHandle(event.u.CreateProcessInfo.hFile);
                break;
    
            case EXIT_PROCESS_DEBUG_EVENT:
                printf("process exited without encoutering breakpoint"
                       " exit code = %d\n", 
                       (int) event.u.ExitProcess.dwExitCode);
                return 0;
    
            case LOAD_DLL_DEBUG_EVENT:
                HMODULE module = (HMODULE) event.u.LoadDll.lpBaseOfDll;
                HANDLE file = event.u.LoadDll.hFile;
                if (get_file_name_from_handle(file,
                                  buf, sizeof buf) == -1) {
                    return 1;
                }
                printf("LOAD_DLL   %p %s\n", module, buf);
                char *s = strrchr(buf, '\\');
                if (s == NULL) {
                    s = buf;
                } else {
                    s++;
                }
                if (stricmp(s, argv[1]) == 0
                    && install_dll_breakpoint(process, module, argv[1],
                                  argv[2]) == -1) {
                    return 1;
                }
                CloseHandle(file);
                break;
    
            case UNLOAD_DLL_DEBUG_EVENT:
                printf("UNLOAD_DLL %p\n",
                       event.u.UnloadDll.lpBaseOfDll);
                break;
            }
            if (ContinueDebugEvent(event.dwProcessId, event.dwThreadId,
                           continue_flag) == 0) {
                winperror("ContinueDebugEvent");
                return 1;
            }
        }
    }
    

    该程序仅实现最小值,以演示如何在加载了LoadLibrary的DLL中设置断点。值得注意的是,真正的调试器需要能够删除断点并恢复原始指令,以便在到达断点后程序可以继续。相反,这个程序只是终止调试过的程序并退出。

    我不明白;IAT与设置断点有什么关系?通常,调试器通过将断点处的指令替换为
    int 3
    ,来设置断点。您是否正在执行其他操作?这就是为什么WaitForDebugEvent()在加载DLL时返回LOAD\u DLL\u DEBUG\u事件的原因。让你武装断点,我知道。它确实包含所有调试_事件结构(除了尚未实现的RIP_信息),但找不到间接加载的库的正确地址。关于IAT,当模块已加载到同一进程(exe)的内存中时,似乎会触发调试器。由于可执行文件已在IAT上包含要使用的必要DLL,因此一旦使用可执行文件,它们将加载到进程中。问题取决于在运行时加载模块的时间。麦卡锡
    LOAD_DLL   76EE0000 \Windows\SysWOW64\ntdll.dll
    UNLOAD_DLL 76AE0000
    UNLOAD_DLL 766B0000
    UNLOAD_DLL 76AE0000
    UNLOAD_DLL 76C00000
    LOAD_DLL   766B0000 \Windows\SysWOW64\kernel32.dll
    LOAD_DLL   76A50000 \Windows\SysWOW64\KernelBase.dll
    LOAD_DLL   75DD0000 \Windows\SysWOW64\user32.dll
    LOAD_DLL   76890000 \Windows\SysWOW64\gdi32.dll
    LOAD_DLL   76EB0000 \Windows\SysWOW64\lpk.dll
    LOAD_DLL   767F0000 \Windows\SysWOW64\usp10.dll
    LOAD_DLL   75FD0000 \Windows\SysWOW64\msvcrt.dll
    LOAD_DLL   75D20000 \Windows\SysWOW64\advapi32.dll
    LOAD_DLL   76420000 \Windows\SysWOW64\sechost.dll
    LOAD_DLL   758B0000 \Windows\SysWOW64\rpcrt4.dll
    LOAD_DLL   749D0000 \Windows\SysWOW64\sspicli.dll
    LOAD_DLL   749C0000 \Windows\SysWOW64\cryptbase.dll
    LOAD_DLL   767C0000 \Windows\SysWOW64\imagehlp.dll
    PROCESS STARTED
    LOAD_DLL   74960000 \Windows\SysWOW64\apphelp.dll
    LOAD_DLL   6E070000 \Windows\AppPatch\AcLayers.dll
    LOAD_DLL   74C60000 \Windows\SysWOW64\shell32.dll
    LOAD_DLL   765E0000 \Windows\SysWOW64\shlwapi.dll
    LOAD_DLL   74B00000 \Windows\SysWOW64\ole32.dll
    LOAD_DLL   76380000 \Windows\SysWOW64\oleaut32.dll
    LOAD_DLL   748B0000 \Windows\SysWOW64\userenv.dll
    LOAD_DLL   748A0000 \Windows\SysWOW64\profapi.dll
    LOAD_DLL   73540000 \Windows\SysWOW64\winspool.drv
    LOAD_DLL   73510000 \Windows\SysWOW64\mpr.dll
    LOAD_DLL   58B50000 \Windows\AppPatch\acwow64.dll
    LOAD_DLL   72EC0000 \Windows\SysWOW64\version.dll
    LOAD_DLL   75F60000 \Windows\SysWOW64\imm32.dll
    LOAD_DLL   74A30000 \Windows\SysWOW64\msctf.dll
    LOAD_DLL   6EF10000 \Windows\SysWOW64\d3d9.dll
    breakpoint set at address 6EF70A62
    UNLOAD_DLL 6EF10000
    LOAD_DLL   6EF10000 \Windows\SysWOW64\d3d9.dll
    breakpoint set at address 6EF70A62
    UNLOAD_DLL 6EF10000
    LOAD_DLL   6EF10000 \Windows\SysWOW64\d3d9.dll
    breakpoint set at address 6EF70A62
    UNLOAD_DLL 6EF10000
    LOAD_DLL   6EF10000 \Windows\SysWOW64\d3d9.dll
    breakpoint set at address 6EF70A62
    LOAD_DLL   6FF20000 \Windows\SysWOW64\d3d8thk.dll
    LOAD_DLL   736F0000 \Windows\SysWOW64\dwmapi.dll
    BREAKPOINT REACHED 6EF70A62