win32 调试器

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了win32 调试器相关的知识,希望对你有一定的参考价值。

概述

什么调试器?就是可以调试原程序的每行代码,在Win32提供了相关API给我们进行进行操作
1.CreateProcessA打开指定的程序,并传入对应的flag让其成为对应的调试器CreateProcessA

2.DebugActiveProcess调试特定的已经打开的程序DebugActiveProcess

3.DebugActiveProcessStop终止调试DebugActiveProcessStop

张银奎的《软件调试》是非常不错的一本书推荐

其大致原理如下:

我们大致说一下CreateProcessA如何调试程序

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

对于CreateProcessA想启用调试需要dwCreationFlags传入调试标志DEBUG_ONLY_THIS_PROCESS(Process Creation Flags)

在之后调用WaitForDebugEvent等候调试事件
WaitForDebugEvent

调用ContinueDebugEvent报告异常应该如何处理
ContinueDebugEvent

我们伪代码如下:

//打开一个进程,并让自己成为被打开程序的调试器
CreateProcessA(...DEBUG_ONLY_THIS_PROCESS...)
while(true)
	//等候调试事件
	WaitForDebugEvent(xx,xx)
	//告诉这个异常如何处理,比如继续运行程序等
	ContinueDebugEvent(zzz)

Demo 输出程序所有加载的dll

下面我们实现一个程序实现打印所有被调试进程加载dll

.386
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc 
   include msvcrt.inc
   
   includelib msvcrt.lib
   includelib user32.lib
   includelib kernel32.lib


; START 初始化变量结束 START 
.data
	g_hProcess dd 0
	g_hThread dd 0

.data
	g_szExePath db "calc.exe",0
	g_szErrCreateProcess db "创建进程失败",0dh,0ah,0
	g_szCREATE_PROCESS_DEBUG_EVENT    db "CREATE_PROCESS_DEBUG_EVENT",0dh,0ah,0
	g_szCREATE_THREAD_DEBUG_EVENT    db "CREATE_THREAD_DEBUG_EVENT",0dh,0ah,0
	g_szEXCEPTION_DEBUG_EVENT    db "EXCEPTION_DEBUG_EVENT",0dh,0ah,0
	g_szEXIT_PROCESS_DEBUG_EVENT    db "EXIT_PROCESS_DEBUG_EVENT",0dh,0ah,0
	g_szEXIT_THREAD_DEBUG_EVENT    db "EXIT_THREAD_DEBUG_EVENT",0dh,0ah,0
	g_szLOAD_DLL_DEBUG_EVENT    db "LOAD_DLL_DEBUG_EVENT",0dh,0ah,0
	g_szOUTPUT_DEBUG_STRING_EVENT    db "OUTPUT_DEBUG_STRING_EVENT",0dh,0ah,0
	g_szRIP_EVENT    db "RIP_EVENT",0dh,0ah,0
	g_szUNLOAD_DLL_DEBUG_EVENT    db "UNLOAD_DLL_DEBUG_EVENT",0dh,0ah,0
	g_szLoadDllFmt db "[event] Lod Dll Base:%08X ImageName:%s",0dh,0ah,0 
	g_dwLoadDllFmtLen dd $-offset g_szLoadDllFmt
	
	g_dwAddr dd 01006121h
	g_bt01dCode db 0
	
	g_bIsSystemCC dd FALSE
	
	g_szDebugFmt db "Excetion addr:%08X",0dh,0ah,0 
	g_szDebug2Fmt db "EXCEPTION_SINGLE_STEP addr:%08X",0dh,0ah,0 


; END 初始化变量结束 END

.code

