大理寺少卿 发表于 2025-1-4 22:51:53

调试器原理与编写03.单步

## 单步

TF - 置位(置1复位就是置0)

#### 单步步入 -- 遇到call便入

#### !(./notesimg/1654775774500-52b256f9-1fcd-426f-9eba-d2091ca7f918.png)

#### 单步步过-- 遇到call不入

!(./notesimg/1654776004810-321e5576-3434-48fb-bafa-cfc10a038c07.png)

区分一条指令是不是call指令:    通过反汇编引擎,反汇编出来是个 call 说明 就是 call指令

#### 代码实现

```
.586
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc
   include msvcrt.inc
   include udis86.inc
   
   includelib user32.lib
   includelib kernel32.lib
   includelib msvcrt.lib
   includelib libudis86.lib
   
.data
    g_szExe db "winmine.exe", 0   ;打开的进程
    g_hExedd 0
    g_szEXCEPTION_DEBUG_EVENT         db "EXCEPTION_DEBUG_EVENT", 0dh, 0ah, 0
    g_szCREATE_THREAD_DEBUG_EVENT   db "CREATE_THREAD_DEBUG_EVENT", 0dh, 0ah, 0
    g_szCREATE_PROCESS_DEBUG_EVENT    db "CREATE_PROCESS_DEBUG_EVENT", 0dh, 0ah, 0
    g_szEXIT_THREAD_DEBUG_EVENT       db "EXIT_THREAD_DEBUG_EVENT", 0dh, 0ah, 0
    g_szEXIT_PROCESS_DEBUG_EVENT      db "EXIT_PROCESS_DEBUG_EVENT", 0dh, 0ah, 0
    g_szLOAD_DLL_DEBUG_EVENT          db "LOAD_DLL_DEBUG_EVENT", 0dh, 0ah, 0
    g_szUNLOAD_DLL_DEBUG_EVENT      db "UNLOAD_DLL_DEBUG_EVENT", 0dh, 0ah, 0
    g_szOUTPUT_DEBUG_STRING_EVENT   db "OUTPUT_DEBUG_STRING_EVENT", 0dh, 0ah, 0

    g_szLoadDllFmt   db "%08X %s", 0dh, 0ah, 0
    g_szwLoadDllFmt   dw '%', '0', '8', 'X', ' ', '%', 's', 0dh, 0ah, 0
    g_szBpFmtdb "CC异常 %08X", 0dh, 0ah, 0
    g_szSsFmtdb "单步异常 %08X", 0dh, 0ah, 0

    g_szInputCmd db "选择命令:", 0dh, 0ah
                db "是:单步步入", 0dh, 0ah
                db "否:单步步过", 0dh, 0ah
                db "取消:直接运行", 0dh, 0ah,0
            

    g_btOldCode db 0               ;下断点之前的指令
    g_dwBpAddrdd   010021a9h   ;下断点的地址
    g_byteCC   db 0CCh             ;CC指令

    g_szOutPutAsm db 64 dup(0)    ;要进行反汇编的指令

    g_szOutPutAsmFmt db "%08x %-20s %-20s", 0dh, 0ah, 0   ;指令反汇编输出格式


    g_ud_obj db 1000h dup(0)

    g_bIsCCStep dd FALSE          ;是否是CC单步

    g_bIsStepStep dd FALSE      ;是否是T命令单步
   
.code


;判断是否是 call 指令
IsCallMnproc uses esi edi pDE:ptr DEBUG_EVENT, pdwCodeLen:DWORD
    LOCAL @dwBytesOut:DWORD
    LOCAL @dwOff:DWORD
    LOCAL @pHex:LPSTR
    LOCAL @pAsm:LPSTR

    mov esi, pDE
    assume esi:ptr DEBUG_EVENT

      ;显示下一条即将执行的指令   从内存读取要反汇编指令 (20个字节)到g_szOutPutAsm
    invoke ReadProcessMemory, g_hExe, .u.Exception.pExceptionRecord.ExceptionAddress, \
      offset g_szOutPutAsm, 20, addr @dwBytesOut
   
    ;初始化结构体
    invoke ud_init, offset g_ud_obj
    invoke ud_set_input_buffer, offset g_ud_obj, offset g_szOutPutAsm, 20
    invoke ud_set_mode, offset g_ud_obj, 32
    invoke ud_set_syntax, offset g_ud_obj, offset ud_translate_intel
    invoke ud_set_pc, offset g_ud_obj, .u.Exception.pExceptionRecord.ExceptionAddress
      
    invoke ud_disassemble, offset g_ud_obj


    ;获取EIP当前指令地址
    invoke ud_insn_off, offset g_ud_obj
    mov @dwOff, eax

    ;获取机器码
    invoke ud_insn_hex, offset g_ud_obj
    mov @pHex, eax

    ;获取反汇编结果
    invoke ud_insn_asm, offset g_ud_obj
    mov @pAsm, eax
   
    ;获取指令长度
    invoke ud_insn_len, offset g_ud_obj
    mov edi, pdwCodeLen

    mov , eax

    ;输出反汇编结果
    invoke crt_printf, offset g_szOutPutAsmFmt, @dwOff, @pHex, @pAsm


    mov eax, @pAsm               ;将反汇编字符数组的首地址给eax
    .if dword ptr == 'llac';call在内存里面是 小尾方式存储
      mov eax, TRUE
      ret      
    .endif

    mov eax, FALSE
    ret

IsCallMn endp

;将TF置位(置1)
SetTF proc dwTID:DWORD
    LOCAL @hThread:HANDLE
    LOCAL @ctx:CONTEXT

    invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID
    mov @hThread, eax

    mov @ctx.ContextFlags, CONTEXT_FULL
    invoke GetThreadContext, @hThread, addr @ctx

    ;将TF置1
    or @ctx.regFlag, 100h

    invoke SetThreadContext, @hThread, addr @ctx
    invoke CloseHandle, @hThread

    ret

SetTF endp

;回退EIP
DecEIP proc dwTID:DWORD
    LOCAL @hThread:HANDLE
    LOCAL @ctx:CONTEXT

    invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID
    mov @hThread, eax

    mov @ctx.ContextFlags, CONTEXT_FULL
    invoke GetThreadContext, @hThread, addr @ctx

    dec @ctx.regEip

    invoke SetThreadContext, @hThread, addr @ctx
    invoke CloseHandle, @hThread
    ret

DecEIP endp

;设置断点
SetBp proc
    LOCAL @dwBytesOut:DWORD
    LOCAL @dwOldProc:DWORD   ;修改之前的内存属性

    ;修改内存属性
    invoke VirtualProtect, g_dwBpAddr, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc
      
    ;保存指定地址的指令,
    invoke ReadProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut

    ;在指定地址 写入cc
    invoke WriteProcessMemory, g_hExe,g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut
   
    ;还原内存属性
    invoke VirtualProtect, g_dwBpAddr, 1, @dwOldProc, addr @dwOldProc
    ret

SetBp endp

;选择指令
InputCmd proc uses esi pDE:ptr DEBUG_EVENT
    LOCAL @bIsCall:BOOL      ;是否call指令
    LOCAL @dwCodeLen:DWORD   ;指令长度

    mov esi, pDE
    assume esi:ptr DEBUG_EVENT

    ;判断是否是call指定以及获取指令长度
    invoke IsCallMn, pDE, addr @dwCodeLen
    mov @bIsCall, eax    ;保存判断结果

    invoke MessageBox, NULL, offset g_szInputCmd, NULL, MB_YESNOCANCEL
   
    .if eax == IDYES
      ;单步步入,直接TF置1
      invoke SetTF, .dwThreadId

      ;单步中需要处理T命令
      mov g_bIsStepStep, TRUE
    .elseif eax == IDNO
      ;单步步过,判断是否是call
      .if @bIsCall
            ;call指令,在下一条指令设置断点
            mov eax, .u.Exception.pExceptionRecord.ExceptionAddress      ;获取call指令地址
            add eax, @dwCodeLen   ;获取call下条指令地址call指令地址 +指令长度
            mov g_dwBpAddr, eax
            invoke SetBp            ;设置断点
      
      .else
            ;单步步入,直接TF置1
            invoke SetTF, .dwThreadId
      
            ;单步中需要处理T命令
            mov g_bIsStepStep, TRUE   
         
      .endif
      
    .else
      ;直接运行
      
    .endif
    ret

InputCmd endp

OnException proc uses esi pDE:ptr DEBUG_EVENT
    LOCAL @dwOldProc:DWORD   ;修改之前的内存属性
    LOCAL @dwBytesOut:DWORD
    mov esi, pDE
    assume esi:ptr DEBUG_EVENT

    .if .u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
      ;判断是否是自己的CC
      mov eax, .u.Exception.pExceptionRecord.ExceptionAddress
      .if eax != g_dwBpAddr
            ;不是自己的CC异常,不处理
            mov eax, DBG_EXCEPTION_NOT_HANDLED
            ret
      .endif

      ;处理自己的CC异常
      invoke crt_printf, offset g_szBpFmt, .u.Exception.pExceptionRecord.ExceptionAddress
      
      ;修改内存属性
      invoke VirtualProtect, g_dwBpAddr, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc
      
      ;恢复之前指令
      invoke WriteProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut
      
      ;还原内存属性
      invoke VirtualProtect, g_dwBpAddr, 1, @dwOldProc, addr @dwOldProc
      
      ;设置单步
      invoke SetTF, .dwThreadId
      invoke DecEIP, .dwThreadId
      
      ;单步中需要处理CC的单步
      mov g_bIsCCStep,TRUE
      
      ;输入命令
      invoke InputCmd, pDE
      
      mov eax, DBG_CONTINUE
      ret
    .endif

    ;单步来了
    .if .u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP

      ;处理自己的单步
      invoke crt_printf, offset g_szSsFmt, .u.Exception.pExceptionRecord.ExceptionAddress
      
      ;处理CC的单步
      .if g_bIsCCStep == TRUE
            mov g_bIsCCStep, FALSE
         
            ;重设断点, 重新写入CC
            ;invoke WriteProcessMemory, g_hExe,g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut
      .endif
      
      ;处理T命令的单步
      .if g_bIsStepStep == TRUE
            mov g_bIsStepStep, FALSE
         
            invoke InputCmd, pDE
      .endif
      
      mov eax, DBG_CONTINUE
      ret
    .endif

    assume esi:nothing

    mov eax, DBG_EXCEPTION_NOT_HANDLED
    ret

OnException endp

OnCreateProcess proc

    ;设置断点
    invoke SetBp

    ret

OnCreateProcess endp


main proc
    LOCAL @si:STARTUPINFO
    LOCAL @pi:PROCESS_INFORMATION
    LOCAL @de:DEBUG_EVENT
    LOCAL @dwStatus:DWORD

    invoke RtlZeroMemory, addr @si, size @si
    invoke RtlZeroMemory, addr @pi, size @pi
    invoke RtlZeroMemory, addr @de, size @de

    mov @dwStatus, DBG_CONTINUE
    ;建立调试会话
    invoke CreateProcess, NULL, offset g_szExe, NULL, NULL, FALSE, \
      DEBUG_ONLY_THIS_PROCESS,\
      NULL, NULL,\
      addr @si,\
      addr @pi
    .if !eax
      ret
    .endif
    mov eax, @pi.hProcess
    mov g_hExe, eax

    ;循环接受调试事件
    .while TRUE
      invoke WaitForDebugEvent, addr @de, INFINITE
      
      ;处理调试事件
      .if @de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
            ;invoke crt_printf, offset g_szEXCEPTION_DEBUG_EVENT
            invoke OnException, addr @de
            mov @dwStatus, eax
      .elseif @de.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT
            invoke crt_printf, offset g_szCREATE_THREAD_DEBUG_EVENT
```

