大理寺少卿 发表于 2025-1-3 22:16:43

调试器原理与编写02.一般断点与反汇编引擎

## 一般断点(软件断点)

断点的尊严

1. 断的下来
2. 走的过去
3. 下次还来

所有合格的断点都应该满足这3个要求

OD下断点实际是把指令的第一个字节改成了CC,当程序执行到CC的时候其实是抛了一个异常(EXCEPTION_BREAKPOINT),这个异常就会进入调试器里面因为处于调试状态,调试器拿到这个异常之后就会知道程序要断到这里,写了CC之后,这条指令正常的功能就被破坏了,但是这条指令正常功能中他还是应该执行的,那怎么让他走过去呢,那就是把这条指令恢复,下次再来只需要走过去之后再把它写回CC,这条指令执行完可通过 TF 标志位 来实现, (TF置1就会抛出异常(EXCEPTION_SINGLE_STEP)),

!(./notesimg/1654685616035-a9b0d3f0-66a1-43ed-8949-b4a842480e1e.png)

断步配合: 断点和单步配合实现断点下次再来

调试的时候 如果 断点下次没来就检查断步配合,如果崩了说明没有恢复,如果程序跑飞了,断点不再来,说明单步没处理好

调试器对被调试进程拥有所有的权限(除了写)

!(./notesimg/1654691491887-01798240-0603-4c86-b865-3109f5131fff.png)

第二个成员就是调试器判断异常第一个次来还是第二次

第一个成员,异常信息结构体

!(./notesimg/1654691606740-76feb84c-cdec-4b37-bddc-12c04b65cbfd.png)

### 获取寄存器环境    GetThreadContext

!(./notesimg/1654692452343-4ce8f217-9ec1-41f0-a0df-0b204bb86994.png)

第一个参数: 线程句柄   ,第二个结构体指针(结构体类型要到 vs中去看, msdn没有)

### 代码实现

以扫雷为例

```
扫雷的过程函数:
地址                  机器码         助记符
01001BC9          55                  push    ebp
01001BCA         8BEC             mov   ebp, esp
01001BCC         83EC 40      sub   esp, 40
01001BCF         8B55 0C      mov   edx, dword ptr       //在该行下断点
01001BD2         8B4D 14       mov   ecx, dword ptr
01001BD5         53                  push    ebx
01001BD6         56                  push    esi
01001BD7         33DB             xor   ebx, ebx
```

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

   include windows.inc
   include user32.inc
   include kernel32.inc
   include msvcrt.inc
   
   includelib user32.lib
   includelib kernel32.lib
   includelib msvcrt.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_btOldCode db   0         ;记录没下断点之前的字符
    g_dwBpAddrdd   01001BCFh   ;下断点的地址
    g_byteCC    db   0CCh      ; 短点符号 CC
   
.code

;处理异常信息
OnException proc uses esi pDE:ptr DEBUG_EVENT
    LOCAL @dwOldProc:DWORD   ;修改之前的内存属性
    LOCAL @dwBytesOut:DWORD   
    LOCAL @hThread:HANDLE
    LOCAL @ctx:CONTEXT

    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 OpenThread, THREAD_ALL_ACCESS, FALSE, .dwThreadId
      mov @hThread, eax
      
      ;设置结构体标志位,用来判断获取那些寄存器环境
      ; CONTEXT_FULL表示获取控制,整数,段   CONTEXT_ALL 是所有的
      mov @ctx.ContextFlags, CONTEXT_FULL
      ;获取寄存器环境
      invoke GetThreadContext, @hThread, addr @ctx
      
      ;将TF标志位置1
      or @ctx.regFlag, 100h
         
      ;返回当前代码地址@ctx.regEip 执行完CC的地址
      dec @ctx.regEip   ;减1,cc是一个字节.
      
      ;设置寄存器环境
      invoke SetThreadContext, @hThread, addr @ctx
      
      ;关闭线程句柄
      invoke CloseHandle, @hThread
      
      mov eax, DBG_CONTINUE
      ret
    .endif

    ;单步来了(如果是单步异常)
    .if .u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP
      ;处理自己的单步
      invoke crt_printf, offset g_szSsFmt, .u.Exception.pExceptionRecord.ExceptionAddress
      
         ;修改内存属性
      invoke VirtualProtect, g_dwBpAddr, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc

      ;重设断点, 重新写入CC
      invoke WriteProcessMemory, g_hExe,g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut

      ;还原内存属性
      invoke VirtualProtect, g_dwBpAddr, 1, @dwOldProc, addr @dwOldProc

      ;异常已处理,继续执行代码
      mov eax, DBG_CONTINUE
      ret
    .endif


    assume esi:nothing

    mov eax, DBG_EXCEPTION_NOT_HANDLED
    ret

