簡體   English   中英

編寫調試器。 如何調試通過LoadLibray訪問的DLL?

[英]Writing a debugger. How to debug a DLL accessed through LoadLibray?

我主要使用CreateProcess編寫了自己的調試器,並相應地訪問DEBUG_EVENT結構以加載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

問題是從LoadLibrary調用了DLL。
例如:

情況1)


調試器可以:

主機(EXE)具有此偽代碼。
EXE名稱: test.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

因此,在加載RND.dll並激活調試器時,將打開一個OpenDialog ,告訴用戶選擇要加載它的主機(EXE)。 在這種情況下為test.exe

因此,當打開我在“ Random”導出函數上設置斷點的DLL時,調試器會正確停止在DLL上的執行。

但是.....如果我的主機包含LoadLibrary ,則不會激活調試器上的斷點。
像這樣:

情況2)
不工作

EXE(主機)現在具有此偽代碼。
例如: test2.exe

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都可以正常工作。

  1. 當主機(EXE)直接調用DLL時,意味着它是IAT的一部分
  2. 當主機對通過LoadLibrary訪問的DLL進行間接調用時。

我的調試器只能執行上述第一種情況。
如何解決?

注意:我曾經在匯編中編寫代碼,這部分代碼來自我正在開發的名為RosAsm匯編器。 但是我無法使調試器在這種情況下工作。
如果有人可以使用WinAPI在C語言中提供此類功能的示例,將不勝感激。 (請不要在C ++或.Net中使用,因為我可以閱讀C,但是由於我無法閱讀,所以我無法使用.Net或C ++來復制它)

提前表示感謝。

如何在由LoadLibrary加載的DLL中設置實際斷點

以下代碼顯示了如何在使用LoadLibrary加載的DLL中使用x86斷點指令INT 3設置實際的斷點。 它處理LOAD_DLL_DEBUG_EVENT並將斷點指令寫入已加載的DLL。 該命令使用兩個參數,即DLL的名稱和該DLL中導出的函數的名稱,以在其開頭設置斷點。 DLL的名稱必須包含擴展名,但不能包含目錄或驅動器號。 如果程序有效,它將打印BREAKPOINT REACHED

#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;
        }
    }
}

要編譯程序,只需要做cl test.c 如果使用test d3d9.dll CreateDirect3D9調用它,則會看到類似以下的輸出:

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

該程序僅實現了最低限度的最低要求,以演示如何在加載了LoadLibrary的DLL中設置斷點。 值得注意的是,真正的調試器將需要能夠刪除斷點並恢復原始指令,以便可以在達到斷點后繼續執行程序。 相反,該程序僅終止調試的程序並退出。

如果用戶嘗試為未加載的DLL設置斷點,則只需記下它。 稍后,當調試對象加載DLL時,調試器循環將獲得模塊加載的通知。 那時,它可以從其注釋中看到,它需要在該模塊中設置一個斷點,並且它會在恢復調試對象之前進行工作。

我僅使用MS detours庫中的DetourCreateProcessWithDll函數使其成功工作。 該API似乎工作正常,除了內部保存的內存分配指針存在一些小問題。 在原始Microsoft源代碼上的函數DetourUpdateProcessWithDll內部錯誤地指向了某些結構(DETOUR_CLR_HEADER)。

原始源代碼很亂,有點慢。 因此,我必須重寫整個DetourCreateProcessWithDll才能使其在調試器上正常工作。

到目前為止,除了在源代碼和調試數據的同步方面存在一些小問題之外,其他所有問題都可以正常運行,但這似乎更容易解決。

如果有人在您自己的調試器上遇到了同樣的問題,即無法直接在DLL通過主機通過Loadlibrary(或其他方法)調用其功能的DLL上設置斷點,則建議嘗試使用MS Detours庫。

我不確定,是否可以通過其他方法解決該問題,但是Detour Api似乎按預期工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM