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

news/2025/3/3 22:59:29/文章来源:https://www.cnblogs.com/weiyuanzhang/p/18749601

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

单步

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

单步步入 -- 遇到call便入

img

单步步过 -- 遇到call不入

img

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

代码实现

.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incinclude msvcrt.incinclude udis86.incincludelib user32.libincludelib kernel32.libincludelib msvcrt.libincludelib libudis86.lib.datag_szExe db "winmine.exe", 0     ;打开的进程g_hExe  dd 0g_szEXCEPTION_DEBUG_EVENT         db "EXCEPTION_DEBUG_EVENT", 0dh, 0ah, 0g_szCREATE_THREAD_DEBUG_EVENT     db "CREATE_THREAD_DEBUG_EVENT", 0dh, 0ah, 0g_szCREATE_PROCESS_DEBUG_EVENT    db "CREATE_PROCESS_DEBUG_EVENT", 0dh, 0ah, 0g_szEXIT_THREAD_DEBUG_EVENT       db "EXIT_THREAD_DEBUG_EVENT", 0dh, 0ah, 0g_szEXIT_PROCESS_DEBUG_EVENT      db "EXIT_PROCESS_DEBUG_EVENT", 0dh, 0ah, 0g_szLOAD_DLL_DEBUG_EVENT          db "LOAD_DLL_DEBUG_EVENT", 0dh, 0ah, 0g_szUNLOAD_DLL_DEBUG_EVENT        db "UNLOAD_DLL_DEBUG_EVENT", 0dh, 0ah, 0g_szOUTPUT_DEBUG_STRING_EVENT     db "OUTPUT_DEBUG_STRING_EVENT", 0dh, 0ah, 0g_szLoadDllFmt   db "%08X %s", 0dh, 0ah, 0g_szwLoadDllFmt   dw '%', '0', '8', 'X', ' ', '%', 's', 0dh, 0ah, 0g_szBpFmt  db "CC异常 %08X", 0dh, 0ah, 0g_szSsFmt  db "单步异常 %08X", 0dh, 0ah, 0g_szInputCmd db "选择命令:", 0dh, 0ahdb "是:单步步入", 0dh, 0ahdb "否:单步步过", 0dh, 0ahdb "取消:直接运行", 0dh, 0ah,0g_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:DWORDLOCAL @dwBytesOut:DWORDLOCAL @dwOff:DWORDLOCAL @pHex:LPSTRLOCAL @pAsm:LPSTRmov esi, pDEassume esi:ptr DEBUG_EVENT;显示下一条即将执行的指令   从内存读取要反汇编指令 (20个字节)到  g_szOutPutAsminvoke ReadProcessMemory, g_hExe, [esi].u.Exception.pExceptionRecord.ExceptionAddress, \offset g_szOutPutAsm, 20, addr @dwBytesOut;初始化结构体  invoke ud_init, offset g_ud_objinvoke ud_set_input_buffer, offset g_ud_obj, offset g_szOutPutAsm, 20invoke ud_set_mode, offset g_ud_obj, 32invoke ud_set_syntax, offset g_ud_obj, offset ud_translate_intelinvoke ud_set_pc, offset g_ud_obj, [esi].u.Exception.pExceptionRecord.ExceptionAddressinvoke ud_disassemble, offset g_ud_obj;获取EIP  当前指令地址invoke ud_insn_off, offset g_ud_objmov @dwOff, eax;获取机器码invoke ud_insn_hex, offset g_ud_objmov @pHex, eax;获取反汇编结果invoke ud_insn_asm, offset g_ud_objmov @pAsm, eax;获取指令长度 invoke ud_insn_len, offset g_ud_objmov edi, pdwCodeLenmov [edi], eax;输出反汇编结果invoke crt_printf, offset g_szOutPutAsmFmt, @dwOff, @pHex, @pAsmmov eax, @pAsm                 ;将反汇编字符数组的首地址给eax.if dword ptr [eax] == 'llac'  ;call在内存里面是 小尾方式存储mov eax, TRUEret      .endifmov eax, FALSEretIsCallMn endp;将TF置位(置1)
SetTF proc dwTID:DWORDLOCAL @hThread:HANDLE LOCAL @ctx:CONTEXTinvoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTIDmov @hThread, eaxmov @ctx.ContextFlags, CONTEXT_FULLinvoke GetThreadContext, @hThread, addr @ctx;将TF置1or @ctx.regFlag, 100hinvoke SetThreadContext, @hThread, addr @ctxinvoke CloseHandle, @hThreadretSetTF endp;回退EIP
DecEIP proc dwTID:DWORDLOCAL @hThread:HANDLE LOCAL @ctx:CONTEXTinvoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTIDmov @hThread, eaxmov @ctx.ContextFlags, CONTEXT_FULLinvoke GetThreadContext, @hThread, addr @ctxdec @ctx.regEipinvoke SetThreadContext, @hThread, addr @ctxinvoke CloseHandle, @hThreadretDecEIP 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;在指定地址 写入ccinvoke WriteProcessMemory, g_hExe,  g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut;还原内存属性invoke VirtualProtect, g_dwBpAddr, 1, @dwOldProc, addr @dwOldProcretSetBp endp;选择指令
InputCmd proc uses esi pDE:ptr DEBUG_EVENT LOCAL @bIsCall:BOOL        ;是否call指令LOCAL @dwCodeLen:DWORD     ;指令长度mov esi, pDEassume esi:ptr DEBUG_EVENT;判断是否是call指定以及获取指令长度invoke IsCallMn, pDE, addr @dwCodeLenmov @bIsCall, eax    ;保存判断结果invoke MessageBox, NULL, offset g_szInputCmd, NULL, MB_YESNOCANCEL.if eax == IDYES;单步步入,直接TF置1invoke 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置1invoke SetTF, [esi].dwThreadId;单步中需要处理T命令mov g_bIsStepStep, TRUE   .endif.else;直接运行.endifretInputCmd endpOnException proc uses esi pDE:ptr DEBUG_EVENT LOCAL @dwOldProc:DWORD   ;修改之前的内存属性LOCAL @dwBytesOut:DWORD  mov esi, pDEassume esi:ptr DEBUG_EVENT.if [esi].u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT;判断是否是自己的CCmov 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].dwThreadIdinvoke DecEIP, [esi].dwThreadId;单步中需要处理CC的单步mov g_bIsCCStep,  TRUE;输入命令invoke InputCmd, pDEmov eax, DBG_CONTINUEret.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 == TRUEmov g_bIsCCStep, FALSE;重设断点, 重新写入CC;invoke WriteProcessMemory, g_hExe,  g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut.endif;处理T命令的单步.if g_bIsStepStep == TRUEmov g_bIsStepStep, FALSEinvoke InputCmd, pDE.endifmov eax, DBG_CONTINUEret.endifassume esi:nothingmov eax, DBG_EXCEPTION_NOT_HANDLED retOnException endpOnCreateProcess proc ;设置断点invoke SetBpretOnCreateProcess endpmain procLOCAL @si:STARTUPINFOLOCAL @pi:PROCESS_INFORMATIONLOCAL @de:DEBUG_EVENT LOCAL @dwStatus:DWORDinvoke RtlZeroMemory, addr @si, size @siinvoke RtlZeroMemory, addr @pi, size @piinvoke RtlZeroMemory, addr @de, size @demov @dwStatus, DBG_CONTINUE;建立调试会话invoke CreateProcess, NULL, offset g_szExe, NULL, NULL, FALSE, \DEBUG_ONLY_THIS_PROCESS,\NULL, NULL,\addr @si,\addr @pi.if !eaxret.endif mov eax, @pi.hProcessmov g_hExe, eax;循环接受调试事件.while TRUEinvoke WaitForDebugEvent, addr @de, INFINITE;处理调试事件.if @de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT;invoke crt_printf, offset g_szEXCEPTION_DEBUG_EVENTinvoke OnException, addr @demov @dwStatus, eax.elseif @de.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENTinvoke crt_printf, offset g_szCREATE_THREAD_DEBUG_EVENT

