【编程技术-Windows平台调试器原理与编写05.内存断点】此文章归类为:编程技术。
可以通过修改内存属性,让其不能访问或读写来实现,之后会触发 c05 异常。
因为内存被改成了不可读或不可写,所以断点的指令无法执行下去,需要断步配合
如果一段内存,前4字节内存访问断点,后四字节内存写入断点,如何实现?
注意,并不是所有地址都适合下内存断点
内存断点实现
.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_szHardbpTip db "硬件访问断点", 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_szOutPutAsmFmt db "%08x %-20s %-20s", 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 010026a7h g_byteCC db 0CCh g_szOutPutAsm db 64 dup(0) g_ud_obj db 1000h dup(0) g_bIsCCStep dd FALSE g_bIsStepStep dd FALSE g_bIsHardBpStep dd FALSE g_bIsResetHardBpStep dd FALSE g_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:DWORD LOCAL @dwBytesOut:DWORD LOCAL @dwOff:DWORD LOCAL @pHex:LPSTR LOCAL @pAsm:LPSTR mov esi, pDE assume esi:ptr DEBUG_EVENT ;显示下一条即将执行的指令 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 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 .if dword ptr [eax] == 'llac' mov eax, TRUE ret .endif mov eax, FALSE ret IsCallMn endp 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 or @ctx.regFlag, 100h invoke SetThreadContext, @hThread, addr @ctx invoke CloseHandle, @hThread ret SetTF endp 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 GetContext proc uses esi dwTID:DWORD, pCtx:ptr CONTEXT LOCAL @hThread:HANDLE invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID mov @hThread, eax mov esi, pCtx assume esi:ptr CONTEXT mov [esi].ContextFlags, CONTEXT_ALL invoke GetThreadContext, @hThread, esi assume esi:nothing invoke CloseHandle, @hThread ret GetContext endp SetContext proc dwTID:DWORD, pCtx:ptr CONTEXT LOCAL @hThread:HANDLE invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID mov @hThread, eax invoke SetThreadContext, @hThread, pCtx invoke CloseHandle, @hThread ret SetContext endp SetBp proc LOCAL @dwBytesOut:DWORD ;保存原来的指令, 在 01001BCF写入CC invoke ReadProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut invoke WriteProcessMemory, g_hExe, g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut ret SetBp endp InputCmd proc uses esi pDE:ptr DEBUG_EVENT LOCAL @bIsCall:BOOL LOCAL @dwCodeLen:DWORD LOCAL @ctx:CONTEXT mov esi, pDE assume esi:ptr DEBUG_EVENT invoke IsCallMn, pDE, addr @dwCodeLen mov @bIsCall, eax invoke 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 ;直接运行 .endif ret InputCmd endp OnException proc uses esi pDE:ptr DEBUG_EVENT LOCAL @dwBytesOut:DWORD LOCAL @ctx:CONTEXT LOCAL @dwInacessAddr:DWORD ;不可访问的地址 LOCAL @dwFlag:DWORD ;不可访问的操作(0读或1写) LOCAL @dwOldproc: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 WriteProcessMemory, g_hExe, g_dwBpAddr, offset g_btOldCode, size g_btOldCode, addr @dwBytesOut ;设置单步 invoke SetTF, [esi].dwThreadId invoke DecEIP, [esi].dwThreadId ;单步中需要处理CC的单步 mov g_bIsCCStep, TRUE ;输入命令 invoke InputCmd, pDE mov eax, DBG_CONTINUE ret .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, TRUE mov eax, DBG_CONTINUE ret .endif ;单步来了 .if [esi].u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP ;处理自己的单步 invoke GetContext, [esi].dwThreadId, addr @ctx invoke crt_printf, offset g_szSsFmt, @ctx.regEip ;处理CC的单步 .if g_bIsCCStep == TRUE mov g_bIsCCStep, FALSE ;重设断点, 重新写入CC ;invoke WriteProcessMemory, g_hExe, g_dwBpAddr, offset g_byteCC, size g_byteCC, addr @dwBytesOut mov eax, DBG_CONTINUE ret .endif ;内存的单步 .if g_bIsMemStep == TRUE mov g_bIsMemStep, FALSE ;重设内存断点 invoke VirtualProtectEx, g_hExe, g_dwMemAddr, g_dwMemLen, PAGE_NOACCESS, addr @dwOldproc .endif mov eax, DBG_CONTINUE ret .endif assume esi:nothing mov eax, DBG_EXCEPTION_NOT_HANDLED ret OnException endp OnCreateProcess proc ;保存原来的指令, 在 01001BCF写入CC 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 .elseif @de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT ;invoke crt_printf, offset g_szCREATE_PROCESS_DEBUG_EVENT invoke OnCreateProcess .elseif @de.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT invoke crt_printf, offset g_szEXIT_THREAD_DEBUG_EVENT .elseif @de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT invoke 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_EVENT invoke crt_printf, offset g_szUNLOAD_DLL_DEBUG_EVENT .elseif @de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT invoke crt_printf, offset g_szOUTPUT_DEBUG_STRING_EVENT .endif ;提交事件处理结果 invoke ContinueDebugEvent, @de.dwProcessId, @de.dwThreadId, @dwStatus invoke RtlZeroMemory, addr @de, size @de .endw ret main endp start: invoke main end start
当内存断点需要支持多个,会面临很多问题
缺页指的是当软件试图访问已映射在虚拟地址空间中,但是并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。
缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。
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的定义形式需要说明一下,第三种使用方法目前资料很少,我看多资料都是直接直接传了个数组进去,然后很多人就发表评论说为什么要传那个长度呢?为什么不可以传这个长度呢?无人回答……那好我们自己来找找标准用法吧。
更多【编程技术-Windows平台调试器原理与编写05.内存断点】相关视频教程:www.yxfzedu.com