反汇编头文件

udis86.inc

```
ifndef UDIS86_H

UDIS86_H equ

ud_init proto c ud:ptr
ud_set_input_buffer proto c ud:ptr, pBuf:ptr, nSize:dword
ud_set_mode proto c ud:ptr, nBit:dword
ud_set_syntax proto c ud:ptr, translate:ptr
ud_translate_intel proto c ud:ptr
ud_set_pc proto c ud:ptr, eip:dword
ud_disassemble proto c ud:ptr
ud_insn_asm proto c ud:ptr
ud_insn_len proto c ud:ptr
ud_insn_off proto c ud:ptr
ud_insn_hex proto c ud:ptr

endif
```

## 反单步

单步主要是通过 TF 置位 来实现的,所以检查单步的一般思路是检查TF 位

获取TF位的方法是获取 标志寄存器

;pushfd

;and dword ptr , 100h

;jz CONTIUE

;invoke MessageBox, NULL, NULL, NULL, MB_OK


但是通过调试发现并不能实现,应该代码执行完 TF 已经被还原为0 了此时在入栈,它的值就是0 ,所以没办法检查判断是否为1,因此要反其道行之 ,自己置个单步, 正常情况下自己可以收到一个单步异常,但处于调试状况时就无法收到,因为被调试器收了,调试器收到之后就继续往后执行