反汇编头文件

udis86.inc

ifndef UDIS86_HUDIS86_H equud_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:ptrendif

反单步

单步主要是通过 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:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.libWinMain proto :DWORD,:DWORD,:DWORD,:DWORD.dataClassName db "MainWinClass",0AppName  db "Main Window",0.data?hInstance HINSTANCE ?CommandLine LPSTR ?.code; ---------------------------------------------------------------------------start:invoke GetModuleHandle, NULLmov    hInstance,eaxinvoke GetCommandLinemov    CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax;SEH异常回调函数
F0Handler proc uses esi edi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordassume esi:ptr EXCEPTION_RECORDmov esi, pERmov edi, pContextassume edi:ptr CONTEXT;跳过下条指令add [edi].regEip, 5assume edi:nothingassume esi:nothingret
F0Handler endpWinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDassume fs:nothingpush offset F0Handler ;handler    ;注册SEH异常push fs:[0] ;nextmov fs:[0], esppushfdor dword ptr [esp], 100hpopfd;异常被调试器收了,会继续执行下面的代码,自己异常无法收到,无法进SEH异常函数,所以无法跳过下条指令invoke ExitProcess, 0   ;退出进程 非单步调试情况下执行当前指令进异常,指令长度为5;获取TF;pushfd;and dword ptr [esp], 100h;jz CONTIUE;invoke MessageBox, NULL, NULL, NULL, MB_OK;CONTIUE:;正常流程,TF没有置1mov   wc.cbSize,SIZEOF WNDCLASSEXmov   wc.style, CS_HREDRAW or CS_VREDRAWmov   wc.lpfnWndProc, OFFSET WndProcmov   wc.cbClsExtra,NULLmov   wc.cbWndExtra,NULLpush  hInstancepop   wc.hInstancemov   wc.hbrBackground,COLOR_BTNFACE+1mov   wc.lpszMenuName,NULLmov   wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov   wc.hIcon,eaxmov   wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov   wc.hCursor,eaxinvoke RegisterClassEx, addr wcINVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULLmov   hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUEinvoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg.ENDW;卸载SEHpop fs:[0]add esp, 4mov     eax,msg.wParamret
WinMain endpWndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL.ELSEIF uMsg==WM_CREATE;.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParam	ret.ENDIFxor eax,eaxret
WndProc endpend start

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

