前言
本教程主旨用于编写一个OD插件修复部分问题或者解析反调试问题.
官方文档
异常过滤器的反调试插件
当触发异常时首先会将异常交付给SEH
然后再交付到 SetUnhandledExceptionFilter
,不管是哪个阶段当存在调试器
时,首先会交付给调试器
,但是在SEH阶段调试器可以把错误交付给被调试程序,但是位于 SetUnhandledExceptionFilter
却不可以。因为我们需要修改一些系统判断逻辑从而实现调试。
首先给出一个Demo
,这个Demo使用了SetUnhandledExceptionFilter
机制进行了反调试,由于不是本文的重点所以不过多介绍。
.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.libWinMain proto :DWORD,:DWORD,:DWORD,:DWORD.dataClassName db "MainWinClass",0AppName db "Main Window",0g_szCC db "这里有CC断点",0g_szover db "程序结束",0g_sz1 db "非法程序后的一个窗口",0g_sz2 db "非法程序后的二个窗口",0g_sz3 db "检测到单步",0g_sz4 db "检测到返回",0g_sz5 db "异常返回",0g_sz6 db "测试",0g_sz7 db "非法程序后的三个窗口",0g_sz8 db 1
.data?hInstance HINSTANCE ?CommandLine LPSTR ?.codeMyUnhandledExceptionFilter proc pExceptionInfo:ptr EXCEPTION_POINTERSLOCAL @pContext:ptr PCONTEXTLOCAL @pExpRecord:ptr PEXCEPTION_RECORDmov ebx,pExceptionInfoassume ebx:ptr EXCEPTION_POINTERSpush [ebx].ContextRecordpop @pContextpush [ebx].pExceptionRecordpop @pExpRecordassume ebx:nothingmov ebx,@pExpRecordassume ebx:ptr EXCEPTION_RECORD;跳过内存访问异常的指令mov edx,@pContextassume edx:ptr CONTEXTmov ecx,0.if [ebx].ExceptionCode == EXCEPTION_ACCESS_VIOLATIONadd [edx].regEip,2;设置单步or [edx].regFlag,100hinvoke MessageBox,NULL,offset g_sz6,NULL,MB_OK.elseif [ebx].ExceptionCode == EXCEPTION_SINGLE_STEP;判断是否有ccmov eax,[edx].regEip.if byte ptr [eax] == 0cch; ;说明有ccinvoke MessageBox,NULL,offset g_szCC,NULL,MB_OK;检测到int3指令,证明被注入程序退出mov eax,EXCEPTION_EXECUTE_HANDLERret; .elseif byte ptr [eax] == 0C3h; ;如果是ret,则不设置单步; invoke MessageBox,NULL,offset g_sz4,NULL,MB_OK.else.if g_sz8==1mov g_sz8,0mov [edx].regEip,offset _SafePlace2;设置单步tf标志位or [edx].regFlag,100h.endif.endif.endifassume ebx:nothingassume edx:nothingmov eax,EXCEPTION_CONTINUE_EXECUTIONretMyUnhandledExceptionFilter endp; ---------------------------------------------------------------------------MyProtoctFun procmov eax,0ffffffffhmov eax,[eax]invoke MessageBox,NULL,offset g_sz1,NULL,MB_OKmov eax,0mov eax,0mov eax,0_SafePlace3: invoke MessageBox,NULL,offset g_sz7,NULL,MB_OKretMyProtoctFun endpstart:invoke SetUnhandledExceptionFilter,offset MyUnhandledExceptionFilterinvoke MyProtoctFun_SafePlace2: invoke MessageBox,NULL,offset g_sz2,NULL,MB_OKmov g_sz8,1invoke MyProtoctFunmov eax,0mov eax,0mov eax,0invoke MessageBox,NULL,offset g_szover,NULL,MB_OKend start
我们需要提前理解一个知识点,(异常过滤器阶段)当触发异常时系统会调用UnhandledExceptionFilter
函数在这个函数内部中又会调用ZwQueryInformationProcess
函数判断是否被调试。
ZwQueryInformationProcess 函数文档链接
我们的思路如下:
修改jnz 7732E179
这个指令为NOP
即可完成任务,在默认情况如果被调试这个指令会被执行跳转。
在捋清楚思路之后我们就开始编写对应的OD
插件把.这个插件您可以用c语言编写也可以用汇编编写可以根据您的个人习惯。
若为的OD插件只不过是一个dll库罢了,不过你需要按照官方的说明导出对应的函数
- 下载所需文件
- 编写对应的导出函数
OD
提供了多个导出函数这里我们举例本例子用到的几个
_ODBG_Plugininit
导出函数用于被od加载时回调,你可以在这个函数里面做初始化操作,如果初始化成功返回0
ODBG_Plugindata
用于告诉OD这个插件支持的版本
ODBG_Paused
OD被执行暂停的时候回调,会传入被暂停的原因
我们这里给出一个汇编版本
;odDllPlugin.Def
EXPORTS_ODBG_Plugindata=ODBG_Plugindata_ODBG_Plugininit=ODBG_Plugininit_ODBG_Paused=ODBG_Paused;Plugin.Inc
PLUGIN_VERSION equ 110
PP_MAIN equ 0003h
PP_EVENT equ 0000h
PP_PAUSE equ 0001h
PP_TERMINATED equ 0002h MM_RESTORE equ 01h
MM_SILENT equ 02h
MM_DELANAL equ 04h MM_RESILENT equ (MM_RESTORE or MM_SILENT)_Readmemory proto C :ptr, :dword,:dword, :dword
_Writememory proto C :ptr, :dword,:dword, :dword
最重要的实现文件
.586
.model flat,stdcall
option casemap:noneinclude Plugin.Incinclude windows.incinclude user32.incinclude kernel32.incinclude msvcrt.incincludelib user32.libincludelib kernel32.libincludelib msvcrt.lib.datag_szPluginName db "myplugin",0g_szformatmsg db "地址为%x",0g_szKernelBase db "KernelBase",0 g_szUnhandledExceptionFilter db "UnhandledExceptionFilter",0g_aryCodeNop db 6 dup(90h)g_szprint db 100 dup(0h)g_dwFixAddr dd 0
.code;int _export cdecl ODBG_Plugindata(char shortname[32]);ODBG_Plugindata proc C shortname:LPSTRinvoke crt_strcpy,shortname,offset g_szPluginNamemov eax,PLUGIN_VERSIONretODBG_Plugindata endp;extc int _export cdecl ODBG_Plugininit(int ollydbgversion,HWND hw,
; ulong *features);ODBG_Plugininit proc C ollydbgversion:dword,hw:dword,features:dwordLOCAL @hKernalbase:dwordinvoke GetModuleHandle,offset g_szKernelBasemov @hKernalbase,eaxinvoke GetProcAddress,@hKernalbase,offset g_szUnhandledExceptionFilteradd eax,0bdhmov g_dwFixAddr,eaxxor eax,eaxretODBG_Plugininit endpODBG_Paused proc C reason:dword,reg:dwordLOCAL @btCode:byte.if reason==PP_EVENTinvoke _Readmemory,addr @btCode,g_dwFixAddr,1,MM_RESILENT.if @btCode != 90hinvoke crt_sprintf,offset g_szprint,offset g_szformatmsg,g_dwFixAddrinvoke MessageBox,NULL,offset g_szprint,NULL,MB_OKinvoke _Writememory,offset g_aryCodeNop,g_dwFixAddr,size g_aryCodeNop, MM_RESILENT or MM_DELANAL.endif.endifmov eax,1ret
ODBG_Paused endpDllMain proc hinstDLL:HINSTANCE, fdwReason:DWORD,lpReserved:LPVOIDmov eax,TRUEret
DllMain endpend DllMain
记得在编译的时候加上OLLYDBG.LIB
库即可,这个库位于第一步中的压缩包中。
OD 窗口过程函数bug
我们知道OD有一个可以检查出程序的窗口过程函数,我们当然也可以用Spy++去查看
OD截图:
SPY++截图
一切看起来都非常正常。
我们现在使用一个unicode程序呢?
OD:
对于unicode
程序发现OD
并没有正确获取到窗口过程函数
.
原因
由于OD获取窗口过程都使用多字节编码函数而不是根据目标语言选择因此存在问题。
我们首先看看GetClassLongA
在这个OD中的调用处
前面两处调用 00497a03
和00479b23
最后都是调用到004af420
,这里的原因是got和ptl重定向的原因,我们不需深究。
我们直接看004af420
处汇编
我们只需要替换50d858此处内容的地址即可完成。
MyGetClassLong proc hwnd:HWND,index:DWORDinvoke IsWindowUnicode,hwnd.if eax ==TRUEinvoke GetClassLongW,hwnd,indexret.endifinvoke GetClassLongA,hwnd,indexretMyGetClassLong endpODBG_Plugininit proc C ollydbgversion:dword,hw:dword,features:dwordLOCAL @pGetAddr:dwordLOCAL @dwOlddprotect:dwordmov @pGetAddr,0050D858hinvoke VirtualProtect,@pGetAddr,1,PAGE_EXECUTE_READWRITE,addr @dwOlddprotectmov eax,@pGetAddrmov dword ptr [eax],offset MyGetClassLonginvoke VirtualProtect,@pGetAddr,1,@dwOlddprotect,addr @dwOlddprotectxor eax,eaxretODBG_Plugininit endp