```
.586
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc
   
   includelib user32.lib
   includelib kernel32.lib


WinMain proto :DWORD,:DWORD,:DWORD,:DWORD


.data
   ClassName db "MainWinClass",0
   AppNamedb "Main Window",0

.data?
   hInstance HINSTANCE ?
   CommandLine LPSTR ?

.code


; ---------------------------------------------------------------------------


start:
        invoke GetModuleHandle, NULL
        mov    hInstance,eax

        invoke GetCommandLine
        mov    CommandLine,eax

        invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
        invoke ExitProcess,eax

;SEH异常回调函数
F0Handler proc uses esi edi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dword

    assume esi:ptr EXCEPTION_RECORD
    mov esi, pER

    mov edi, pContext
    assume edi:ptr CONTEXT

    ;跳过下条指令
    add .regEip, 5

    assume edi:nothing
    assume esi:nothing

    ret
F0Handler endp

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
        LOCAL wc:WNDCLASSEX
        LOCAL msg:MSG
        LOCAL hwnd:HWND



        assume fs:nothing
    push offset F0Handler ;handler    ;注册SEH异常
    push fs: ;next
    mov fs:, esp

        pushfd
        or dword ptr , 100h
        popfd

    ;异常被调试器收了,会继续执行下面的代码,自己异常无法收到,无法进SEH异常函数,所以无法跳过下条指令

        invoke ExitProcess, 0   ;退出进程 非单步调试情况下执行当前指令进异常,指令长度为5

        ;获取TF
        ;pushfd
        ;and dword ptr , 100h
        ;jz CONTIUE
        ;invoke MessageBox, NULL, NULL, NULL, MB_OK

;CONTIUE:
    ;正常流程,TF没有置1
      
      
      
        mov   wc.cbSize,SIZEOF WNDCLASSEX
        mov   wc.style, CS_HREDRAW or CS_VREDRAW
        mov   wc.lpfnWndProc, OFFSET WndProc
        mov   wc.cbClsExtra,NULL
        mov   wc.cbWndExtra,NULL
        pushhInstance
        pop   wc.hInstance
        mov   wc.hbrBackground,COLOR_BTNFACE+1
        mov   wc.lpszMenuName,NULL
        mov   wc.lpszClassName,OFFSET ClassName

        invoke LoadIcon,NULL,IDI_APPLICATION
        mov   wc.hIcon,eax
        mov   wc.hIconSm,eax

        invoke LoadCursor,NULL,IDC_ARROW
        mov   wc.hCursor,eax

        invoke RegisterClassEx, addr wc
        INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
         WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
         CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
         hInst,NULL
        mov   hwnd,eax

        invoke ShowWindow, hwnd,SW_SHOWNORMAL
        invoke UpdateWindow, hwnd

        .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
        .ENDW

      ;卸载SEH
      pop fs:
      add esp, 4

        mov   eax,msg.wParam
        ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

        .IF uMsg==WM_DESTROY
                invoke PostQuitMessage,NULL
        .ELSEIF uMsg==WM_CREATE
                ;
        .ELSE
                invoke DefWindowProc,hWnd,uMsg,wParam,lParam       
                ret
        .ENDIF

        xor eax,eax
        ret
WndProc endp


end start
```

