Win32调试API原理.docx
《Win32调试API原理.docx》由会员分享,可在线阅读,更多相关《Win32调试API原理.docx(23页珍藏版)》请在冰豆网上搜索。
Win32调试API原理
Win32调试API原理
在Win32中自带了一些API函数,它们提供了相当于一般调试器的大多数功能,这些函数统称为Win32调试API(Win32DebugAPI)。
利用这些API可以做到加载一个程序或捆绑到一个正在运行的程序上以供调试;可以获得被调试的程序的底层信息,例如进程ID、进入地址、映像基址等;甚至可以对被调试的程序进行任意的修改,包括进程的内存、线程的运行环境等。
简而言之,读者可以用这些API写一个进程调试器。
就像现在流行的调试器VisualC++调试器、WinDBG、OllyDbg等一样。
当然除了能写调试器外,利用调试API还能做很多不同寻常的工作。
3.1 Win32调试API原理
3.1.1 调试相关函数简要说明
Windows提供了一组Win32DebugAPI,其具体定义如下。
(1)ContinueDebugEvent函数
说明:
此函数允许调试器恢复先前由于调试事件而挂起的线程。
语法:
BOOLContinueDebugEvent(DWORD dwProcessId,DWORD dwThreadId, DWORD dwContinueStatus )
参数:
dwProcessId DWORD 被调试进程的进程标识符
dwThreadId DWORD 欲恢复线程的线程标识符
dwContinueStatus DWORD 此值指定了该线程将以何种方式继续,包含两个定义值DBG_CONTINUE和DBG_EXCEPTION_NOT_HANDLED
返回值 BOOL 如果函数成功,则返回非零值;如果失败,则返回零
(2)DebugActiveProcess
说明:
此函数允许将调试器捆绑到一个正在运行的进程上。
语法:
BOOLDebugActiveProcess(DWORD dwProcessId )
参数:
dwProcessId DWORD 欲捆绑进程的进程标识符
返回值 BOOL 如果函数成功,则返回非零值;如果失败,则返回零
(3)DebugActiveProcessStop
说明:
此函数允许将调试器从一个正在运行的进程上卸载。
语法:
BOOLDebugActiveProcessStop(DWORD dwProcessId )
参数:
dwProcessId DWORD 欲卸载的进程的进程标识符
返回值 BOOL 如果函数成功,则返回非零值;如果失败,则返回零
注意:
Windows9x内核不支持此函数。
(4)DebugBreak
说明:
在当前进程中产生一个断点异常,如果当前进程不是处在被调试状态,那么这个异常将被系统例程接管,多数情况下会导致当前进程被终止。
语法:
VOIDDebugBreak(VOID)
参数:
无
其他:
其实这个函数的用处与在程序中直接插入INT3的效果是一样的,如果反编译Windows98的KERNEL32.dll,读者可以发现这个函数只包含两句,一句是INT3,一句是RET。
(5)DebugBreakProcess
说明:
在指定进程中产生一个断点异常。
语法:
VOIDDebugBreakProcess (HANDLE hProcess)
参数:
hProcess HANDLE 进程的句柄
返回值 无
(6)FatalExit
说明:
此函数将使调用进程强制退出,将控制权转移至调试器。
与ExitProcess不同的是,在退出前会先调用一个INT3断点。
语法:
VOIDFatalExit(int ExitCode)
参数:
ExitCode int 退出码
返回值 无
(7)FlushInstructionCache
说明:
刷新指令高速缓存。
语法:
BOOLFlushInstructionCache(HANDLE hProcess, LPCVOID lpBassAddress, SIZE_T dwSize)
参数:
hProcess HANDLE 进程的句柄
lpBassAddress LPCVOID 欲刷新区域的基地址
dwSize SIZE_T 欲刷新区域的长度
返回值 BOOL 如果函数成功,则返回非零值;如果失败,则返回零
(8)GetThreadContext
说明:
获取指定线程的执行环境。
语法:
BOOLGetThreadContext(HANDLE hThread, LPCONTEXT lpContext )
参数:
hThread HANDLE 欲获取执行环境的线程的句柄
lpContext LPCONTEXT 指向CONTEXT结构的指针
返回值 BOOL 如果函数成功,则返回非零值;如果失败,则返回零
(9)GetThreadSelectorEntry
说明:
此函数返回指定选择器和线程的描述符表的入口地址。
语法:
BOOLGetThreadSelectorEntry( HANDLE hThread,DWORD dwSelector, LPLDT_ENTRY lpSelectorEntry )
参数:
hThread HANDLE 包含指定选择器的线程的句柄
dwSelector DWORD 选择器数目
lpSelectorEntry LPLDT_ENTRY 指向用来接收描述符表的结构的指针
返回值 如果函数成功,则返回非零值,此外lpSelectorEntry指向的结构中将被填入接收到的描述符表;如果失败,则返回零
(10)IsDebuggerPresent
说明:
此函数用来判断调用进程是否处于被调试环境中。
语法:
BOOLIsDebuggerPresent(VOID)
参数:
返回值 BOOL:
如果进程处在被调试状态,则返回非零值,不是处在被调试状态则返回零
(11)OutputDebugString
说明:
将一个字符串传递给调试器显示。
语法:
VOIDOutputDebugString(LPCYSTR lpOutputString)
参数:
lpOutputString LPCYSTR:
指向要显示的以“00”结尾的字符串的指针
返回值 无
(12)ReadProcessMemory
说明:
读取指定进程的某区域内的数据。
语法:
BOOLReadProcessMemory(HANDLE hProcess, LPCVOID lpBassAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T * lpNumberOfBytesRead)
参数:
hProcess HANDLE 进程的句柄
lpBassAddress LPCVOID 欲读取区域的基地址
lpBuffer LPVOID 保存读取数据的缓冲的指针
nSize SIZE_T 欲读取的字节数
lpNumberOfBytesRead SIZE_T 存储已读取字节数的地址指针
返回值 BOOL:
如果函数成功,则返回非零值;如果失败,则返回零
(13)SetThreadContext
说明:
设置指定线程的执行环境。
语法:
BOOLSetThreadContext(HANDLE hThread, LPCONTEXT lpContext )
参数:
hThread HANDLE 欲设置执行环境的线程的句柄
lpContext LPCONTEXT 指向CONTEXT结构的指针
返回值 BOOL:
如果函数成功,则返回非零值;如果失败,则返回零
(14)WaitForDebugEvent
说明:
此函数用来等待被调试进程发生调试事件。
语法:
BOOLWaitForDebugEvent(LPDEBUG_ENENT lpDebugEvent, DWORD dwMilliseconds)
参数:
lpDebugEvent LPDEBUG_ENENT 指向接收调试事件信息的DEBUG_ENENT结构的指针
dwMilliseconds DWORD 该函数用来等待调试事件发生的毫秒数,如果这段时间内没有调试事件发生,函数将返回调用者;如果将该参数指定为INFINITE,函数将一直等待直到调试事件发生
返回值 BOOL:
如果函数成功,则返回非零值;如果失败,则返回零
(15)WriteProcessMemory
说明:
在指定进程的某区域内写入数据。
语法:
BOOLWriteProcessMemory(HANDLE hProcess, LPCVOID lpBassAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T * lpNumberOfBytesRead)
参数:
hProcess HANDLE 进程的句柄
lpBassAddress LPCVOID 欲写入区域的基地址
lpBuffer LPVOID 保存欲写入数据的缓冲的指针
nSize SIZE_T 欲写入的字节数
lpNumberOfBytesRead SIZE_T 存储已写入字节数的地址的指针
返回值 BOOL:
如果函数成功,则返回非零值;如果失败,则返回零
3.1.2 调试事件
作为调试器,监视目标进程的执行、对目标进程发生的每一个调试事件做出应有的反应是它的主要工作。
当目标进程发生一个调试事件后,系统将会通知调试器来处理这个事件。
调试器将利用WaitForDebugEvent函数来获取目标进程的相关环境信息。
可能存在的调试事件类型如表3-1所示。
表3-1 调试事件
调试事件
含 义
CREATE_PROCESS_DEBUG_EVENT
进程被创建。
当调试的进程刚被创建(还未运行)或调试器开始调试已经激活的进程时,就会生成这个事件
CREATE_THEAD_DEBUG_EVENT
在调试进程中创建一个新的进程或调试器开始调试已经激活的进程时,就会生成这个调试事件。
要注意的是当调试的主线程被创建时不会收到该通知
EXCEPTION_DEBUG_EVENT
在调试的进程中出现了异常,就会生成该调试事件
EXIT_PROCESS_DEBUG_EVENT
每当退出调试进程中的最后一个线程时,产生这个事件
EXIT_THREAD_DEBUG_EVENT
调试中的线程退出时事件发生,调试的主线程退出时不会收到该通知
LOAD_DLL_DEBUG_EVENT
每当被调试的进程装载DLL文件时,就生成这个事件。
当PE装载器第一次解析出与DLL文件有关的链接时,将收到这一事件。
调试进程使用了LoadLibrary时也会发生。
每当DLL文件装载到地址空间中去时,都要调用该调试事件
OUTPUT_DEBUG_STRING_EVENT
当调试进程调用DebugOutputString函数向程序发送消息字符串时该事件发生
UNLOAD_DLL_DEBUG_EVENT
每当调试进程使用FreeLibrary函数卸载DLL文件时,就会生成该调试事件。
仅当最后一次从过程的地址空间卸载DLL文件时,才出现该调试事件(也就是说DLL文件的使用次数为0时)
RIP_EVENT
只有Windows98检查过的构件才会生成该调试事件。
该调试事件是报告错误信息
当WaitForDebugEvent接收到一个调试事件时,它将把调试事件的信息填写入DEBUG_EVENT结构中并返回。
这个结构定义如下:
viewplain
1.typedef struct _DEBUG_EVENT {
2.DWORD dwDebugEventCode;
3.DWORD dwProcessId;
4.DWORD dwThreadId;
5.union {
6.EXCEPTION_DEBUG_INFO Exception;
7.CREATE_THREAD_DEBUG_INFO CreateThread;
8.CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
9.EXIT_THREAD_DEBUG_INFO ExitThread;
10.EXIT_PROCESS_DEBUG_INFO ExitProcess;
11.LOAD_DLL_DEBUG_INFO LoadDll;
12.UNLOAD_DLL_DEBUG_INFO UnloadDll;
13.OUTPUT_DEBUG_STRING_INFO DebugString;
14.RIP_INFO RipInfo;
15.} u;
16.} DEBUG_EVENT;
在dwDebugEventCode中的值标记了所发生的调试事件的类型。
dwProcessId的值是调试事件所发生的进程的标识符。
dwThreadId的值是调试事件所发生的线程的标识符。
u结构包含了关于调试事件的更多信息,根据上面dwDebugEventCode的不同,它可以是表3-2中所示的结构。
表3-2 事件信息与u结构成员
dwDebugEventCode
u的解释
CREATE_PROCESS_DEBUG_EVENT
名为CreateProcessInfo的CREATE_PROCESS_DEBUG_INFO结构
EXIT_PROCESS_DEBUG_EVENT
名为ExitProcess的EXIT_PROCESS_DEBUG_INFO结构
CREATE_THREAD_DEBUG_EVENT
名为CreateThread的CREATE_THREAD_DEBUG_INFO结构
EXIT_THREAD_DEBUG_EVENT
名为ExitThread的EXIT_THREAD_DEBUG_EVENT结构
LOAD_DLL_DEBUG_EVENT
名为LoadDll的LOAD_DLL_DEBUG_INFO结构
UNLOAD_DLL_DEBUG_EVENT
名为UnloadDll的UNLOAD_DLL_DEBUG_INFO结构
EXCEPTION_DEBUG_EVENT
名为Exception的EXCEPTION_DEBUG_INFO结构
OUTPUT_DEBUG_STRING_EVENT
名为DebugString的OUTPUT_DEBUG_STRING_INFO结构
RIP_EVENT
名为RipInfo的RIP_INFO结构
那么如何访问这些数据呢?
假设程序调用了WaitForDebugEvent函数并返回,那么要做的第一件事就是检查dwDebugEventCode字段中的值,根据它来判断debugger进程中发生了哪种类型的调试事件。
比如说,如果dwDebugEventCode字段的值为CREATE_PROCESS_DEBUG_EVENT,就可认为u的成员为CreateProcessInfo并可通过u.CreateProcessInfo来访问。
下面是常用的CREATE_PROCESS_DEBUG_INFO结构的简要说明。
CREATE_PROCESS_DEBUG_INFO结构定义:
viewplain
1.typedef struct _CREATE_PROCESS_DEBUG_INFO {
2. HANDLE hFile; // 进程文件的句柄,利用它可对文件进行操作
3. HANDLE hProcess; // 进程的句柄,在进程空间中进行读写操作时要用到它
4. HANDLE hThread; // 主线程的句柄,在读取、设置线程环境时都要用到它
5. LPVOID lpBaseOfImage; // 进程执行的映像基地址
6. DWORD dwDebugInfoFileOffset;
7. DWORD nDebugInfoSize;
8. LPVOID lpThreadLocalBase;
9. LPTHREAD_START_ROUTINE lpStartAddress;
10. LPVOID lpImageName;
11. WORD fUnicode;
12.} CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;
3.1.3 如何在调试时创建并跟踪一个进程
这是使用Win32调试API的第一步。
可以通过以下方式创建进程。
1. 如何创建一个新进程以供调试
通过CreateProcess创建新进程时,如果在dwCreationFlags标志字段中设置了DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS标志,将创建一个用以调试的新进程。
如果是以DEBUG_PROCESS标志创建新进程,调试器将会接收到目标进程及由目标进程创建的所有子进程发生的所有调试事件,一般来说这是没有必要的。
建议可以指定DEBUG_ONLY_THIS_PROCESS和DEBUG_PROCESS的组合标志来禁止它,如果设置了DEBUG_ONLY_THIS_PROCESS标志,调试器将只会收到目标进程的调试事件,而对其子进程的调试事件不予理睬。
当进程创建成功后,可以通过查看PROCESS_INFORMATION结构来获取被创建进程及其主线程的进程标识符和线程标识符。
由于操作系统将调试对象标记为在特殊模式下运行,所以,可以使用IsDebuggerPresent函数查看进程是否在调试器下运行。
2. 如何将调试器捆绑到一个正在运行的进程上
利用DebugActiveProcess函数可以将调试器捆绑到一个正在运行的进程上,如果执行成功,则效果类似于利用DEBUG_ONLY_THIS_PROCESS标志创建的新进程。
要注意的是,在NT内核下当试图通过DebugActiveProcess函数将调试器捆绑到一个创建时带有安全描述符的进程上时,将被拒绝。
在Windows9x中则简单得多,只有当指定了一个无效的进程标识符时调用才会失败。
所以看上去NT内核的系统要更安全。
将调试器捆绑到一个进程上一般是比较好的一种做法,但是有时除了利用CreateProcess函数来载入进程外没有其他的办法。
那么到底该用哪种方法呢?
这涉及读者将要进行的工作。
比如要做一个简单的游戏修改器时,用临时捆绑调试器的方法可能比较好,如果要做一些不同寻常的工作的话,利用载入的方法可能更好,因为它获得目标进程及其线程的所有控制权,这样就可以为所欲为了。
3.1.4 调试循环体
用调试API建立一个简单的调试程序是非常简单的,所有要做的只是创建一个用来调试的新进程,然后执行相关代码来监视所有的调试事件。
笔者把监视所有的调试事件的这部分代码称为“调试循环体”,为什么呢?
因为它的实现非常简单,看上去就像一个“while”循环,所需要做的只是使用WaitForDebugEvent和ContinueDebugEvent函数。
就像上面说的,WaitForDebugEvent在一段时间内等待目标进程中调试事件的发生,如果在这段时间没有调试事件发生,那么函数将返回FALSE。
如果在指定时间内调试事件发生了,那么函数将返回TRUE,并且它会把所发生的调试事件及其相关信息填写入一个DEBUG_EVENT结构。
然后调试器会检查这些信息,并据此做出相应的反应。
在对这些事件做出相应的操作后,就可以使用ContinueDebugEvent函数来恢复线程的执行,并等待下一个调试事件的发生。
要注意的一点是WaitForDebugEvent只能使用在创建的或是捆绑上的进程中的某个线程上。
WaitForDebugEvent–ContinueDebugEvent循环的C语言示例:
viewplain
1.PROCESS_INFORMATION pi;
2.STARTUP_INFO si;
3.DEBUG_EVENT devent;
4.If (CreateProcess( 0 , "target.exe" , 0 , 0 ,FALSE ,DEBUG_ONLY_THIS_PROCESS , 0 ,0 ,&si , π))
5.{
6. while(TRUE)
7. {
8. if (WaitForDebugEvent( &devent , 150)) //在150毫秒内等待调试事件
9. {
10. switch (devent.dwDebugEventCode)
11.{
12.case CREATE_PROCESS_DEBUG_EVENT:
13.//在此填入你的处理程序
14.break;
15.case EXIT_PROCESS_DEBUG_EVENT:
16.//在此填入你的处理程序
17.break;
18.case EXCEPTION_DEBUG_EVENT:
19.//在此填入你的处理程序
20.break;
21.}
22. ContinueDebugEvent(devent.dwProcessId , devent.dwThreadId , DBG_CONTINUE);
23.