调试器原理与编写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. Refit 原理解析:从初识到实践

    在现代的分布式系统和微服务架构中,HTTP API 调用是不可或缺的一部分.为了简化 HTTP 请求的构建和解析,我们可以使用 Refit 这个强大的库.Refit 通过将 HTTP API 抽象为接 ...

  2. Python基础笔记-while、字符串格式化、运算符、基础概念与数据类型

    前言 !!!注意:本系列所写的文章全部是学习笔记,来自于观看视频的笔记记录,防止丢失.观看的视频笔记来自于:哔哩哔哩武沛齐老师的视频:2022 Python的web开发(完整版) 入门全套教程,零基础 ...

  3. 百万架构师第四十七课:并发编程的原理(二)|JavaGuide

    原文链接 JavaGuide <并发编程的艺术> 并发编程的实现原理 目标 上节课内容回顾 synchronized 原理分析 wait 和 notify Lock 同步锁 回顾 原子性 ...

  4. 关于我第二周学习kotlin这门语言

    有关kotlin的知识点: 在学习lambda之前,我们先了解一下什么是lambda,简答来说就是一小段代码块,并且我们可以将这个代码块在函数之间传递,这是函数式编程的一个重要特性. 通常我们会需要一 ...

  5. echarts柱形图给X轴坐标类目添加点击事件

    在项目中遇到这么个需求要在柱形图上的x轴添加点击事件,当点击对应x轴文字的时候要弹出模态框展示子图表 根据echarts的Api给图表实例绑定点击事件 myChartInstance?.on('cli ...

  6. SQL 常见优化指南

    这一章介绍SQL常见的优化,一共30条 第一条 对查询优化,要尽量的避免全表扫描,首先应该考虑在where以及order by 涉及的列上建立索引. 第二条 应尽量避免在where子句中对字段的nul ...

  7. bs4库爬取天气预报

    Python不仅用于网站开发,数据分析,图像处理,也常用于爬虫技术方向,最近学习了解下,爬虫技术入门一般先使用bs4库,爬取天气预报简单尝试下. 第一步:首先选定目标网站地址 网上查询,天气预报准确率 ...

  8. mysql-installer-community-8.0.19.0.msi 的自定义安装与卸载

    一.双击运行安装包执行安装 1.选择Custom,该种方式可以设置安装位置,仅安装所需的组件,点击Next 2.选择需要的组件,点击Advanced Options 3.设置安装位置,点击OK 4.点 ...

  9. MD5加密BASE64加解密

    MD5需要引入system.Hash,BASE64需要引入System.NetEncoding,这两个单元应该只有高版本的DELPHI IDE才有 (貌似XE5以上版本才有).如果是D7的话,找第三方 ...

  10. supOS蓝卓工业互联网与iNeuOS工业互联网操作系统对比

    最近有一个企业正在调研工业互联网平台,其中调研的是supOS蓝卓工业互联网和iNeuOS工业互联网操作系统.让我们给出一个对比情况表,我们自己总结了一份,由于不太了解supOS,于是也问了deepse ...