main proc 
	LOCAL @si:STARTUPINFO
	LOCAL @pi:PROCESS_INFORMATION
	LOCAL @de:DEBUG_EVENT 
	LOCAL @dwStatus:DWORD
	
	;初始化一些变量
	invoke RtlZeroMemory,addr @si,type @si
	mov @si.cb,type @si
	invoke RtlZeroMemory,addr @pi,type @pi
	
	;开启一个程序,并传入调试标志
	invoke CreateProcess,offset g_szExePath,NULL,NULL,NULL,FALSE,DEBUG_ONLY_THIS_PROCESS,NULL,NULL,addr @si,addr @pi
	
	;判断是否启动成功
	.if eax==FALSE
		invoke crt_printf,offset g_szErrCreateProcess
		ret
	.endif
	
	;存储局部变量
	push @pi.hProcess
	pop g_hProcess
	
		;等候调试事件
	.while TRUE 
	
	    ;开始等候调试事件
		invoke RtlZeroMemory,addr @de,type @de
		invoke WaitForDebugEvent,addr @de,INFINITE
	 	
	 	;运行到此处证明有事件发生
	 	invoke OpenThread,THREAD_ALL_ACCESS,FALSE,@de.dwThreadId
	 	mov g_hThread,eax
	 	
	 	;处理调试事件
	 	mov @dwStatus,DBG_CONTINUE
	 	
	 	;根据事件类型进行打印,我们这边打印LOAD_DLL_DEBUG_EVENT里面module名称
	 	.if @de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
	 		invoke crt_printf,offset g_szCREATE_PROCESS_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==CREATE_THREAD_DEBUG_EVENT
	 		invoke crt_printf,offset g_szCREATE_THREAD_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==EXCEPTION_DEBUG_EVENT
	 		invoke crt_printf,offset g_szEXCEPTION_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==EXIT_PROCESS_DEBUG_EVENT
	 		invoke crt_printf,offset g_szEXIT_PROCESS_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==EXIT_THREAD_DEBUG_EVENT
	 		invoke crt_printf,offset g_szEXIT_THREAD_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==LOAD_DLL_DEBUG_EVENT
	 		;调用OnLoadDll打印所有dll
	 		invoke OnLoadDll,addr @de
	 		invoke crt_printf,offset g_szLOAD_DLL_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==OUTPUT_DEBUG_STRING_EVENT
	 		invoke crt_printf,offset g_szOUTPUT_DEBUG_STRING_EVENT
	 	.elseif  @de.dwDebugEventCode ==RIP_EVENT
	 		invoke crt_printf,offset g_szRIP_EVENT
	 	.elseif  @de.dwDebugEventCode ==UNLOAD_DLL_DEBUG_EVENT
	 		invoke crt_printf,offset g_szUNLOAD_DLL_DEBUG_EVENT
	 	.endif
	 
	 	;我们假设所有的事件都继续处理即可
		invoke CloseHandle,g_hThread 
		invoke ContinueDebugEvent,@de.dwProcessId,@de.dwThreadId,DBG_CONTINUE
		
	.endw
	
	
	ret

main endp



START:
 	invoke main
 	xor eax,eax
 	invoke ExitProcess,eax

end START

我们最后看下OnLoadDll函数

OnLoadDll proc uses esi pDe:ptr DEBUG_EVENT
	LOCAL @szFileName[MAX_PATH*2 ]:CHAR
	LOCAL @dwAddr:DWORD 
	LOCAL @dwBytesReaded:DWORD
	LOCAL @szwLoadDllFmt[MAX_PATH ]:CHAR
	mov esi,pDe
	assume esi:ptr DEBUG_EVENT
	
	;读取另一个进程的内存地址,得到指向module的名称的指针
	invoke ReadProcessMemory,g_hProcess,[esi].u.LoadDll.lpImageName,addr @dwAddr,type @dwAddr,addr @dwBytesReaded

	;判断是否成功
	.if eax==FALSE || @dwAddr ==NULL
		ret
	.endif
	
	 ;上一步得到指针后继续读取指针域得到结果
	invoke ReadProcessMemory,g_hProcess,@dwAddr,addr @szFileName,MAX_PATH*2,addr @dwBytesReaded
	
	;判断名称是否位unicode编码,如果是如unicode编码需要做一次转化后在打印
	.if [esi].u.LoadDll.fUnicode ==TRUE
		invoke crt_mbstowcs,addr @szwLoadDllFmt,offset g_szLoadDllFmt,g_dwLoadDllFmtLen
		invoke crt_wprintf,addr @szwLoadDllFmt,[esi].u.LoadDll.lpBaseOfDll,addr @szFileName
	.else
		invoke crt_printf ,offset g_szLoadDllFmt,[esi].u.LoadDll.lpBaseOfDll,addr @szFileName
	.endif
	
		
	
	assume esi:nothing
	
	
	ret

