Windows平台调试器原理与编写03.单步
调试器原理与编写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.单步的更多相关文章
- 手把手教你写Windows 64位平台调试器
本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. ------------------------------------------------------- ...
- 嵌入式调试器原理和各类调试器集锦(JLINK、STLINK、CCDEBUG)
工欲善其事,必先善其器.调试器在嵌入式开发调试中的重要性不言而喻,单步.断点和监察的效率远高于串口打印.但是,调试器对于一般开发人员往往是一个黑匣子.今天我们就来谈谈调试器的原理,顺便把自己的几类调试 ...
- 修改Windows默认调试器
程序崩溃时,系统会弹窗让你选择是否进行调试,可以设置系统默认调试器. 注册表位置: HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVe ...
- 第二章排错的工具:调试器Windbg(上)
感谢博主 http://book.51cto.com/art/200711/59731.htm <Windows用户态程序高效排错>第二章主要介绍用户态调试相关的知识和工具.本文主要讲了排 ...
- Windows Kernel Way 1:Windows内核调试技术
掌握Windows内核调试技术是学习与研究Windows内核的基础,调试Windows内核的方式大致分为两种: (1)通过Windbg工具在Windows系统运行之初连接到Windows内核,连接成功 ...
- 使用GDB命令行调试器调试C/C++程序
原文:http://xmodulo.com/gdb-command-line-debugger.html作者: Adrien Brochard 没有调试器的情况下编写程序时最糟糕的状况是什么?编译时跪 ...
- 使用GDB命令行调试器调试C/C++程序【转】
转自:https://linux.cn/article-4302-1.html 编译自:http://xmodulo.com/gdb-command-line-debugger.html作者: Adr ...
- Windbg是windows平台上强大的调试器
基础调试命令 - .dump/.dumpcap/.writemem/!runaway Windbg是windows平台上强大的调试器,它相对于其他常见的IDE集成的调试器有几个重要的优势, Windb ...
- [原创]在Windows平台使用msys2、mingw64和vscode编写和调试C/C++代码
相关名词就不解释了,这里主要讲讲在vscode里怎么配,这里假设大家相关工具已经装好. 题外话:里面的大多数坑都是windows平台和linux平台的差异造成的,如果在linux平台配置,应该会顺利很 ...
- 使用Windows事件查看器调试崩溃
本文讨论如何使用Windows事件查看器获取实际崩溃的模块以及代码中崩溃的位置.示例代码是用C++编写的,以生成不同类型的崩溃,例如访问冲突和堆栈溢出. 简介 我经常听同事和QA那里听说,一个特定的崩 ...
随机推荐
- 6. Calcite添加自定义函数
1. 简介 在上篇博文中介绍了如何使用calcite进行sql验证, 但是真正在实际生产环境中我们可能需要使用到 用户自定义函数(UDF): 通过代码实现对应的函数逻辑并注册给calcite sql验 ...
- 【代码设计】C# 实现 AOP 面向切面编程
简单记录一下对AOP的认识,正文为3个部分 一.AOP由来 二.用DispatchProxy动态代理实现AOP 三.通过特性标记,处理多种不同执行前.执行后的逻辑编排 一.AOP 由来 IUserHe ...
- SQL Server 2025 AI相关能力初探
SQL Server 在2024年11月开始进行社区私有预览(链接),由于涉及AI能力,我也是第一时间申请了内侧资格,悲剧的是,直到2025年2月,才拿到预览版的测试资格-.-,此时已经是CTP1.3 ...
- Netty基础—1.网络编程基础一
大纲 1.什么是OSI开放系统互连 2.OSI七层模型各层的作用 3.TCP/IP协议的简介 4.TCP和UDP的简介 5.TCP连接的三次握手 6.TCP连接的四次挥手 7.TCP/IP中的数据包 ...
- Web前端入门第 15 问:学会查阅 HTML 文档
HELLO,这里是大熊学习前端开发的入门笔记. 本系列笔记基于 windows 系统. HTML的魔法世界,多姿多彩,千变万化. 授人以鱼不如授人以渔,文档阅读是开发者必备的技能,毕竟代码出现 BUG ...
- laravel-admin php artisan admin:install报错问题解决办法
问题描述:根据laravel-admin官方文档安装步骤,执行:php artisan admin:install 安装时报错. 其实是数据库创建特殊字符过长问题,laravel 5.4 改变了默认的 ...
- Dubbo学习系列之十九(Apollo分布式部署)
说一个人是武林高手:十八般武艺,样样精通!如今,后端技术层出不穷,让人眼花缭乱,如果看官不能达到样样精通,至少 拿起方天画戟能耍几下才行,比如削个苹果.言归正传,配置中心属于基础设施,当然必须玩得溜, ...
- .NET Core & ConsoleApp & appsettings.json
准备 Visual Studio 2017 .NET Core 2.1 新建控制台应用(.NET Core) 默认的 Program.cs // Program.cs using System; na ...
- AI时代:大模型开发framework之langchain和huggingface
langchain: 提供了大模型相关应用开发的所有便利. https://python.langchain.com/docs/get_started/introduction Build your ...
- 解决 Maven 打包项目中 Excel 文件乱码问题
在 Java 项目开发过程中,我们常常会使用 Maven 来管理项目依赖和进行项目打包.当涉及到使用 Freemarker 导出 Excel 文件时,不少开发者可能会遇到一个让人头疼的问题 --Exc ...