TRACE 追踪

OD的追踪功能

img

也可以设置条件

img

img

查看

img

img

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/893194.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day14 服务管理篇的学习

day14 服务管理的学习 1.Linux的默认提供的服务 1. shhd 的命令 [root@linux-yzk ~]# netstat -tnlp | grep sshd tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1137/sshd tcp 0 0 127.0.0.1:6010 0.0.0…

关于ADAC儿童安全座椅排名

ADAC官网地址 直接翻看排行榜, 注意, 分数越小,排名越靠前。 国内推荐使用 亚马逊中国这样的app或者网站进行购买,海外直邮。 避免在国内买到贴牌货, 国内, 懂得都懂, 5星批发部, 实际使用的和送检的不是同一种, AB货。当然不是所有的国内的商品都是AB货。ADAC不支持主…

六、MyBatis特殊的SQL:模糊查询、动态设置表名、校验名称唯一性

六、MyBatis特殊的SQL:模糊查询、动态设置表名、校验名称唯一性@目录六、MyBatis特殊的SQL6.1 模糊查询6.2 动态设置表名6.3 校验名称唯一性本人其他相关文章链接 六、MyBatis特殊的SQL 6.1 模糊查询方式1:select * from litemall_user where username like %${username}%(推…

AI生成代码测试,前端加后端

首先是话术, 让我们先理顺一下项目的逻辑,对于这样一个WEB管理系统界面,我们有多个思路,如功能模块化,或者前后端分离。 由于我们的MIS系统相对简单,我们可以将整个开发流程进行功能化细分。 首先,完成基础的环境配置。 需求描述: 请设计一个仓储管理系统原型系统,该系…

目前国内可用Docker镜像源汇总(截至2025年2月)

[目前国内可用Docker镜像源汇总(截至2025年2月) - CoderJia](https://www.coderjia.cn/archives/dba3f94c-a021-468a-8ac6-e840f85867ea) 在国内使用 Docker 的朋友们,可能都遇到过配置镜像源来加速镜像拉取的操作。然而,最近几个月发现许多曾经常用的国内镜像站(包括各种…

我的lua使用初体验

本文记录作者第一次使用lua的一次实践,主要借助lua来保证检查锁和释放锁的原子性使用lua实现检查和删除分布式锁的原子性很多时候出现并发问题的根本原因在于检查和操作不是同一个操作,不具有原子性,所以中间会被其他线程插一脚。所以我们需要有一种工具保证这两种操作的原子…

三剑客与正则系列-sed命令

1.概述核心功能:取行,过滤,替换修改文件内容 难点:后向引用(截取). sed stream editor流编辑器.2.格式命令 选项 详细格式 参数sed 选项 条件动作 文件找谁干啥找谁:条件,匹配哪一行,哪些行. 干啥:动作,增删改查. #显示文件的第3行 sed -n 3p /etc/passwd选项 说明-n 取消默认输…

加速PyTorch模型训练技巧

Pytorch-Lightning 可以在Pytorch的库Pytorch-lightning中找到我们在这里讨论的每一个优化。Lightning是在Pytorch之上的一个封装,它可以自动训练,同时让研究人员完全控制关键的模型组件。Lightning使用最新的最佳实践,并将你可能出错的地方最小化。 我们为MINST定义为Light…

能力全面提升综合题单-练习

Part1 语言基础题 P1089 [NOIP 2004 提高组] 津津的储蓄计划import java.util.Scanner;// P1089 [NOIP 2004 提高组] 津津的储蓄计划 public class P1089 {public static void main(String[] args) {Scanner in = new Scanner(System.in);int[] budget = new int[12];for (int …

kettle插件-git/svn版本管理插件

场景:大家都知道我们平时使用spoon客户端的时候时无法直接使用git的,给我们团队协作带来了一些小问题,需要我们本机单独安装git客户端进行手动上传trans或者job。 我们团队成员倪老师开发了一款kettle的git插件,帮我们解决了这个大难题,大大方便了我们团队协作,今天一起来…

自我介绍,软工五问

作业相关信息这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023/homework/13325这个作业的目标 学习博客的使用自我介绍 我是软工2班的彭颂华…