Windows平台调试器原理与编写05.内存断点

news/2025/3/9 18:03:28/文章来源:https://www.cnblogs.com/weiyuanzhang/p/18761117

https://www.bpsend.net/thread-274-1-3.html

内存断点

  1. 访问断点
  2. 写入断点

内存写入断点

  • 简介:当被调试进程访问,读或写指定内存的时候,程序能够断下来。
  • 思考1:
    • 要想将一段内存设为内存断点,最终的目的是让其能够抛异常。调试器是基于异常的一个程序。应该如何实现呢?
可以通过修改内存属性,让其不能访问或读写来实现,之后会触发 c05 异常。
    • 修改内存属性,至少修改影响一个分页,怎么办?
      • 断下来后,判断不可访问地址是否为内存断点的内存范围。
因为内存被改成了不可读或不可写,所以断点的指令无法执行下去,需要断步配合
        • 范围内:断步配合,还原内存属性,执行完了后再次设置内存属性。
        • 范围外:恢复内存属性,执行,单步异常-》重新设置内存属性。
  • 思考2:
如果一段内存,前4字节内存访问断点,后四字节内存写入断点,如何实现?
    • 修改属性为不可读,不可写,不可执行,异常后,判断地址是否在范围内,再判断访问类型,是否和设置的一样。

流程图

img

代码实现

注意,并不是所有地址都适合下内存断点

img

内存断点实现

