ITEEDU

第1章 Iczelion的Win32汇编教程

第2章 Iczelion的ODBC教程

第3章 Iczelion的VxD教程

第4章 Iczelion的PE教程

第5章 罗云彬的Win32汇编教程

第6章 Win32ASM经验点滴

第7章 X86汇编语言编程

第8章 加密解密

第9章 病毒的分析和防治

第30课: Win32调试API 第三部分

在本章中,我们将继续探讨win32调试api。特别地,我们将学习如何去跟踪被调试程序.

理论:

如果你以前使用过调试器,那么你应对跟踪比较熟悉。当"跟踪"一个程序时, 程序在每执行一条指令后将会停止,这使你有机会去检查寄存器/内存中的值。这种单步运 行的官方定义为跟踪(tracing)。
单步运行的特色是由CPU本身提供的。标志寄存器的第8位称为陷阱标志trap flag。 如果该位设置,则CPU运行于单步模式。CPU将在每条指令后产生一个debug异常。当debug 异常产生后,陷阱标志自动清除。利用win32调试api,我们也可以单步运行被调试程序。 方法如下:

  1. 调用GetThreadContext, 指定 ContextFlags为CONTEXT_CONTROL, 来获得标志寄存器的值
  2. 设置CONTEXT结构成员标志寄存器regFlag中的陷阱标志位
  3. 调用 SetThreadContext
  4. 等待调式事件。被调试程序将按单步模式执行,在每执行一条指令后,我们将得到调试 事件,u.Exception.pExceptionRecord.ExceptionCode值为EXCEPTION_SINGLE_STEP
  5. 如果要跟踪下一条指令,需要再次设置陷阱标志位。

例:

                                                              

.386
.MODEL        FLAT,STDCALL
option casemap:none 
     INCLUDE  \MASM32\INCLUDE\WINDOWS.INC
     INCLUDE  \MASM32\INCLUDE\KERNEL32.INC
     INCLUDE  \MASM32\INCLUDE\COMDLG32.INC
     INCLUDE  \MASM32\INCLUDE\USER32.INC
  INCLUDELIB  \MASM32\LIB\KERNEL32.LIB
  INCLUDELIB  \MASM32\LIB\COMDLG32.LIB
  INCLUDELIB  \MASM32\LIB\USER32.LIB

.DATA
     APPNAME  DB        "WIN32 DEBUG EXAMPLE NO.4",0
ofn OPENFILENAME <> 
            FILTERSTRING  DB        "EXECUTABLE FILES",0,"*.EXE",0
              DB        "ALL FILES",0,"*.*",0,0
    EXITPROC  DB        "THE DEBUGGEE EXITS",0DH,0AH
              DB        "TOTAL INSTRUCTIONS EXECUTED : %LU",0
        TOTALINSTRUCTION  DD        0

.DATA?
      BUFFER  DB        512 DUP(?)
startinfo STARTUPINFO <> 
pi PROCESS_INFORMATION <> 
DBEvent DEBUG_EVENT <> 
context CONTEXT <> 

.CODE
      START:
              MOV       OFN.LSTRUCTSIZE,SIZEOF OFN
              MOV       OFN.LPSTRFILTER, OFFSET FILTERSTRING
              MOV       OFN.LPSTRFILE, OFFSET BUFFER
              MOV       OFN.NMAXFILE,512
              MOV       OFN.FLAGS, OFN_FILEMUSTEXIST OR OFN_PATHMUSTEXIST OR OFN_LONGNAMES OR OFN_EXPLORER OR OFN_HIDEREADONLY
              INVOKE    GETOPENFILENAME, ADDR OFN
.IF           EAX==TRUE
              INVOKE    GETSTARTUPINFO,ADDR STARTINFO
              INVOKE    CREATEPROCESS, ADDR BUFFER, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, ADDR STARTINFO, ADDR PI
.WHILE        TRUE
              INVOKE    WAITFORDEBUGEVENT, ADDR DBEVENT, INFINITE
.IF           DBEVENT.DWDEBUGEVENTCODE==EXIT_PROCESS_DEBUG_EVENT
              INVOKE    WSPRINTF, ADDR BUFFER, ADDR EXITPROC, TOTALINSTRUCTION
              INVOKE    MESSAGEBOX, 0, ADDR BUFFER, ADDR APPNAME, MB_OK+MB_ICONINFORMATION
