我主要使用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
。
因此,当打开我在“随机”导出函数上设置断点的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在这两种情况下都能正常工作。
LoadLibrary
。我的调试器只能执行上面的第一个案例 如何解决这个问题?
注意:我习惯于在汇编代码中编写代码,这部分代码来自我正在开发的名为RosAsm
的汇编程序。但我无法使调试器适用于这些情况
如果有人可以使用WinAPI在C中提供此类功能的示例,那么将不胜感激。 (请不要使用C ++或.Net,因为我可以阅读C,但我无法使用.Net或C ++重现它,因为我无法阅读它)
非常感谢,提前。
答案 0 :(得分:3)
以下代码显示如何使用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中设置断点。值得注意的是,真正的调试器需要能够删除断点并恢复原始指令,以便在达到断点后继续执行程序。相反,该程序只是终止已调试的程序并退出。
答案 1 :(得分:1)
如果用户尝试为未加载的DLL设置断点,请记下它。稍后,当debugee加载DLL时,调试器循环会获得模块加载的通知。那时,它可以从它的注释中看到它有一个需要在该模块中设置的断点,并且它在恢复调试对象之前完成工作。
答案 2 :(得分:-1)
我只是成功地使用MS绕道库中的DetourCreateProcessWithDll函数使其工作。 api似乎工作正常,除了它在内部保存的内存分配指针上有一些小问题。 某些结构(DETOUR_CLR_HEADER)在原始Microsoft源代码的DetourUpdateProcessWithDll函数内被错误地指向。
原始源代码很乱,而且有点慢。因此,我必须重写整个DetourCreateProcessWithDll以尝试使其在我的调试器上工作。
到目前为止,除了源代码和调试数据同步方面的一些小问题外,它工作正常,但这似乎更容易解决。
如果有人在你自己的调试器上遇到同样的问题,而这个问题无法直接在主机通过Loadlibrary(或其他方法)调用其功能的DLL上设置断点,我建议试试MS Detours库。< / p>
我还不确定,如果我能以另一种方式克服这个问题,但是,Detour Api似乎按预期工作了。