.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", 0g_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_szHardbpTip                     db "硬件访问断点", 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_szOutPutAsmFmt db "%08x %-20s %-20s", 0dh, 0ah, 0g_szInputCmd db "选择命令:", 0dh, 0ahdb "是:设置硬件执行断点", 0dh, 0ahdb "否:设置硬件访问断点", 0dh, 0ahdb "取消:直接运行", 0dh, 0ah,0g_btOldCode db 0g_dwBpAddr  dd   010026a7h g_byteCC   db 0CChg_szOutPutAsm db 64 dup(0)g_ud_obj db 1000h dup(0)g_bIsCCStep dd FALSEg_bIsStepStep dd FALSEg_bIsHardBpStep dd FALSEg_bIsResetHardBpStep dd FALSEg_dwMemAddr dd 01005360h    ;设置内存断点地址g_dwMemLen  dd 40h          ;设置内存断点大小g_dwOldProc dd 0            ;原来的内存属性g_bIsMemStep dd FALSE       ;是否是内存断点单步.code  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;显示下一条即将执行的指令invoke ReadProcessMemory, g_hExe, [esi].u.Exception.pExceptionRecord.ExceptionAddress, \offset g_szOutPutAsm, 20, addr @dwBytesOutinvoke 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_objinvoke ud_insn_off, offset g_ud_objmov @dwOff, eaxinvoke ud_insn_hex, offset g_ud_objmov @pHex, eaxinvoke ud_insn_asm, offset g_ud_objmov @pAsm, eaxinvoke ud_insn_len, offset g_ud_objmov edi, pdwCodeLenmov [edi], eaxinvoke crt_printf, offset g_szOutPutAsmFmt, @dwOff, @pHex, @pAsmmov eax, @pAsm.if dword ptr [eax] == 'llac'mov eax, TRUEret      .endifmov eax, FALSEretIsCallMn endpSetTF proc dwTID:DWORDLOCAL @hThread:HANDLE LOCAL @ctx:CONTEXTinvoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTIDmov @hThread, eaxmov @ctx.ContextFlags, CONTEXT_FULLinvoke GetThreadContext, @hThread, addr @ctxor @ctx.regFlag, 100hinvoke SetThreadContext, @hThread, addr @ctxinvoke CloseHandle, @hThreadretSetTF endpDecEIP 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 endpGetContext proc uses esi dwTID:DWORD, pCtx:ptr CONTEXTLOCAL @hThread:HANDLE invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTIDmov @hThread, eaxmov esi, pCtxassume esi:ptr CONTEXTmov [esi].ContextFlags, CONTEXT_ALLinvoke GetThreadContext, @hThread, esiassume esi:nothinginvoke CloseHandle, @hThreadretGetContext endpSetContext proc dwTID:DWORD, pCtx:ptr CONTEXTLOCAL @hThread:HANDLE invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTIDmov @hThread, eaxinvoke SetThreadContext, @hThread, pCtxinvoke CloseHandle, @hThreadretSetContext endpSetBp proc  LOCAL @dwBytesOut:DWORD  ;保存原来的指令, 在 01001BCF写入CCinvoke ReadProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOutinvoke WriteProcessMemory, g_hExe,  g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOutretSetBp endpInputCmd proc uses esi pDE:ptr DEBUG_EVENT LOCAL @bIsCall:BOOLLOCAL @dwCodeLen:DWORDLOCAL @ctx:CONTEXTmov esi, pDEassume esi:ptr DEBUG_EVENTinvoke IsCallMn, pDE, addr @dwCodeLenmov @bIsCall, eaxinvoke MessageBox, NULL, offset g_szInputCmd, NULL, MB_YESNO.if eax == IDYES;设置内存访问断点(修改内存属性即可)invoke VirtualProtectEx, g_hExe, g_dwMemAddr, g_dwMemLen, PAGE_NOACCESS, offset g_dwOldProc.else;直接运行.endifretInputCmd endpOnException proc uses esi pDE:ptr DEBUG_EVENT LOCAL @dwBytesOut:DWORD  LOCAL @ctx:CONTEXTLOCAL @dwInacessAddr:DWORD  ;不可访问的地址LOCAL @dwFlag:DWORD         ;不可访问的操作(0读或1写)LOCAL @dwOldproc:DWORDmov 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 WriteProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut ;设置单步invoke SetTF, [esi].dwThreadIdinvoke DecEIP, [esi].dwThreadId;单步中需要处理CC的单步mov g_bIsCCStep,  TRUE;输入命令invoke InputCmd, pDEmov eax, DBG_CONTINUEret.endif;c05异常   EXCEPTION_ACCESS_VIOLATION.if [esi].u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION;判断是否命中内存断点;获取读写标志 (0写 1读)mov eax, [esi].u.Exception.pExceptionRecord.ExceptionInformation[0]mov @dwFlag, eax;不可访问的数据地址  加4是因为操作占四个字节mov eax, [esi].u.Exception.pExceptionRecord.ExceptionInformation[4]mov @dwInacessAddr, eax;判断地址范围.if @dwInacessAddr > 01005360h && @dwInacessAddr < 01005360h + 40h && @dwFlag == 1;命中,等待输入命令invoke InputCmd, pDE.endif;还原内存属性invoke VirtualProtectEx, g_hExe, g_dwMemAddr, g_dwMemLen, g_dwOldProc,  addr @dwOldproc ;TF置位invoke SetTF, [esi].dwThreadId;标志置位mov g_bIsMemStep, TRUEmov eax, DBG_CONTINUEret.endif;单步来了.if [esi].u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP;处理自己的单步invoke GetContext, [esi].dwThreadId, addr @ctxinvoke crt_printf, offset g_szSsFmt, @ctx.regEip;处理CC的单步.if g_bIsCCStep == TRUEmov g_bIsCCStep, FALSE;重设断点, 重新写入CC;invoke WriteProcessMemory, g_hExe,  g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOutmov eax, DBG_CONTINUEret.endif;内存的单步.if g_bIsMemStep == TRUEmov g_bIsMemStep, FALSE;重设内存断点invoke VirtualProtectEx, g_hExe, g_dwMemAddr, g_dwMemLen, PAGE_NOACCESS,  addr @dwOldproc .endifmov eax, DBG_CONTINUEret.endifassume esi:nothingmov eax, DBG_EXCEPTION_NOT_HANDLED retOnException endpOnCreateProcess proc ;保存原来的指令, 在 01001BCF写入CCinvoke 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.elseif @de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT;invoke crt_printf, offset g_szCREATE_PROCESS_DEBUG_EVENTinvoke OnCreateProcess.elseif @de.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENTinvoke crt_printf, offset g_szEXIT_THREAD_DEBUG_EVENT.elseif @de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENTinvoke 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_EVENTinvoke crt_printf, offset g_szUNLOAD_DLL_DEBUG_EVENT.elseif @de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENTinvoke crt_printf, offset g_szOUTPUT_DEBUG_STRING_EVENT.endif;提交事件处理结果invoke ContinueDebugEvent, @de.dwProcessId, @de.dwThreadId, @dwStatusinvoke RtlZeroMemory, addr @de, size @de.endwretmain endpstart:invoke main 
end start

注意

调试器添加内存断点功能,需要注意的细节部分

① 分页,缺页

