调试器原理与编写03.单步-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net

单步

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

单步步入 -- 遇到call便入

单步步过 -- 遇到call不入

区分一条指令是不是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_hExe dd 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_szBpFmt db "CC异常 %08X", 0dh, 0ah, 0
g_szSsFmt db "单步异常 %08X", 0dh, 0ah, 0 g_szInputCmd db "选择命令:", 0dh, 0ah
db "是:单步步入", 0dh, 0ah
db "否:单步步过", 0dh, 0ah
db "取消:直接运行", 0dh, 0ah,0 g_btOldCode db 0 ;下断点之前的指令
g_dwBpAddr dd 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 指令
IsCallMn proc 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, [esi].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, [esi].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 [edi], eax ;输出反汇编结果
invoke crt_printf, offset g_szOutPutAsmFmt, @dwOff, @pHex, @pAsm mov eax, @pAsm ;将反汇编字符数组的首地址给eax
.if dword ptr [eax] == '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, [esi].dwThreadId ;单步中需要处理T命令
mov g_bIsStepStep, TRUE
.elseif eax == IDNO
;单步步过,判断是否是call
.if @bIsCall
;call指令,在下一条指令设置断点
mov eax, [esi].u.Exception.pExceptionRecord.ExceptionAddress ;获取call指令地址
add eax, @dwCodeLen ;获取call下条指令地址 call指令地址 + 指令长度
mov g_dwBpAddr, eax
invoke SetBp ;设置断点 .else
;单步步入,直接TF置1
invoke SetTF, [esi].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 [esi].u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
;判断是否是自己的CC
mov eax, [esi].u.Exception.pExceptionRecord.ExceptionAddress
.if eax != g_dwBpAddr
;不是自己的CC异常,不处理
mov eax, DBG_EXCEPTION_NOT_HANDLED
ret
.endif ;处理自己的CC异常
invoke crt_printf, offset g_szBpFmt, [esi].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, [esi].dwThreadId
invoke DecEIP, [esi].dwThreadId ;单步中需要处理CC的单步
mov g_bIsCCStep, TRUE ;输入命令
invoke InputCmd, pDE mov eax, DBG_CONTINUE
ret
.endif ;单步来了
.if [esi].u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP ;处理自己的单步
invoke crt_printf, offset g_szSsFmt, [esi].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 [esp], 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
AppName db "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 [edi].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:[0] ;next
mov fs:[0], esp pushfd
or dword ptr [esp], 100h
popfd ;异常被调试器收了,会继续执行下面的代码,自己异常无法收到,无法进SEH异常函数,所以无法跳过下条指令 invoke ExitProcess, 0 ;退出进程 非单步调试情况下执行当前指令进异常,指令长度为5 ;获取TF
;pushfd
;and dword ptr [esp], 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
push hInstance
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:[0]
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的追踪功能

也可以设置条件

查看

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

Windows平台调试器原理与编写03.单步的更多相关文章

  1. 手把手教你写Windows 64位平台调试器

    本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. ------------------------------------------------------- ...

  2. 嵌入式调试器原理和各类调试器集锦(JLINK、STLINK、CCDEBUG)

    工欲善其事,必先善其器.调试器在嵌入式开发调试中的重要性不言而喻,单步.断点和监察的效率远高于串口打印.但是,调试器对于一般开发人员往往是一个黑匣子.今天我们就来谈谈调试器的原理,顺便把自己的几类调试 ...

  3. 修改Windows默认调试器

    程序崩溃时,系统会弹窗让你选择是否进行调试,可以设置系统默认调试器. 注册表位置: HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVe ...

  4. 第二章排错的工具:调试器Windbg(上)

    感谢博主 http://book.51cto.com/art/200711/59731.htm <Windows用户态程序高效排错>第二章主要介绍用户态调试相关的知识和工具.本文主要讲了排 ...

  5. Windows Kernel Way 1:Windows内核调试技术

    掌握Windows内核调试技术是学习与研究Windows内核的基础,调试Windows内核的方式大致分为两种: (1)通过Windbg工具在Windows系统运行之初连接到Windows内核,连接成功 ...

  6. 使用GDB命令行调试器调试C/C++程序

    原文:http://xmodulo.com/gdb-command-line-debugger.html作者: Adrien Brochard 没有调试器的情况下编写程序时最糟糕的状况是什么?编译时跪 ...

  7. 使用GDB命令行调试器调试C/C++程序【转】

    转自:https://linux.cn/article-4302-1.html 编译自:http://xmodulo.com/gdb-command-line-debugger.html作者: Adr ...

  8. Windbg是windows平台上强大的调试器

    基础调试命令 - .dump/.dumpcap/.writemem/!runaway Windbg是windows平台上强大的调试器,它相对于其他常见的IDE集成的调试器有几个重要的优势, Windb ...

  9. [原创]在Windows平台使用msys2、mingw64和vscode编写和调试C/C++代码

    相关名词就不解释了,这里主要讲讲在vscode里怎么配,这里假设大家相关工具已经装好. 题外话:里面的大多数坑都是windows平台和linux平台的差异造成的,如果在linux平台配置,应该会顺利很 ...

  10. 使用Windows事件查看器调试崩溃

    本文讨论如何使用Windows事件查看器获取实际崩溃的模块以及代码中崩溃的位置.示例代码是用C++编写的,以生成不同类型的崩溃,例如访问冲突和堆栈溢出. 简介 我经常听同事和QA那里听说,一个特定的崩 ...

随机推荐

  1. jmeter 压测tcp协议

    https://www.cnblogs.com/TestSu/p/10552239.html

  2. Detected non-NVML platform: could not load NVML: libnvidia-ml.so.1: cannot open shared object

    前言 在 kubernetes 中配置 https://github.com/NVIDIA/k8s-device-plugin 时, 报错:Detected non-NVML platform: co ...

  3. ubuntu网络连接失败

    首先要保证pc上可以连接上网络,然后进行下面的操作: 1.首先选择VMware的编辑选项 2.选择虚拟网络编辑器 3.还原默认设置,等待几分钟,确定就可以解决了

  4. hotmail 获取邮箱授权码

    第一步:登录microsoft账户,进入到安全性页面. https://account.microsoft.com/?lang=zh-CN&refd=account.live.com& ...

  5. MOS管的引脚,G、S、D分别代表什么?

    引脚解析: G:gate 栅极,N沟道的电源一般接在D. S:source 源极,输出S,P沟道的电源一般接在S. D:drain 漏极,输出D.增强耗尽接法基本一样. mos管是金属(metal)- ...

  6. IvorySQL 4.2 发布

    IvorySQL 4.2 已于 2025 年 1 月 13 日正式发布.新版本全面支持 PostgreSQL 17.2,并修复了多项 bug. 增强功能 PostgreSQL 17.1 增强功能 确保 ...

  7. Python 加上颜色进行输出

    博客地址:https://www.cnblogs.com/zylyehuo/ print(f"\033[42m文本内容\033[0m")

  8. K8S 问题排查: cgroup 内存泄露问题

    Posted on 2019年12月6日Leave a comment Contents [hide] 1 前言 2 现象 3 原因 4 解决方案 4.1 方案一 4.2 方案二 4.3 方案三 5 ...

  9. 【Python自动化测试环境管理】tox

    1. tox基本介绍 1.1 tox是什么? tox 是一个用于管理 Python 项目的自动化测试和环境管理工具.它的主要功能是创建虚拟环境并运行项目的测试套件,tox能够让我们在同一个Host上自 ...

  10. shader中的if语句优化

    可以借用lerp和step来拼接模仿语句 lerp(a,b,step(value,c))