应对方法,在调试器里把上面代码 nop 调,调试器本身没办法处理,因为没办法判断单步是调试器自己的还是程序的

## TRACE追踪

OD的追踪功能

!(./notesimg/1655124469931-29e1e1d2-9aa3-40c2-949e-0856589b8400.png)

也可以设置条件

!(./notesimg/1655124513586-6973ff6a-91e2-4f24-b453-45467eb7b637.png)

!(./notesimg/1655124550439-0df70600-c828-46ea-b129-9d71b7c80644.png)

查看

!(./notesimg/1655124652143-19ff6930-0bac-494d-bf11-993c80e9e7e1.png)

!(./notesimg/1655124697922-1cf8f448-0b4b-4437-b3c7-6e44495c9bf7.png)

开启trace 就在 指定范围地址直接运行 p 命令,不用提示输入指令

#### 代码实现

点击否执行trace功能

```
.586
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc
   include msvcrt.inc
   include udis86.inc
   
   includelib user32.lib
   includelib kernel32.lib
   includelib msvcrt.lib
   includelib libudis86.lib
   
.data
    g_szExe db "winmine.exe", 0
    g_hExedd 0
    g_szEXCEPTION_DEBUG_EVENT         db "EXCEPTION_DEBUG_EVENT", 0dh, 0ah, 0
    g_szCREATE_THREAD_DEBUG_EVENT   db "CREATE_THREAD_DEBUG_EVENT", 0dh, 0ah, 0
    g_szCREATE_PROCESS_DEBUG_EVENT    db "CREATE_PROCESS_DEBUG_EVENT", 0dh, 0ah, 0
    g_szEXIT_THREAD_DEBUG_EVENT       db "EXIT_THREAD_DEBUG_EVENT", 0dh, 0ah, 0
    g_szEXIT_PROCESS_DEBUG_EVENT      db "EXIT_PROCESS_DEBUG_EVENT", 0dh, 0ah, 0
    g_szLOAD_DLL_DEBUG_EVENT          db "LOAD_DLL_DEBUG_EVENT", 0dh, 0ah, 0
    g_szUNLOAD_DLL_DEBUG_EVENT      db "UNLOAD_DLL_DEBUG_EVENT", 0dh, 0ah, 0
    g_szOUTPUT_DEBUG_STRING_EVENT   db "OUTPUT_DEBUG_STRING_EVENT", 0dh, 0ah, 0

    g_szLoadDllFmt   db "%08X %s", 0dh, 0ah, 0
    g_szwLoadDllFmt   dw '%', '0', '8', 'X', ' ', '%', 's', 0dh, 0ah, 0
    g_szBpFmtdb "CC异常 %08X", 0dh, 0ah, 0
    g_szSsFmtdb "单步异常 %08X", 0dh, 0ah, 0
    g_szOutPutAsmFmt db "%08x %-20s %-20s", 0dh, 0ah, 0
    g_szInputCmd db "选择命令:", 0dh, 0ah
                db "是:单步步入", 0dh, 0ah
                db "否:单步步过", 0dh, 0ah
                db "取消:直接运行", 0dh, 0ah,0
            

    g_btOldCode db 0
    g_dwBpAddrdd   01003e21h
    g_byteCC   db 0CCh
    g_szOutPutAsm db 64 dup(0)
    g_ud_obj db 1000h dup(0)
    g_bIsCCStep dd FALSE
    g_bIsStepStep dd FALSE
    g_bIsAutoStep dd FALSE   ;是否trace追踪
   
.code

IsCallMnproc uses esi edi pDE:ptr DEBUG_EVENT, pdwCodeLen:DWORD
    LOCAL @dwBytesOut:DWORD
    LOCAL @dwOff:DWORD
    LOCAL @pHex:LPSTR
    LOCAL @pAsm:LPSTR

    mov esi, pDE
    assume esi:ptr DEBUG_EVENT

      ;显示下一条即将执行的指令
    invoke ReadProcessMemory, g_hExe, .u.Exception.pExceptionRecord.ExceptionAddress, \
      offset g_szOutPutAsm, 20, addr @dwBytesOut
    invoke ud_init, offset g_ud_obj
    invoke ud_set_input_buffer, offset g_ud_obj, offset g_szOutPutAsm, 20
    invoke ud_set_mode, offset g_ud_obj, 32
    invoke ud_set_syntax, offset g_ud_obj, offset ud_translate_intel
    invoke ud_set_pc, offset g_ud_obj, .u.Exception.pExceptionRecord.ExceptionAddress
   
    invoke ud_disassemble, offset g_ud_obj
    invoke ud_insn_off, offset g_ud_obj
    mov @dwOff, eax
    invoke ud_insn_hex, offset g_ud_obj
    mov @pHex, eax
    invoke ud_insn_asm, offset g_ud_obj
    mov @pAsm, eax
    invoke ud_insn_len, offset g_ud_obj
    mov edi, pdwCodeLen
    mov , eax

    invoke crt_printf, offset g_szOutPutAsmFmt, @dwOff, @pHex, @pAsm

    mov eax, @pAsm
    .if dword ptr == 'llac'
      mov eax, TRUE
      ret   
    .endif

    mov eax, FALSE
    ret

IsCallMn endp

SetTF proc dwTID:DWORD
    LOCAL @hThread:HANDLE
    LOCAL @ctx:CONTEXT

    invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID
    mov @hThread, eax

    mov @ctx.ContextFlags, CONTEXT_FULL
    invoke GetThreadContext, @hThread, addr @ctx

    or @ctx.regFlag, 100h

    invoke SetThreadContext, @hThread, addr @ctx
    invoke CloseHandle, @hThread

    ret

SetTF endp

DecEIP proc dwTID:DWORD
    LOCAL @hThread:HANDLE
    LOCAL @ctx:CONTEXT

    invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID
    mov @hThread, eax

    mov @ctx.ContextFlags, CONTEXT_FULL
    invoke GetThreadContext, @hThread, addr @ctx

    dec @ctx.regEip

    invoke SetThreadContext, @hThread, addr @ctx
    invoke CloseHandle, @hThread
    ret

DecEIP endp

SetBp proc
    LOCAL @dwBytesOut:DWORD

    ;保存原来的指令, 在 01001BCF写入CC
    invoke ReadProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut
    invoke WriteProcessMemory, g_hExe,g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut

    ret

SetBp endp

InputCmd proc uses esi pDE:ptr DEBUG_EVENT
    LOCAL @bIsCall:BOOL
    LOCAL @dwCodeLen:DWORD

    mov esi, pDE
    assume esi:ptr DEBUG_EVENT

    .if .u.Exception.pExceptionRecord.ExceptionAddress == 01003ebeh
      mov g_bIsAutoStep, FALSE
    .endif

    invoke IsCallMn, pDE, addr @dwCodeLen
    mov @bIsCall, eax


    ;如果开启了trace 直接执行p命令,否则提示输入指令
    .if g_bIsAutoStep != TRUE
      invoke MessageBox, NULL, offset g_szInputCmd, NULL, MB_YESNOCANCEL
   .else
         mov eax, IDNO
    .endif

    .if eax == IDYES
      ;单步步入,直接TF置1
      invoke SetTF, .dwThreadId

      ;单步中需要处理T命令
      mov g_bIsStepStep, TRUE
    .elseif eax == IDNO

      ;开启trace追踪
      mov g_bIsAutoStep, TRUE
      ;单步步过,判断是否是call
      .if @bIsCall
            ;call指令,在下一条指令设置断点
            mov eax, .u.Exception.pExceptionRecord.ExceptionAddress
            add eax, @dwCodeLen
            mov g_dwBpAddr, eax
            invoke SetBp
      .else
            ;单步步入,直接TF置1
            invoke SetTF, .dwThreadId
   
            ;单步中需要处理T命令
            mov g_bIsStepStep, TRUE   
      
      .endif
   
    .else
      ;直接运行
   
    .endif
    ret

InputCmd endp

OnException proc uses esi pDE:ptr DEBUG_EVENT
    LOCAL @dwBytesOut:DWORD
    mov esi, pDE
    assume esi:ptr DEBUG_EVENT

    .if .u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
      ;判断是否是自己的CC
      mov eax, .u.Exception.pExceptionRecord.ExceptionAddress
      .if eax != g_dwBpAddr
            ;不是自己的CC异常,不处理
            mov eax, DBG_EXCEPTION_NOT_HANDLED
            ret
      .endif

      ;处理自己的CC异常
      invoke crt_printf, offset g_szBpFmt, .u.Exception.pExceptionRecord.ExceptionAddress
   
      ;恢复指令
      invoke WriteProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut
   
      ;设置单步
      invoke SetTF, .dwThreadId
      invoke DecEIP, .dwThreadId
   
      ;单步中需要处理CC的单步
      mov g_bIsCCStep,TRUE
   
      ;输入命令
      invoke InputCmd, pDE
   
      mov eax, DBG_CONTINUE
      ret
    .endif

    ;单步来了
    .if .u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP

      ;处理自己的单步
      invoke crt_printf, offset g_szSsFmt, .u.Exception.pExceptionRecord.ExceptionAddress
   
      ;处理CC的单步
      .if g_bIsCCStep == TRUE
            mov g_bIsCCStep, FALSE
      
            ;重设断点, 重新写入CC
            ;invoke WriteProcessMemory, g_hExe,g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut
      .endif
   
      ;处理T命令的单步
      .if g_bIsStepStep == TRUE
            mov g_bIsStepStep, FALSE
      
            invoke InputCmd, pDE
      .endif
   
      mov eax, DBG_CONTINUE
      ret
    .endif

    assume esi:nothing

    mov eax, DBG_EXCEPTION_NOT_HANDLED
    ret

OnException endp

OnCreateProcess proc

    ;保存原来的指令, 在 01001BCF写入CC
    invoke SetBp

    ret

OnCreateProcess endp


main proc
    LOCAL @si:STARTUPINFO
    LOCAL @pi:PROCESS_INFORMATION
    LOCAL @de:DEBUG_EVENT
    LOCAL @dwStatus:DWORD

    invoke RtlZeroMemory, addr @si, size @si
    invoke RtlZeroMemory, addr @pi, size @pi
    invoke RtlZeroMemory, addr @de, size @de

    mov @dwStatus, DBG_CONTINUE
    ;建立调试会话
    invoke CreateProcess, NULL, offset g_szExe, NULL, NULL, FALSE, \
      DEBUG_ONLY_THIS_PROCESS,\
      NULL, NULL,\
      addr @si,\
      addr @pi
    .if !eax
      ret
    .endif
    mov eax, @pi.hProcess
    mov g_hExe, eax

    ;循环接受调试事件
    .while TRUE
      invoke WaitForDebugEvent, addr @de, INFINITE
   
      ;处理调试事件
      .if @de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
            ;invoke crt_printf, offset g_szEXCEPTION_DEBUG_EVENT
            invoke OnException, addr @de
            mov @dwStatus, eax
      .elseif @de.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT
            invoke crt_printf, offset g_szCREATE_THREAD_DEBUG_EVENT
      .elseif @de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
            ;invoke crt_printf, offset g_szCREATE_PROCESS_DEBUG_EVENT
            invoke OnCreateProcess
      .elseif @de.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT
            invoke crt_printf, offset g_szEXIT_THREAD_DEBUG_EVENT
      .elseif @de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
            invoke crt_printf, offset g_szEXIT_PROCESS_DEBUG_EVENT
      .elseif @de.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT
            ;invoke OnLoadDll, addr @de
      .elseif @de.dwDebugEventCode == UNLOAD_DLL_DEBUG_EVENT
            invoke crt_printf, offset g_szUNLOAD_DLL_DEBUG_EVENT
      .elseif @de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT
            invoke crt_printf, offset g_szOUTPUT_DEBUG_STRING_EVENT
      .endif
   
      ;提交事件处理结果
      invoke ContinueDebugEvent, @de.dwProcessId, @de.dwThreadId, @dwStatus
      invoke RtlZeroMemory, addr @de, size @de
    .endw

    ret

main endp


start:
    invoke main
```
页: [1]
查看完整版本: 调试器原理与编写03.单步