OnLoadDll endp

输出

Demo 单步调试

理解的知识点:

  1. 设置标志寄存器的TF位,会触发硬件提供的单步执行。即执行xxx后判断是否存在tf为1,如果为1那么会触发中断
  2. cc 即我们int 3会触发断点事件

我们结合上面两个技巧即可完成,所谓单步调试。

假设我们需要给一个程序01006121h地址触发断点调试,每次路过这个位置时都会触发断点。

.386
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc 
   include msvcrt.inc
   
   includelib msvcrt.lib
   includelib user32.lib
   includelib kernel32.lib

.data
	g_hProcess dd 0
	g_hThread dd 0

.data
	g_szExePath db "calc.exe",0
	g_szErrCreateProcess db "创建进程失败",0dh,0ah,0
	g_szCREATE_PROCESS_DEBUG_EVENT    db "CREATE_PROCESS_DEBUG_EVENT",0dh,0ah,0
	g_szCREATE_THREAD_DEBUG_EVENT    db "CREATE_THREAD_DEBUG_EVENT",0dh,0ah,0
	g_szEXCEPTION_DEBUG_EVENT    db "EXCEPTION_DEBUG_EVENT",0dh,0ah,0
	g_szEXIT_PROCESS_DEBUG_EVENT    db "EXIT_PROCESS_DEBUG_EVENT",0dh,0ah,0
	g_szEXIT_THREAD_DEBUG_EVENT    db "EXIT_THREAD_DEBUG_EVENT",0dh,0ah,0
	g_szLOAD_DLL_DEBUG_EVENT    db "LOAD_DLL_DEBUG_EVENT",0dh,0ah,0
	g_szOUTPUT_DEBUG_STRING_EVENT    db "OUTPUT_DEBUG_STRING_EVENT",0dh,0ah,0
	g_szRIP_EVENT    db "RIP_EVENT",0dh,0ah,0
	g_szUNLOAD_DLL_DEBUG_EVENT    db "UNLOAD_DLL_DEBUG_EVENT",0dh,0ah,0
	g_szLoadDllFmt db "[event] Lod Dll Base:%08X ImageName:%s",0dh,0ah,0 
	g_dwLoadDllFmtLen dd $-offset g_szLoadDllFmt
	
	g_dwAddr dd 01006121h
	g_bt01dCode db 0
	
	g_bIsSystemCC dd FALSE
	
	g_szDebugFmt db "Excetion addr:%08X",0dh,0ah,0 
	g_szDebug2Fmt db "EXCEPTION_SINGLE_STEP addr:%08X",0dh,0ah,0 
.code



OnBreakPoint proc pER:ptr EXCEPTION_RECORD
	LOCAL @dwStatus:DWORD
	LOCAL @dwOldProtect:DWORD
	LOCAL @btCodeCC:BYTE
	LOCAL @dwBytesWrited:DWORD
	LOCAL @dwBytesReaded:DWORD
	LOCAL @ctx: CONTEXT
	
	
	mov @dwStatus,DBG_CONTINUE
	
	mov eax,@dwStatus
	
	mov esi,pER
	assume esi:ptr EXCEPTION_RECORD
	
	;系统断点,忽略
	.if g_bIsSystemCC==FALSE
		mov g_bIsSystemCC,TRUE
		mov eax,@dwStatus
		ret
	.endif
	
	;判断是否是自己的断点
	;判断是否是自己的断点
	mov eax,g_dwAddr
	.if [esi].ExceptionAddress == eax
		;还原原指令
		invoke VirtualProtectEx,g_hProcess,g_dwAddr,1,PAGE_EXECUTE_READWRITE,addr @dwOldProtect
		invoke WriteProcessMemory,g_hProcess,g_dwAddr,offset g_bt01dCode,type g_bt01dCode,addr @dwBytesWrited
		invoke VirtualProtectEx,g_hProcess,g_dwAddr,1,@dwOldProtect ,addr @dwOldProtect
		
		;TF置位
		invoke RtlZeroMemory,addr @ctx,type @ctx
		mov @ctx.ContextFlags,CONTEXT_ALL
		invoke GetThreadContext,g_hThread,addr @ctx
		or @ctx.regFlag,100h
		dec @ctx.regEip
		invoke SetThreadContext,g_hThread,addr @ctx
		
		mov eax,@dwStatus
		ret
		
	.endif
	
	mov eax,@dwStatus
	ret
	
	ret