.BREAK
.ELSEIF       DBEVENT.DWDEBUGEVENTCODE==EXCEPTION_DEBUG_EVENT .IF DBEVENT.U.EXCEPTION.PEXCEPTIONRECORD.EXCEPTIONCODE==EXCEPTION_BREAKPOINT
              MOV       CONTEXT.CONTEXTFLAGS, CONTEXT_CONTROL
              INVOKE    GETTHREADCONTEXT, PI.HTHREAD, ADDR CONTEXT
              OR        CONTEXT.REGFLAG,100H
              INVOKE    SETTHREADCONTEXT,PI.HTHREAD, ADDR CONTEXT
              INVOKE    CONTINUEDEBUGEVENT, DBEVENT.DWPROCESSID, DBEVENT.DWTHREADID, DBG_CONTINUE
.CONTINUE
.ELSEIF       DBEVENT.U.EXCEPTION.PEXCEPTIONRECORD.EXCEPTIONCODE==EXCEPTION_SINGLE_STEP
              INC       TOTALINSTRUCTION
              INVOKE    GETTHREADCONTEXT,PI.HTHREAD,ADDR CONTEXT OR CONTEXT.REGFLAG,100H
              INVOKE    SETTHREADCONTEXT,PI.HTHREAD, ADDR CONTEXT
              INVOKE    CONTINUEDEBUGEVENT, DBEVENT.DWPROCESSID, DBEVENT.DWTHREADID,DBG_CONTINUE
.CONTINUE
.ENDIF
.ENDIF
              INVOKE    CONTINUEDEBUGEVENT, DBEVENT.DWPROCESSID, DBEVENT.DWTHREADID, DBG_EXCEPTION_NOT_HANDLED
.ENDW
.ENDIF
              INVOKE    CLOSEHANDLE,PI.HPROCESS
              INVOKE    CLOSEHANDLE,PI.HTHREAD
              INVOKE    EXITPROCESS, 0
              END       START

分析:

该程序先显示一个打开文件对话框,当用户选择了一个可执行文件,它将单步执行该程序, 并记录执行的指令数,直到被调试程序退出运行。

.ELSEIF       DBEVENT.DWDEBUGEVENTCODE==EXCEPTION_DEBUG_EVENT
.IF           DBEVENT.U.EXCEPTION.PEXCEPTIONRECORD.EXCEPTIONCODE==EXCEPTION_BREAKPOINT

利用该机会来设置被调试程序为单步运行模式。记住,在执行被调试程序的第一条指令前 windows将发送一个EXCEPTION_BREAKPOINT消息。

              MOV       CONTEXT.CONTEXTFLAGS, CONTEXT_CONTROL
              INVOKE    GETTHREADCONTEXT, PI.HTHREAD, ADDR CONTEXT

调用GetThreadContext,以被调试程序的当前寄存器内容来填充CONTEXT 结构 特别地,我们需要标志寄存器的当前值。

              OR        CONTEXT.REGFLAG,100H

设置标志寄存器映象的陷阱位(第8位)

              INVOKE    SETTHREADCONTEXT,PI.HTHREAD, ADDR CONTEXT
              INVOKE    CONTINUEDEBUGEVENT, DBEVENT.DWPROCESSID, DBEVENT.DWTHREADID, DBG_CONTINUE
.CONTINUE

然后调用SetThreadContext去覆盖CONTEXT的值。再以DBG_CONTINUE调用 ContinueDebugEvent 来恢复被调试程序的运行。

.ELSEIF       DBEVENT.U.EXCEPTION.PEXCEPTIONRECORD.EXCEPTIONCODE==EXCEPTION_SINGLE_STEP
              INC       TOTALINSTRUCTION

当调试程序中一条指令执行后,我们将接收到EXCEPTION_DEBUG_EVENT的调试事件, 必须要检查u.Exception.pExceptionRecord.ExceptionCode的值。如果该值为 EXCEPTION_SINGLE_STEP,那么,该调试事件是单步运行模式造成的。在这种情况 下,TotalInstruction加一,因为我们确切地知道此时被调试程序执行了一条指令。

              INVOKE    GETTHREADCONTEXT,PI.HTHREAD,ADDR CONTEXT OR CONTEXT.REGFLAG,100H
              INVOKE    SETTHREADCONTEXT,PI.HTHREAD, ADDR CONTEXT
              INVOKE    CONTINUEDEBUGEVENT, DBEVENT.DWPROCESSID, DBEVENT.DWTHREADID,DBG_CONTINUE
.CONTINUE

由于陷阱标志在debug异常后自动清除了,如果我们需要继续保持单步运行模式,则 必须设置陷阱标志位。
警告: 不要用本教程中的此例子来调试大程序: 跟踪是很慢的。你或许需要等待10 多分钟才能关闭被调试程序。