复杂但究其根源的方法是修改程序的PEB,将调试位置0。比较方便的方法为先通过dg获取fs寄存器指向的基址,然后通过eb指令将对应位置置0。
0:000> dg fs P Si Gr Pr LoSel Base Limit Type l ze an es ng Flags---- -------- -------- ---------- - -- -- -- -- --------0053 00e05000 00000fff Data RW Ac 3 Bg By P Nl 000004f30:000> eb poi(00e05000+0x30)+2 0
点击继续无法运行,一直卡在了异常报错处。在Windbg中也好不到哪去
0:000> g(1f84.3ea0): Integer divide-by-zero - code c0000094 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=00000006 ebx=0048b000 ecx=a5e70000 edx=00000000 esi=0032fa08 edi=0032faf0eip=003b19a5 esp=0032fa08 ebp=0032faf0 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246Breakpoints!main+0x75:003b19a5 f73538a13b00 div eax,dword ptr [Breakpoints!flag (003ba138)] ds:002b:003ba138=000000000:000> g(1f84.3ea0): Integer divide-by-zero - code c0000094 (!!! second chance !!!)eax=00000006 ebx=0048b000 ecx=a5e70000 edx=00000000 esi=0032fa08 edi=0032faf0eip=003b19a5 esp=0032fa08 ebp=0032faf0 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246Breakpoints!main+0x75:003b19a5 f73538a13b00 div eax,dword ptr [Breakpoints!flag (003ba138)] ds:002b:003ba138=00000000在没有调试器的情况下程序跑得很欢:
成了,十分符合题目的要求,接下来开搞。
失败的尝试-1
首先联想到的是其底层实现可能与IsDebugPresent一样,通过检测PEB中的BeingDebugging位来判断是否将异常传递给调试器。那就改来试试
0:000> dg fs P Si Gr Pr LoSel Base Limit Type l ze an es ng Flags---- -------- -------- ---------- - -- -- -- -- --------0053 009a1000 00000fff Data RW Ac 3 Bg By P Nl 000004f30:000> eb poi(009a1000+0x30)+2 00:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (first chance)0:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (!!! second chance !!!)死循环,看来系统的异常分发机制与该标志位无关。改下EIP强行跳转到下一条语句?
0:000> r @eip=003b19ab0:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (first chance)eip=003b19a50:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (!!! second chance !!!)eip=003b19a5不仅还是陷入了死循环,而且EIP还跳回去了。
失败的尝试-2
在windbg中有一条call指令可以直接调用某个地址的方法,格式为.call API(Param1, Param2,..)。如果在引发异常的时候直接调用异常处理API能不能绕过呢?不过异常处理函数的原型为LONG WINAPI UnhandledExcept(EXCEPTION_POINTERS *pExpInfo),参数需要一个指向异常信息的EXCEPTION_POINTERS指针。其原型为
typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord;} EXCEPTION_POINTERS, *PEX在内存里搜搜看
0:000> .excrUnable to get exception context, HRESULT 0x8000FFFF0:000> .exr -1ExceptionAddress: 003b19a5 (Breakpoints!main+0x00000075)ExceptionCode: c0000094 (Integer divide-by-zero)ExceptionFlags: 00000000NumberParameters: 00:000> s -d esp L1000 0xc000009400b3f948 c0000094 00000000 00000000 003ba57c ............|.;.00b3f9b4 c0000094 753fa0e0 56fb63e5 2ab7db1f ......?u.c.V...*0:000> .exr 00b3f948ExceptionAddress: 003ba57c (Breakpoints!__dyn_tls_dtor_callback)ExceptionCode: c0000094 (Integer divide-by-zero)ExceptionFlags: 00000000NumberParameters: 39090000:000> .exr 00b3f9b4ExceptionAddress: 2ab7db1fExceptionCode: c0000094 (Integer divide-by-zero)ExceptionFlags: 753fa0e0NumberParameters: 0在内存中能搜索到结构体的第一个元素ExceptionRecord,但是指向其的指针却没有任何踪迹,总不能按每4个字节全部解引用一遍看看内容吧。
于是尝试传个0作为参数,手动跳过流程,改改寄存器变量值:
0:000> .call Breakpoints!UnhandledExcept(0)Thread is set up for call, 'g' will execute.WARNING: This can have serious side-effects,including deadlocks and corruption of the debuggee.0:000> t(2668.1f30): Integer divide-by-zero - code c0000094 (first chance)0:000> t(2668.1f30): Integer divide-by-zero - code c0000094 (!!! second chance !!!)不行,无法跳转,EIP已经被锁死了,只能从异常分发机制下手,而非对寄存器或执行顺序进行魔改。
32位
搜索了一番与反调试相关的文章教程,发现了一篇挺不错的文章[How to debug UnhandleExceptionHandler!],刚好适用于这种情况。文章的第一步
Parameters
ProcessHandleA handle to the process for which information is to be retrieved.
ProcessInformationClassThe type of process information to be retrieved. This parameter can be one of the following values from the PROCESSINFOCLASS enumeration.
ValueMeaningProcessBasicInformation 0Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, and a unique value used by the system to identify the specified process.Use the CheckRemoteDebuggerPresent and GetProcessId functions to obtain this information.ProcessDebugPort 7Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process is being run under the control of a ring 3 debugger.Use the CheckRemoteDebuggerPresent or IsDebuggerPresent function.
第一个参数为进程的句柄,第二个参数是选定查询信息的类型,第三个参数是指向返回查询结果的指针,第四个参数是信息的长度,第五个参数是返回信息的长度。看看传入的参数信息:
0:000> kbChildEBP RetAddr Args to Child 001af6f4 770eef55 ffffffff 00000007 001af710 ntdll!NtQueryInformationProcess0:000> dd 001af6f4+8001af6fc ffffffff 00000007 001af710 00000004001af70c 00000000 00000000 001af798 770eed8c第一个参数FFFFFFFF是一个伪句柄,只适用于线程内部使用,指向其自身。第二个参数7根据表格意为查询调试端口信息,返回地址为001af710。通过查看EPROCESS结构体发现其中有DEBUG_PORT位域,推测与其有关。
0:000> .processImplicit process is now 7ffd40000:000> dt _EPROCESS 7ffd4000ntdll!_EPROCESS ...... +0x0ec DebugPort : (null) .....尝试在该位域下一个读写硬件断点,但是函数执行完毕后没有中断,看来与其无关
0:000> ba r4 7ffd4000++0x0ec0:000> peax=000000ea ebx=001af7c8 ecx=001af71d edx=777f70b4 esi=00000000 edi=00000000eip=777f604d esp=001af6f8 ebp=001af714 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246ntdll!NtQueryInformationProcess+0x5:777f604d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)0:000> peax=000000ea ebx=001af7c8 ecx=001af71d edx=7ffe0300 esi=00000000 edi=00000000eip=777f6052 esp=001af6f8 ebp=001af714 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246ntdll!NtQueryInformationProcess+0xa:777f6052 ff12 call dword ptr [edx] ds:0023:7ffe0300={ntdll!KiFastSystemCall (777f70b0)}0:000> peax=00000000 ebx=001af7c8 ecx=001af6f4 edx=777f70b4 esi=00000000 edi=00000000eip=777f6054 esp=001af6f8 ebp=001af714 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246ntdll!NtQueryInformationProcess+0xc:777f6054 c21400 ret 14h函数执行完毕后,查看第三个参数指向的地址,非0,意味着系统监测到该程序处于ring3级别的调试器下,监测的方法未知。
0:000> dd 001af710 L1001af710 ffffffff调用完毕后EAX = 0。如果在此时将EAX置为0x80000000,执行完test指令后,zf=1 sf=1, of=0,满足下一个jl的跳转条件,便可绕过调试器监测。
770eef4f ff154c150977 call dword ptr [kernel32!_imp__NtQueryInformationProcess (7709154c)]770eef55 85c0 test eax,eax770eef57 7c0a jl kernel32!BasepIsDebugPortPresent+0x2b (770eef63)770eef59 837dfc00 cmp dword ptr [ebp-4],0770eef5d 0f85f7d20100 jne kernel32!BasepIsDebugPortPresent+0x26 (7710c25a)770eef63 33c0 xor eax,eax770eef65 c9 leave770eef66 c3 ret770eef67 90 nop
不作处理,接着跳转到程序的末尾,API通过清空EAX并自增来返回1这个状态,反汇编如下
7710c25a 33c0 xor eax,eax7710c25c 40 inc eax7710c25d c9 leave7710c25e c3 ret在返回前将EAX清空也是一个绕过的方法。
64位
上面网站的教程是32位的,在64位的WIN10下并没有kernel32!UnhandledExceptionFilter这个API。
至此本来打算告一段落了,突然想起,如果在对ntdll!NtQueryInformationProcess下个断,会不会有意外收获?
0:000> bu ntdll!NtQueryInformationProcess0:000> gBreakpoint 3 hiteax=011bf7d8 ebx=00000000 ecx=011bf7dc edx=011bf7e0 esi=011bf860 edi=011bfed8eip=774d0610 esp=011bf7a8 ebp=011bf848 iopl=0 nv up ei ng nz na po cycs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000283ntdll!NtQueryInformationProcess:774d0610 b819000000 mov eax,19h嗯,一模一样...但为什么没有kernel32!UnHandledExceptionFilter这个API,看了看调用堆栈明白了...全新的异常分发调用,走的是KERNELBASE!UnhandledExceptionFilter。
0:000> kp # ChildEBP RetAddr 00 003af0b0 754f9d74 ntdll!NtQueryInformationProcess01 003af0d4 754fa1d1 KERNELBASE!BasepIsDebugPortPresent+0x1d02 003af16c 77502fff KERNELBASE!UnhandledExceptionFilter+0xf103 003af9dc 774c65fd ntdll!__RtlUserThreadStart+0x3ca0104 003af9ec 00000000 ntdll!_RtlUserThreadStart+0x1b还是一样的配方,还是熟悉的味道,修改KERNELBASE!BasepIsDebugPortPresent的返回结果,大功告成。