WIN32汇编语言教程:第13章 进程控制 · 13.3 进程调试(6)

; 如果进程开始,则将入口地址处的代码改为 int 3 断点中断

;********************************************************************

             .if    stDE.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT

                     invoke WriteProcessMemory,stProcInfo.hProcess,\

                             BREAK_POINT1,addr dbInt3,1,NULL

;********************************************************************

; 如果发生断点中断,则恢复断点处代码并设置单步中断

;********************************************************************

             .elseif stDE.dwDebugEventCode == EXCEPTION_DEBUG_EVENT

                     .if     stDE.u.Exception.pExceptionRecord.ExceptionCode\

                             == EXCEPTION_BREAKPOINT

                             mov    stCT.ContextFlags,CONTEXT_FULL

                                   invoke  GetThreadContext,stProcInfo.hThread,addr stCT

                             .if    stCT.regEip == BREAK_POINT1 + 1

                             dec    stCT.regEip

                             invoke WriteProcessMemory,

                                     stProcInfo.hProcess,\

                                     BREAK_POINT1,addr dbOldByte,1,NULL

                             or     stCT.regFlag,100h

                             invoke SetThreadContext,\

                                     stProcInfo.hThread,addr stCT

                     .endif

;********************************************************************

; 如果单步中断到了指定位置,则进行内存补丁

;********************************************************************

                 .elseif stDE.u.Exception.pExceptionRecord.ExceptionCode\

                          == EXCEPTION_SINGLE_STEP

                         mov    stCT.ContextFlags,CONTEXT_FULL

                         invoke  GetThreadContext,stProcInfo.hThread,addr stCT

                         .if    stCT.regEip == BREAK_POINT2

                                 invoke WriteProcessMemory,\

                                         stProcInfo.hProcess,PATCH_POSITION,\

                                         addr dbPatched,sizeof dbPatched,NULL

                        .else

                                 or     stCT.regFlag,100h

                                 invoke SetThreadContext,\

                                         stProcInfo.hThread,addr stCT

                         .endif

                 .endif

          .endif

          invoke ContinueDebugEvent,stDE.dwProcessId,\

                 stDE.dwThreadId,DBG_CONTINUE

.endw

invoke CloseHandle,stProcInfo.hProcess

invoke CloseHandle,stProcInfo.hThread

invoke ExitProcess,NULL

;####################################################################

                 end    Start

程序不长,不到100行,这在Win32汇编程序中的规模已经是相当小了,但由于调试API使用的数据结构比较复杂,所以代码看起来比较费解,如下面的代码:

.if stDE.u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT

读者看到这里千万不要打退堂鼓,这只是4层结构的嵌套而已,接下来就会介绍它们的使用方法。(不过退一步讲,如果调试API使用起来很简单,Soft-ICE之类的软件就显示不出它们的伟大了!)

在程序的开始,还是例行公事地使用CreateProcess去创建被调试的进程,不过在创建标志中必须指定DEBUG_PROCESS标志,只有指定了这个标志,Windows才会将父进程视为子进程的调试器并让子进程进入被调试状态,如果不想子进程创建的“孙”进程也被调试的话,可以同时指定DEBUG_ONLY_THIS_PROCESS标志。

如果需要调试的进程不是由程序直接创建的,可以通过DebugActiveProcess函数让它进入调试状态:

   invoke DebugActiveProcess,dwProcessId

dwProcessId参数指定需要调试的进程ID。

2. 调试API

在用做调试器的父进程中,可以通过调试API完成下面的功能,如果愿意的话,可以使用这些功能来写一个全功能的Debug程序:

● 获取被调试程序的底层信息,如进程ID、入口地址和映像基址等。

● 当发生与调试有关的事件时获得通知,如进程或线程的开始和结束,DLL的加载和释放,以及发生各种异常等。

● 修改被调试的进程或线程。