当内存断点需要支持多个,会面临很多问题

Ⅰ分页
  • 内存断点的范围是可以指定的,内存断点(范围)就会可能比较长,如果内存断点跨分页,比如是这个分页的末尾到下一个分页的开始
  • 跨分页带来的危险:会引发缺页问题
Ⅱ 缺页

缺页指的是当软件试图访问已映射在虚拟地址空间中,但是并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。

缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。

img

Ⅲ 缺页如何解决
  • 我们的解决是不让他在缺页处下断点
Ⅳ 如何检查是否缺页

NtQueryVirtualMemory NtQueryVirtualMemory是windows的一个未公开API(导出但未形成文档),他的作用主要是查询指定进程的某个虚拟地址控件所在的内存对象的一些信息。我们可以通过这个api看有没有缺页!

原型(prototype):

NTSTATUS NTAPI NtQueryVirtualMemory(IN HANDLE                 ProcessHandle,              //目标进程句柄IN PVOID                  BaseAddress,                //目标内存地址IN MEMORY_INFORMATION_CLASS   MemoryInformationClass, //查询内存信息的类别OUT PVOID                 Buffer,                     //用于存储获取到的内存信息的结构地址IN ULONG                  Length,                     //Buffer的最大长度OUT PULONG                ResultLength OPTIONAL);     //存储该函数处理返回的信息的长度的ULONG的地址 

第一个参数是目标进程的句柄,第二个参数是要查询的内存地址,第五个和第六个参数为Buffer长度,和函数处理结果返回的长度。

第三个参数类型MEMORY_INFORMATION_CLASS是一个枚举类型其定义如下:

//MEMORY_INFORMATION_CLASS定义
typedef enum _MEMORY_INFORMATION_CLASS
{MemoryBasicInformation,            //内存基本信息MemoryWorkingSetInformation,       //工作集信息MemoryMappedFilenameInformation    //内存映射文件名信息
} MEMORY_INFORMATION_CLASS;

第四个参数是根据第三个参数选用不同的结构去接收内存信息的地址。

其对应关系如下:

0x00:使用MemoryBasicInformation时,Buffer应当指向的结构为MEMORY_BASIC_INFORMATION,其定义如下:

typedef struct _MEMORY_BASIC_INFORMATION {PVOID       BaseAddress;PVOID       AllocationBase;DWORD       AllocationProtect;SIZE_T      RegionSize;DWORD       State;DWORD       Protect;DWORD       Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

0x01:使用MemoryWorkingSetInformation时,Buffer应当指向的结构为MEMORY_WORKING_SET_INFORMATION,其定义如下:

typedef struct _MEMORY_WORKING_SET_INFORMATION {ULONG       SizeOfWorkingSet;DWORD       WsEntries[ANYSIZE_ARRAY];
} MEMORY_WORKING_SET_INFORMATION, *PMEMORY_WORKING_SET_INFORMATION;

0x02:当使用MemoryMappedFilenameInformation 时,Buffer应当指向结构为MEMORY_MAPPED_FILE_NAME_INFORMATION,其定义如下:

#define _MAX_OBJECT_NAME 1024/sizeof(WCHAR)
typedef struct _MEMORY_MAPPED_FILE_NAME_INFORMATION {UNICODE_STRING Name;WCHAR     Buffer[_MAX_OBJECT_NAME];
} MEMORY_MAPPED_FILE_NAME_INFORMATION, *PMEMORY_MAPPED_FILE_NAME_INFORMATION; 

第一种和第二种没有太多需要解释的地方,至于第三种的MEMORY_MAPPED_FILE_NAME_INFORMATION的定义形式需要说明一下,第三种使用方法目前资料很少,我看多资料都是直接直接传了个数组进去,然后很多人就发表评论说为什么要传那个长度呢?为什么不可以传这个长度呢?无人回答……那好我们自己来找找标准用法吧。

② 两个断点导致的问题:

img

Ⅰ解决方式:
  • 不让他输入进来
Ⅱ 内存的恢复就需要谨慎
  • 比如第一个断点设上之后,要修改内存属性,如果此时在这个内存分页中再添加一个内存断点,你保存的内存属性,就是改过之后的内存属性
Ⅲ 建三张表
  • 内存、分页、断点三者之间的数据关

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

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

相关文章

Redis--Lesson01--NoSQL简史

单击MySQL的演进 单机MySQL 在早期互联网时代,也就是90年代以前,一个基本的互联网的访问量不会太大,可以说很多国家和地区都还没有配备互联网,所以在这种情况下的互联网格局使用的数据存储格式就是简单的单机模式,即使用一个数据库的如MySQL库就可以满足日常的数据读写 如…

Excel的快捷键

1、填充序号1~1000(删除后,序号会自动更新) (1)首先在左上角的位置框中输入A1:A1000,然后按Enter回车健,即可选中A1到A1000的单元格。(2)然后在函数框中输入=ROW(),按Ctrl + Enter即可,即可填充1-1000。 本文来自博客园,作者:业余砖家,转载请注明原文链接:http…

AutoGLM: Autonomous Foundation Agents for GUIs

AutoGLM: 针对Web和手机,基于ChatGLM,具体细节并不清楚。主要内容 提出AUTOGLM,集成了一套全面的技术和基础设施,以创建适合用户交付的可部署代理系统。首先,为GUI控制设计合适的"intermediate interface"是至关重要的,可以实现规划和定位的分离。其次,开发了…

Vulnhub-election靶机

总结:本靶机给了很多目录,对于信息收集考察的比较严格,给了一个数据库,很多时候容易陷进去,拿到用户权限登录后,也需要大量的信息收集,虽然可以在数据库里找到root和密码,但是不是靶机本身的,最终利用suid发现可疑目录,查找日志后利用脚本提权一、靶机搭建 选择扫描虚…

[HDCTF 2023]double_code _wp

其实这道题的加密函数我是手翻出来的,但是做完之后了解到这是一个sheelcode 实际上就是跑病毒的代码 WriteProcessMemory 用于向指定进程中写入数据,写入一个缓冲区中的数据到另一个进程指定的内存地址中。 函数接受的参数包括要写入的进程句柄,要写入的内存地址,要写入的…

VisionPro添加显示标签(二维码)简单版

!!!——!!! 咱们先展示效果,这个显示的是二维码的信息1.首先呢,你先添加工具 CogIDTool ,工具里我是这么设置的,如果你自己添加的码跟我的不一样,左边几个都运行看看2.现在可以添加脚本了,我接触的都是第二个C#高级脚本,下边是C#高级脚本演示 1)先创建 1个标签2)…

初步学习Android studio

下载安装了Android studio,并在其中下载好了gradle,在模拟手机中实现helloworld

2025低空经济eVTOL行业研究报告42份汇总解读|附PDF下载

原文链接:https://tecdat.cn/?p=40459在科技与交通领域加速融合的当下,低空经济正凭借其独特优势,逐步成为全球经济发展的新焦点。电动垂直起降飞行器(eVTOL)作为低空经济的核心要素,其发展态势备受瞩目。本报告汇总洞察基于文末42份低空经济行业研究报告的数据,报告合…

Prometheus服务的动态发现

prometheus服务的动态发现原文链接:https://blog.csdn.net/2302_79199605/article/details/136441386一、概述 ​ 目前,我们每增加一个被监控的节点,就需要修改prometheus的配置文件,然后重新加载prometheus服务,这种方式比较繁琐,每次新增、删除被监控节点都需要重新操…

AtCoder Beginner Contest 396(d和e)

题目链接d 题目分析 本题要求在一个简单连通无向图中,找出从顶点 1 到顶点 N 的所有简单路径(即不重复经过同一顶点的路径)中,路径上所有边的标签的异或值的最小值。 输入信息第一行包含两个整数 N 和 M,分别表示图的顶点数和边数,其中 2 ≤ N ≤ 10,N - 1 ≤ M ≤ N * …

实验1C语言开发环境使用和数据模型,运算符,表达式

实验1 代码:#include<stdio.h> int main() { printf(" 0 \n"); printf("<H>\n"); printf("I I\n");return 0; } task1 运行结果截图:实验2 代码:#include<stdio.h> int main(){ char ans1,ans2;printf("每次课前认真…

R语言NIMBLE、Stan和INLA贝叶斯平滑及条件空间模型死亡率数据分析:提升疾病风险估计准确性

全文链接:https://tecdat.cn/?p=40365 原文出处:拓端数据部落公众号 在环境流行病学研究中,理解空间数据的特性以及如何通过合适的模型分析疾病的空间分布是至关重要的。本文主要介绍了不同类型的空间数据、空间格点过程的理论,并引入了疾病映射以及对空间风险进行平滑处理…