利用SEH技术实现反跟踪,这个方法比单纯用判断API函数第一个字节是否为断点更加有效,可以防止在API函数内部的多处地址设置断点
通过int 3指令故意产生一个异常,从而让系统转入自己的异常处理函数,修改CONTEXT结构的regFlag中的TF位设置为1,并且将ContextFlags修改为CONTEXT_NULL,那么当程序恢复执行时,标志寄存器TF位也将为1,这样当程序执行一条指令后,将产生EXCEPTION_SINGLE_STEP异常,系统将再次转出我们的异常处理函数中
如果每次对EXCEPTION_SINGLE_STEP异常的处理中把CONTEXT结构的regFlag中的TF位设置为1,那么程序就如同我们单步执行程序的效果一样
此时可以在EXCEPTION_SINGLE_STEP异常处理中判断当前IP处的自己是否为CC(INT 3)即可
;****************************************** ;coded by ;****************************************** ;演示检测BPX断点 ;****************************************** ;---------------------------------------asm------------------------------------------------ COMMENT $ 编译使用: \masm32\bin\ml /c /coff antibpx1.asm \masm32\bin\Link /SECTION:.text,ERW /SUBSYSTEM:WINDOWS antibpx1.obj $ .586P .MODEL FLAT,STDCALL OPTION CASEMAP:NONE include e:\masm32\include\windows.inc include e:\masm32\include\kernel32.inc include e:\masm32\include\user32.inc include e:\masm32\include\comctl32.inc includelib e:\masm32\lib\kernel32.lib includelib e:\masm32\lib\user32.lib includelib e:\masm32\lib\comctl32.lib .Data szDebugMsg db 0Dh,0Ah,0Dh,0Ah db '你可以通过在以下API函数中设置断点来进行测试:' db 0Dh,0Ah,0Dh,0Ah db 'MessageBoxA',0Dh,0Ah,'MessageBeep',0Dh,0Ah db 0Dh,0Ah,0Dh,0Ah,0 szNoFoundTracerMsg db '我没有发现被跟踪...:)',0 szFoundTracerMsg db '我发现你了!....你在跟踪我....哈哈...',0 szTitle db '样例:利用SEH技术进行反跟踪',0 SafeEsp dd 0 CallLevel dd 0 ReturnAddrEsp dd 255 dup(0) hKernel32 dd 0 szKernel32Dll db 'KERNEL32.DLL',0 szSleepEx db 'SleepEx',0 szFormat db 'KERNEL32.SleepEx : %08lX',0 szText db 255 dup(0) .Code assume fs:nothing ;---------------------------------------------主程序开始------------------------------------------------ Main: ; 建立异常处理机制:结构化异常处理 push xMyHandler push fs:[0] mov fs:[0],esp ; mov [SafeEsp],esp invoke MessageBoxA,NULL,addr szDebugMsg,addr szTitle,MB_OK ; 故意产生一个异常 int 3h ; 异常!!将被系统捕获,系统将调用我们的异常处理过程 xApiHandler nop ; 运行在 start_anti_trace 到 stop_anti_trace 之间的代码时,都处在 ; 程序通过SEH机制建立的单步调试状态,程序将对每一条指令进行识别(包括API函数中的指令), ; 如果这些指令中存在断点,都将被程序发现。 start_anti_trace: ;===反跟踪开始=== invoke MessageBeep,100 invoke LoadLibraryA,addr szKernel32Dll mov [hKernel32],eax invoke GetProcAddress,[hKernel32],addr szSleepEx invoke wsprintf,addr szText,addr szFormat,eax invoke MessageBoxA,NULL,addr szText,addr szTitle,MB_OK stop_anti_trace: ;===反跟踪结束=== ; 如果在以上进行反跟踪的代码执行中没有设置断点,程序将执行到此处, ; 并且显示"没有发现跟踪者...:)"的提示信息 invoke MessageBoxA,NULL,addr szNoFoundTracerMsg,addr szTitle,MB_OK jmp stop_self_trace_addr found_tracer: ; 如果在以上进行反跟踪的代码执行中发现设置断点,程序将执行到此处, ; 并且显示"我发现你了....!"等提示信息(可能仅在此测试样例中我会这样做 :) invoke MessageBoxA,NULL,addr szFoundTracerMsg,addr szTitle,MB_OK stop_self_trace_addr: ; 解除自己建立的SEH结构化异常处理,然后结束进程 mov esp,[SafeEsp] pop fs:[0] add esp,4 invoke ExitProcess,0 ;---------------------------------------------主程序结束------------------------------------------------ ;--------------------------------------------异常处理函数------------------------------------------------ xMyHandler proc C uses ebx esi edi pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD mov esi,pExcept assume esi:ptr EXCEPTION_RECORD mov edi,pContext assume edi:ptr CONTEXT ; 如果发生严重错误,则不进行处理直接返回,从而转向下一级异常处理程序 test [esi].ExceptionFlags,1 jnz lm0_continue_search ; 如果异常进行展开,则不进行处理直接返回。 test [esi].ExceptionFlags,6 jnz lm0_unwind ; 对"软断点异常"进行处理 cmp [esi].ExceptionCode,EXCEPTION_BREAKPOINT jz lm0_start_self_trace ; 对"单步异常"进行处理 cmp [esi].ExceptionCode,EXCEPTION_SINGLE_STEP jz lm0_self_trace ; 其他情况,都直接返回,转向下一级异常处理程序 lm0_continue_search: lm0_unwind: mov eax,ExceptionContinueSearch jmp lm0_ret lm0_start_self_trace: ; 开始启动单步自跟踪,即初始化变量、设置TF标志等 ;mov [CallLevel],0 ; 初始化 CALL层级 inc [edi].regEip lm0_trap_it:or byte ptr [edi+1].regFlag,01h ; 设置 TF 标志 lm0_modify_drx_reg: mov [edi].iDr0,0 and [edi].iDr6,0FFFF0FF0h mov [edi].iDr7,0h ; 清除调试寄存器设置的信息 mov [edi].ContextFlags,CONTEXT_FULL OR CONTEXT_DEBUG_REGISTERS jmp lm0_continue_exec lm0_self_trace: ; 判断是否自跟踪到达结束地址,是,则停止单步自跟踪 mov ebx,[edi].regEip cmp ebx,stop_anti_trace jnz @F ; 不是,则跳转 mov [CallLevel],0 ; 初始化CALL层级 and byte ptr [edi+1].regFlag,00h ; 复位 TF 标志,停止单步自跟踪 jmp lm0_modify_drx_reg ; @@: lea eax,[ebx+5] ; 获取下下一条要执行的指令地址到 EAX 寄存器 cmp byte ptr [ebx],0E8h ; 判断下一条要执行的指令是否为 CALL x 指令 jz lm0_do_call_instr inc eax cmp word ptr [ebx],015FFh ; 判断下一条要执行的指令是否为 CALL [x] 指令 jz lm0_do_call_instr cmp byte ptr [ebx],0C2h ; 判断下一条要执行的指令是否为 ret n 指令 jz lm0_do_ret_instr cmp byte ptr [ebx],0C3h ; 判断下一条要执行的指令是否为 retn 指令 jz lm0_do_ret_instr cmp byte ptr [ebx],0CCh ; 判断下一条要执行的指令是否为 INT 3h 指令 jz lm0_do_int3_instr cmp word ptr [ebx],08964h ; 判断下一条要执行的指令是否为修改FS:[0]之类指令 jz lm0_do_fs_instr jmp lm0_trap_it ; 都不是以上的指令,则继续进行单步自跟踪 lm0_do_fs_instr: ; mov al,byte ptr [ebx+2] and al,11100111b cmp al,00000101b ; 判断下一条要执行的指令是否为 mov fs:[0],reg 指令 jnz lm0_do_other_fs_instr movzx eax,byte ptr [ebx+2] and eax,00011000b shr eax,3 not eax and eax,00000011b mov eax,[edi+eax*4].regEbx cmp eax,xMyHandler jz @F mov eax,[eax+4] mov [ApiHandler],eax jmp lm0_skip_this_instr @@: mov [ApiHandler],0 lm0_skip_this_instr: lea eax,[ebx+7] mov [edi].regEip,eax jmp lm0_trip_it lm0_do_other_fs_instr: ; 针对其他修改FS:[x]的指令,改用调试寄存器断点跟踪 mov ebx,[CallLevel] mov eax,[ReturnAddrEsp+ebx*4] mov eax,[eax] ; 取得最后一个CALL调用的返回地址 dec [CallLevel] lm0_bp_trace: mov [edi].iDr0,eax ; 在CALL指令的返回地址处设置调试寄存器断点 and [edi].iDr6,0FFFF0FF0h mov [edi].iDr7,155h mov [edi].ContextFlags,CONTEXT_FULL OR CONTEXT_DEBUG_REGISTERS jmp lm0_continue_exec ; 继续 lm0_do_int3_instr: mov [edi].regEip,found_tracer ; 发现设置API函数断点,则修改CONTEXT.EIP mov eax,[SafeEsp] mov [edi].regEsp,eax ; 修改CONTEXT.ESP jmp lm0_modify_drx_reg ; lm0_do_ret_instr: dec [CallLevel] ; 下一步将执行 ret 指令, CALL层级 减一 jmp lm0_trap_it ; 继续进行单步自跟踪 lm0_do_call_instr: ; 通过判断CALL层级,可以知道是否已经执行进入到API函数内 cmp [CallLevel],0 ; jnz lm0_bp_trace ; 对API函数内部的CALL调用不再进行单步自跟踪, ; 而改用调试寄存器断点自跟踪方式 inc [CallLevel] ; 下一步将执行 CALL 指令, CALL 层级 加一 mov ebx,[CallLevel] mov eax,[edi].regEsp sub eax,4 ; 计算 CALL 返回地址的CONTEXT.ESP堆栈指针 mov [ReturnAddrEsp+ebx*4],eax ; 保存 jmp lm0_trap_it ; 继续进行单步自跟踪 lm0_continue_exec: mov eax,ExceptionContinueExecution lm0_ret: ret xMyHandler endp End Main ;End of code, Main is the entrypoint