主页 > 手机  > 

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

Windows平台调试器原理与编写03.单步
调试器原理与编写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.单步由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Windows平台调试器原理与编写03.单步