OnBreakPoint endp



OnSingleStep proc pER:ptr EXCEPTION_RECORD   
	LOCAL @dwOldProtect:DWORD
	LOCAL @btCodeCC:BYTE
	LOCAL @dwBytesWrited:DWORD
	LOCAL @dwBytesReaded:DWORD
	LOCAL @dwStatus:DWORD
	
	
	;处理调试事件
	mov @dwStatus,DBG_CONTINUE
	
	
	invoke VirtualProtectEx,g_hProcess,g_dwAddr,1,PAGE_EXECUTE_READWRITE,addr @dwOldProtect
	
	mov @btCodeCC,0cch
	invoke ReadProcessMemory,g_hProcess,g_dwAddr,offset g_bt01dCode,type g_bt01dCode,addr @dwBytesReaded
	invoke WriteProcessMemory,g_hProcess,g_dwAddr,addr @btCodeCC,type @btCodeCC,addr @dwBytesWrited


	invoke VirtualProtectEx,g_hProcess,g_dwAddr,1,@dwOldProtect,addr @dwOldProtect
	
	mov eax,@dwStatus 
	ret 

OnSingleStep endp

OnException proc uses esi pDe:ptr DEBUG_EVENT
	LOCAL @dwStatus:DWORD
	LOCAL @dwOldProtect:DWORD
	LOCAL @btCodeCC:BYTE
	LOCAL @dwBytesWrited:DWORD
	LOCAL @dwBytesReaded:DWORD
	LOCAL @ctx: CONTEXT
	
	;处理调试事件
	mov @dwStatus,DBG_CONTINUE
	mov esi,pDe
	assume esi:ptr DEBUG_EVENT
	
	lea esi,[esi].u.Exception
	
	
	assume esi:ptr EXCEPTION_RECORD 
	

	
	
	
	
	
	.if [esi].ExceptionCode==EXCEPTION_BREAKPOINT
		invoke crt_printf,offset g_szDebugFmt,[esi].ExceptionAddress
		invoke OnBreakPoint,esi
		ret
	.elseif [esi].ExceptionCode==EXCEPTION_SINGLE_STEP
		invoke crt_printf,offset g_szDebug2Fmt,[esi].ExceptionAddress
		invoke OnSingleStep,esi
		ret
	.endif 
	
	
	
	
	
	assume esi:nothing
	mov eax,@dwStatus
 
 ret

OnException endp



OnCreateProcess proc uses esi pDe:ptr DEBUG_EVENT
	LOCAL @dwOldProtect:DWORD
	LOCAL @btCodeCC:BYTE
	LOCAL @dwBytesWrited:DWORD
	LOCAL @dwBytesReaded:DWORD
	
	invoke VirtualProtectEx,g_hProcess,g_dwAddr,1,PAGE_EXECUTE_READWRITE,addr @dwOldProtect
	
	mov @btCodeCC,0cch
	invoke ReadProcessMemory,g_hProcess,g_dwAddr,offset g_bt01dCode,type g_bt01dCode,addr @dwBytesReaded
	invoke WriteProcessMemory,g_hProcess,g_dwAddr,addr @btCodeCC,type @btCodeCC,addr @dwBytesWrited


	invoke VirtualProtectEx,g_hProcess,g_dwAddr,1,@dwOldProtect,addr @dwOldProtect
	
	ret
	
	
OnCreateProcess endp