OnException endp



OnCreateProcess proc
    LOCAL @dwBytesOut:DWORD
    LOCAL @dwOldProc:DWORD   ;修改之前的内存属性

    ;修改内存属性
    invoke VirtualProtect, g_dwBpAddr, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc


    ;保存原来的被下断点地址 指令到 g_btOldCode ,用于恢复
    invoke ReadProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut

    ;在 01001BCF(断点地址)写入CC
    invoke WriteProcessMemory, g_hExe,g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut


    ;还原内存属性
    invoke VirtualProtect, g_dwBpAddr, 1, @dwOldProc, addr @dwOldProc

    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

end start
```

## 反汇编引擎

https://bbs.pediy.com/thread-205590.htm

udis86官网    http://udis86.sourceforge.net/

[📎udis86.zip](/udis86.zip)

```
00513D41    8945 FC         mov   dword ptr , eax
00513D44    817D FC 4EE640B>cmp   dword ptr , BB40E64E
00513D4B    75 09         jnz   short 00513D56
00513D4D    C745 FC 4FE640B>mov   dword ptr , BB40E64F
00513D54    EB 1C         jmp   short 00513D72
00513D56    8B55 FC         mov   edx, dword ptr
00513D59    81E2 0000FFFF   and   edx, FFFF0000
00513D5F    75 11         jnz   short 00513D72
00513D61    8B45 FC         mov   eax, dword ptr
00513D64    0D 11470000   or      eax, 4711
00513D69    C1E0 10         shl   eax, 10
00513D6C    0B45 FC         or      eax, dword ptr
00513D6F    8945 FC         mov   dword ptr , eax
00513D72    8B4D FC         mov   ecx, dword ptr
00513D75    890D 04A05100   mov   dword ptr , e>
00513D7B    8B55 FC         mov   edx, dword ptr
00513D7E    F7D2            not   edx
00513D80    8915 00A05100   mov   dword ptr [__security_cookie_com>
00513D86    8BE5            mov   esp, ebp
00513D88    5D            pop   ebp
00513D89    C3            ret

```

```
#include "udis86.h"
#pragma comment(lib, "libudis86.lib")

#include <iostream>
using namespace std;

intmain()
{
        unsigned char data = {
        0x89, 0x45, 0xFC, 0x81, 0x7D, 0xFC, 0x4E, 0xE6, 0x40, 0xBB, 0x75, 0x09, 0xC7, 0x45, 0xFC, 0x4F,
        0xE6, 0x40, 0xBB, 0xEB, 0x1C, 0x8B, 0x55, 0xFC, 0x81, 0xE2, 0x00, 0x00, 0xFF, 0xFF, 0x75, 0x11,
        0x8B, 0x45, 0xFC, 0x0D, 0x11, 0x47, 0x00, 0x00, 0xC1, 0xE0, 0x10, 0x0B, 0x45, 0xFC, 0x89, 0x45,
        0xFC, 0x8B, 0x4D, 0xFC, 0x89, 0x0D, 0x04, 0xA0, 0x51, 0x00, 0x8B, 0x55, 0xFC, 0xF7, 0xD2, 0x89,
        0x15, 0x00, 0xA0, 0x51, 0x00, 0x8B, 0xE5, 0x5D, 0xC3
        };

        ud_t ud_obj;                                        //定义
        ud_init(&ud_obj);                                 //初始化
        ud_set_input_buffer(&ud_obj, data, sizeof(data));   //缓冲区来源
        ud_set_mode(&ud_obj, 32);                           //32还是64位反汇编
        ud_set_syntax(&ud_obj, UD_SYN_INTEL);               //默认语法   
        ud_set_pc(&ud_obj, 0x00513D41);                     //指令开始地址
        while (ud_disassemble(&ud_obj))                     //开始反汇编
        {
                auto nLen = ud_insn_len(&ud_obj);   //当前指令长度
                auto nOff = ud_insn_off(&ud_obj);   //EIP当前指令地址
                auto pHex = ud_insn_hex(&ud_obj);   //机器码
                auto ptr = ud_insn_ptr(&ud_obj);    //在本程序内存中的地址
                auto opr = ud_insn_opr(&ud_obj, 0);
                auto mn = ud_insn_mnemonic(&ud_obj);
                auto mn0 = ud_lookup_mnemonic(mn);
                cout << hex << nOff << " "
                        << pHex << "\t\t"
                        << ud_insn_asm(&ud_obj) << endl;            //反汇编结果
        }

        return 0;
}

```

!(./notesimg/1654698738597-0af26ff0-8025-4e15-b972-d164a227c5ce.png)

## 作业:

#### 调试器,支持一般断点功能。
页: [1]
查看完整版本: 调试器原理与编写02.一般断点与反汇编引擎