当被调试进程发生与调试有关的事件时,Windows将目标进程的线程挂起并给调试器发送一个事件通知,调试器进程使用WaitForDebugEvent函数获取这些事件,在处理完调试事件后,可以恢复目标进程的执行并等待下一个事件的发生,例子程序就是在这样一个无限循环中使用WaitForDebugEvent函数来获取调试事件并进行处理的。

WaitForDebugEvent函数的用法是:

   invoke WaitForDebugEvent,lpDebugEvent,dwMilliseconds

dwMilliseconds指定一个以ms为单位的等待时间。如果要一直等待到某个事件发生函数才返回,可以在这里使用INFINITE值。lpDebugEvent参数指向一个DEBUG_EVENT结构,函数在这里返回调试事件的具体信息。DEBUG_EVENT结构的定义比较复杂,例子代码看上去很复杂的原因就是因为这个结构的复杂性。结构的定义为:

DEBUG_EVENT STRUCT

 dwDebugEventCode DWORD      ?     ;调试事件类型

 dwProcessId      DWORD      ?     ;发生调试事件的进程ID

 dwThreadId        DWORD      ?     ;发生调试事件的线程ID

 u                  DEBUGSTRUCT    <>    ;事件的具体信息

DEBUG_EVENT ENDS

dwDebugEventCode字段指定了发生的调试事件类型。因为可能发生的事件类型有很多种,所以程序要检查此字段并根据不同的事件做不同的处理。例子程序中设置了一个逻辑分支代码来处理EXIT_PROCESS_DEBUG_EVENT,CREATE_PROCESS_DEBUG_EVENT以及EXCEPTION_DEBUG_EVENT事件。可能发生的事件有:

● CREATE_PROCESS_DEBUG_EVENT——进程被创建。当使用CreateProcess函数刚创建被调试进程(还未开始运行),或者已经运行中的进程刚被DebugActiveProcess函数捆绑到调试器中时发生这个事件。

● EXIT_PROCESS_DEBUG_EVENT——被调试进程退出。

● CREATE_THEAD_DEBUG_EVENT——被调试进程中新建立了一个线程(但是被调试进程的主线程被创建的时候不会收到这个事件)。

● EXIT_THREAD_DEBUG_EVENT——被调试进程中某个线程结束。

● LOAD_DLL_DEBUG_EVENT——被调试进程装入一个DLL时发生本事件。当系统分析可执行文件并根据文件头中的导入表装入DLL时,程序会收到这个事件;当被调试进程使用LoadLibrary装入DLL时也会发生本事件。

● UNLOAD_DLL_DEBUG_EVENT——当一个DLL从被调试进程中卸载时发生本事件。

● EXCEPTION_DEBUG_EVENT——被调试进程中发生异常事件。被调试进程开始执行第一条指令前本事件会发生一次,以后只有在发生调试中断(遇到int 3或者单步中断)以及发生异常的时候才会发生本事件。

● OUTPUT_DEBUG_STRING_EVENT——当被调试进程调用DebugOutputString函数时发生本事件。被调试进程可以通过这种方法向调试器程序发送消息字符串。

● RIP_EVENT——系统调试发生错误。

DEBUG_EVENT 结构中的dwProcessId 和dwThreadId字段为发生调试事件的进程和线程ID。虽然使用CreateProcess创建被调试进程的时候就已经得到两个ID值,但这两个ID是属于子进程的。当没有指定DEBUG_ONLY_THIS_PROCESS标志时,调试事件可能发生在“孙”进程中,这时dwProcessId和dwThreadId字段指明的就是“孙”进程的ID。通过检测这个ID值和调用CreateProcess函数时获取的ID值是否一致,可以知道事件是发生在子进程还是孙进程中。

u字段包含了调试事件的详细信息,根据dwDebugEventCode的不同,它被定义为不同的结构,结构的名称和事件的对应关系如表13.1所示。在这里由于篇幅的关系就不列出所有的结构定义了,读者可以参考Win32 API手册。

上页:第13章 进程控制 · 13.3 进程调试(5) 下页:第13章 进程控制 · 13.3 进程调试(7)

第13章 进程控制

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号