OnLoadDll proc uses esi pDe:ptr DEBUG_EVENT
	LOCAL @szFileName[MAX_PATH*2 ]:CHAR
	LOCAL @dwAddr:DWORD 
	LOCAL @dwBytesReaded:DWORD
	LOCAL @szwLoadDllFmt[MAX_PATH ]:CHAR
	mov esi,pDe
	assume esi:ptr DEBUG_EVENT
	
	
	invoke ReadProcessMemory,g_hProcess,[esi].u.LoadDll.lpImageName,addr @dwAddr,type @dwAddr,addr @dwBytesReaded

	.if eax==FALSE || @dwAddr ==NULL
	 
		ret
	.endif
	
	 
	invoke ReadProcessMemory,g_hProcess,@dwAddr,addr @szFileName,MAX_PATH*2,addr @dwBytesReaded
	
	
	
	.if [esi].u.LoadDll.fUnicode ==TRUE
		invoke crt_mbstowcs,addr @szwLoadDllFmt,offset g_szLoadDllFmt,g_dwLoadDllFmtLen
		invoke crt_wprintf,addr @szwLoadDllFmt,[esi].u.LoadDll.lpBaseOfDll,addr @szFileName
	.else
		invoke crt_printf ,offset g_szLoadDllFmt,[esi].u.LoadDll.lpBaseOfDll,addr @szFileName
	.endif
	
		
	
	assume esi:nothing
	
	
	ret

OnLoadDll endp


main proc 
	LOCAL @si:STARTUPINFO
	LOCAL @pi:PROCESS_INFORMATION
	LOCAL @de:DEBUG_EVENT 
	LOCAL @dwStatus:DWORD
	
	
	
	invoke RtlZeroMemory,addr @si,type @si
	mov @si.cb,type @si
	 
	
	invoke RtlZeroMemory,addr @pi,type @pi
	
	
	
	invoke CreateProcess,offset g_szExePath,NULL,NULL,NULL,FALSE,DEBUG_ONLY_THIS_PROCESS,NULL,NULL,addr @si,addr @pi
	.if eax==FALSE
		invoke crt_printf,offset g_szErrCreateProcess
		ret
		
	.endif
	
	push @pi.hProcess
	pop g_hProcess
	
	
	.while TRUE 
		;等候调试事件
		invoke RtlZeroMemory,addr @de,type @de
		invoke WaitForDebugEvent,addr @de,INFINITE
	 	
	 	;获取线程句柄
	 	invoke OpenThread,THREAD_ALL_ACCESS,FALSE,@de.dwThreadId
	 	mov g_hThread,eax
	 	
	 	;处理调试事件
	 	mov @dwStatus,DBG_CONTINUE
	 	
	 	.if @de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
	 		invoke OnCreateProcess,addr @de
	 		invoke crt_printf,offset g_szCREATE_PROCESS_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==CREATE_THREAD_DEBUG_EVENT
	 		invoke crt_printf,offset g_szCREATE_THREAD_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==EXCEPTION_DEBUG_EVENT
	 		invoke crt_printf,offset g_szEXCEPTION_DEBUG_EVENT
	 		invoke OnException, addr @de
	 		mov @dwStatus,eax
	 		
	 	.elseif  @de.dwDebugEventCode ==EXIT_PROCESS_DEBUG_EVENT
	 		invoke crt_printf,offset g_szEXIT_PROCESS_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==EXIT_THREAD_DEBUG_EVENT
	 		invoke crt_printf,offset g_szEXIT_THREAD_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==LOAD_DLL_DEBUG_EVENT
	 		invoke OnLoadDll,addr @de
	 		invoke crt_printf,offset g_szLOAD_DLL_DEBUG_EVENT
	 	.elseif  @de.dwDebugEventCode ==OUTPUT_DEBUG_STRING_EVENT
	 		invoke crt_printf,offset g_szOUTPUT_DEBUG_STRING_EVENT
	 	.elseif  @de.dwDebugEventCode ==RIP_EVENT
	 		invoke crt_printf,offset g_szRIP_EVENT
	 	.elseif  @de.dwDebugEventCode ==UNLOAD_DLL_DEBUG_EVENT
	 		invoke crt_printf,offset g_szUNLOAD_DLL_DEBUG_EVENT
	 	.endif
	 
	 
		invoke CloseHandle,g_hThread 
		invoke ContinueDebugEvent,@de.dwProcessId,@de.dwThreadId,DBG_CONTINUE
		
	.endw
	
	
	ret

main endp



START:
 	invoke main
 	xor eax,eax
 	invoke ExitProcess,eax

end START

本小节源码地址

内存断点

硬件断点

以上是关于win32 调试器的主要内容,如果未能解决你的问题,请参考以下文章

win32 调试器

win32 api 原理

如何调试意外以静默方式终止的 win32 进程?

使用AllocConsole在Win32程序中调用控制台调试输出

[Win32]一个调试器的实现调试符号

win32调试打印