利用Windows异常处理和RDTSC指令反调试学习
最近在学习反调试,刚好学到用rdtsc指令反调试。学习加密解密这么久了,感觉自己对Windows的异常处理还是了解得不透彻,所以决心自己好好学习跟踪一把。
于是自己写了个小对话框程序,添加了一个按钮,按钮单击代码如下:
voidCAnti_debugDlg::OnTest()
{
// TODO: Add your control notificationhandler code here
int i_Debug = -1;
unsigned int delay = 0;
__asm
{
pushoffset handler
pushdword ptr fs:[0]
movfs:[0],esp ;注册一个异常处理
rdtsc
push eax ;时间1入栈
xor eax, eax
xor [eax], ebx ;触发异常
rdtsc
sub eax, [esp] ;时间间隔
add esp, 4 ;时间1出栈
pop dword ptrfs:[0]
add esp, 4 ;注销异常处理
cmp eax, 50000h ;若是大于说明存在调试
jb not_debugged
debugged:
mov i_Debug, 1
jmp end
not_debugged:
mov i_Debug, 0
jmp end
handler:
mov ecx,[esp+0Ch] ;_CONTEXT结构
add dword ptr[ecx+0B8h], 2 ;修改_CONTEXT结构中的EIP
xor eax, eax
ret
End: mov delay, eax
}
SetDlgItemInt(IDC_EDIT1, delay, FALSE);
if (i_Debug)
{
MessageBox("The programis being debuged!", ">_<!!", MB_ICONWARNING);
}
else
{
MessageBox("The programis NOT being debuged!", ">_<!!", MB_OK);
}
}
先解释一下RDTSC指令:
RDTSC(Read Time Stamp Count),将计算机启动以来的CPU运行周期数放到EDX:EAX里面,EDX是高位,EAX是低位。这样我们就可以两次获取CPU执行周期数,相减得到中间执行花费了多少时间。在debug等待我们处理的时候,CPU采用多任务处理的机制,将这个线程挂起,把CPU的时间片分给了其他线程,因为实在是太快了导致就象多个线程同时运行一样,而停在push eax的时候虽然时间很短0.0几秒,但是CPU的时间片却已经轮回了很多次。所以这条指令可以用来反调试。但是该指令对于多CPU可能存在误差,详细请参考:http://blog.csdn.net/Solstice/article/details/5196544
用Windbg加载源码调试,很方便,运行,捕获了一个异常:
(744.6c4):Access violation - code c0000005 (first chance)
First chanceexceptions are reported before any exception handling.
Thisexception may be expected and handled.
eax=00000000ebx=00000000 ecx=0012fe74 edx=00000a40 esi=00143948 edi=0012f6a0
eip=00401cb6esp=0012f620 ebp=0012f6a0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
***WARNING: Unable to verify checksum for anti_debug.exe
anti_debug!CAnti_debugDlg::OnTest+0x36:
00401cb63118 xor dword ptr [eax],ebx ds:0023:00000000=????????
单步进入异常处理:
首先进入的函数是ntdll!KiUserExceptionDispatcher,整个函数代码如下:
ntdll!KiUserExceptionDispatcher:
7c92eaec8b4c2404 mov ecx,dword ptr [esp+4]
7c92eaf08b1c24 mov ebx,dword ptr [esp]
7c92eaf351 push ecx
7c92eaf453 push ebx
7c92eaf5 e8c78c0200 call ntdll!LdrAddRefDll+0x1a8 (7c9577c1)//本例这个函数调用返回1
7c92eafa0ac0 or al,al
7c92eafc740c je ntdll!KiUserExceptionDispatcher+0x1e(7c92eb0a)
7c92eafe5b pop ebx
7c92eaff59 pop ecx
7c92eb006a00 push 0
7c92eb0251 push ecx
7c92eb03 e811ebffff call ntdll!ZwContinue (7c92d619)//跳过这个函数的话则直接回到异常发生的下一个指令了
7c92eb08eb0b jmp ntdll!KiUserExceptionDispatcher+0x29(7c92eb15)
7c92eb0a5b pop ebx
7c92eb0b59 pop ecx
7c92eb0c6a00 push 0
7c92eb0e51 push ecx
7c92eb0f53 push ebx
7c92eb10e83df7ffff call ntdll!NtRaiseException (7c92e252)
7c92eb1583c4ec add esp,0FFFFFFECh
7c92eb18890424 mov dword ptr [esp],eax
7c92eb1bc744240401000000 mov dword ptr[esp+4],1
7c92eb23895c2408 mov dword ptr [esp+8],ebx
7c92eb27c744241000000000 mov dword ptr[esp+10h],0
7c92eb2f54 push esp
7c92eb30e877000000 call ntdll!RtlRaiseException (7c92ebac)
7c92eb35c20800 ret 8
ntdll!ZwContinue: //此函数的功能应该就是回到原程序异常处,继续执行
7c92d619b820000000 mov eax,20h
7c92d61eba0003fe7f mov edx,offset SharedUserData!SystemCallStub(7ffe0300)
7c92d623ff12 call dword ptr [edx] ds:0023:7ffe0300={ntdll!KiFastSystemCall(7c92eb8b)}
7c92d625c20800 ret 8
在异常函数那里下一个断点,发现在步过call ntdll!LdrAddRefDll+0x1a8函数的时候会中断在异常处理函数,说明异常处理函数是在该函数里调用的。展开该函数:
7c9577c18bff mov edi,edi
7c9577c355 push ebp
7c9577c48bec mov ebp,esp
7c9577c683ec64 sub esp,64h
7c9577c956 push esi
7c9577caff750c push dword ptr [ebp+0Ch]
7c9577cd8b7508 mov esi,dword ptr [ebp+8]
7c9577d056 push esi
7c9577d1c645ff00 mov byte ptr [ebp-1],0
7c9577d5 e8c2ffffff call ntdll!LdrAddRefDll+0x183 (7c95779c) //eax = 0
7c9577da84c0 test al,al
7c9577dc0f8584720100 jne ntdll!RtlInitializeSListHead+0x15a56(7c96ea66)
7c9577e253 push ebx
7c9577e38d45f4 lea eax,[ebp-0Ch]
7c9577e650 push eax
7c9577e78d45f8 lea eax,[ebp-8]
7c9577ea50 push eax
7c9577eb e81cc1fcff call ntdll!RtlCaptureContext+0xc7 (7c92390c)
7c9577f0 e838c1fcff call ntdll!RtlCaptureContext+0xe8 (7c92392d)
7c9577f583650800 and dword ptr [ebp+8],0
7c9577f98bd8 mov ebx,eax
7c9577fb83fbff cmp ebx,0FFFFFFFFh
7c9577fe0f848f000000 je ntdll!LdrAddRefDll+0x27a (7c957893) [br=0]//不跳
7c95780457 push edi
7c9578053b5df8 cmp ebx,dword ptr [ebp-8]
7c9578080f821d32ffff jb ntdll!RtlIdentifierAuthoritySid+0x47(7c94aa2b)
7c95780e 8d4308 lea eax,[ebx+8] //此处应该是异常处理函eax = 0x12f62c
7c9578113b45f4 cmp eax,dword ptr [ebp-0Ch] //和栈底地址比较
7c9578140f871132ffff ja ntdll!RtlIdentifierAuthoritySid+0x47(7c94aa2b)
7c95781af6c303 test bl,3 //ebx=0x12f624是不是4的倍数
7c95781d0f850832ffff jne ntdll!RtlIdentifierAuthoritySid+0x47(7c94aa2b)
7c9578238b4304 mov eax,dword ptr [ebx+4] //异常处理函数地址
7c9578263b45f8 cmp eax,dword ptr [ebp-8] //0x12d0000
7c9578297209 jb ntdll!LdrAddRefDll+0x21b (7c957834)
7c95782b3b45f4 cmp eax,dword ptr [ebp-0Ch] //0x130000
7c95782e0f82f731ffff jb ntdll!RtlIdentifierAuthoritySid+0x47(7c94aa2b)
7c95783450 push eax
7c957835 e867000000 call ntdll!LdrAddRefDll+0x288 (7c9578a1) // eax=0012f201
7c95783a84c0 test al,al
7c95783c0f84e931ffff je ntdll!RtlIdentifierAuthoritySid+0x47(7c94aa2b)
7c957842f6055ac3997c80 test byte ptr [ntdll!NlsMbOemCodePageTag+0x342(7c99c35a)],80h
7c957849 0f8520720100 jne ntdll!RtlInitializeSListHead+0x15a5f (7c96ea6f)
7c95784fff7304 push dword ptr [ebx+4] //异常函数地址
7c9578528d45ec lea eax,[ebp-14h]
7c95785550 push eax
7c957856ff750c push dword ptr [ebp+0Ch]
7c95785953 push ebx
7c95785a56 push esi
7c95785b e8f3befcff call ntdll!RtlConvertUlongToLargeInteger+0xe (7c923753)//此函数调用了异常处理过程,返回后一直执行至末尾
7c957860f6055ac3997c80 test byte ptr [ntdll!NlsMbOemCodePageTag+0x342(7c99c35a)],80h
7c9578678bf8 mov edi,eax
7c9578690f8516720100 jne ntdll!RtlInitializeSListHead+0x15a75(7c96ea85)
7c95786f395d08 cmp dword ptr [ebp+8],ebx
7c9578720f841b720100 je ntdll!RtlInitializeSListHead+0x15a83(7c96ea93)
7c9578788bc7 mov eax,edi
7c95787a33c9 xor ecx,ecx
7c95787c2bc1 sub eax,ecx
7c95787e0f858631ffff jne ntdll!RtlIdentifierAuthoritySid+0x26(7c94aa0a)
7c957884f6460401 test byte ptr [esi+4],1
7c9578880f854f720100 jne ntdll!RtlInitializeSListHead+0x15acd(7c96eadd)
7c95788ec645ff01 mov byte ptr [ebp-1],1
7c9578925f pop edi
7c9578935b pop ebx
7c957894 8a45ff mov al,byte ptr [ebp-1]
7c9578975e pop esi
7c957898c9 leave
7c957899c20800 ret 8
call ntdll!RtlConvertUlongToLargeInteger+0xe(函数里边又嵌套了2层调用,下面仅列出最里边的最关键的地方):
7c92379955 push ebp
7c92379a8bec mov ebp,esp
7c92379cff750c push dword ptr [ebp+0Ch]
7c92379f 52 push edx
7c9237a0 64ff3500000000 push dword ptr fs:[0]
7c9237a7 64892500000000 mov dword ptr fs:[0],esp //注册一个异常处理
7c9237ae ff7514 push dword ptr [ebp+14h]// void * DispatcherContext
7c9237b1 ff7510 push dword ptr [ebp+10h]// struct _CONTEXT *ContextRecord,
7c9237b4 ff750c push dword ptr [ebp+0Ch]// void * EstablisherFrame,
7c9237b7 ff7508 push dword ptr [ebp+8] // struct _EXCEPTION_RECORD *ExceptionRecord
7c9237ba 8b4d18 mov ecx,dword ptr [ebp+18h]
7c9237bd ffd1 call ecx {anti_debug!CAnti_debugDlg::OnTest+0x66 (00401ce6)} //异常处理回调函数
7c9237bf648b2500000000 mov esp,dword ptr fs:[0]
7c9237c6648f0500000000 pop dword ptr fs:[0]
7c9237cd8be5 mov esp,ebp
7c9237cf5d pop ebp
7c9237d0c21400 ret 14h
进入异常处理后,看看相应的参数:
0:000>dd esp l8
0012f250 7c9237bf 0012f338 0012f624 0012f354
0012f260 0012f30c 0012f624 7c9237d8 0012f624
0x0012f338指向_EXCEPTION_RECORD,0x0012f354指向_CONTEXT
先加载一下符号:srv*c:\symbolslocal*http://msdl.microsoft.com/download/symbols
让我们来看下windbg的解释:
0:000>dt _EXCEPTION_RECORD 0x12f338
MSVCRTD!_EXCEPTION_RECORD
+0x000 ExceptionCode : 0xc0000005 //违反访问
+0x004 ExceptionFlags : 0
+0x008 ExceptionRecord : (null)
+0x00c ExceptionAddress : 0x00401cb6 //异常发生时的地址
+0x010 NumberParameters : 2
+0x014ExceptionInformation : [15] 1
0:000>dt _CONTEXT 0x12f354
MSVCRTD!_CONTEXT
+0x000 ContextFlags : 0x1003f
+0x004 Dr0 : 0
+0x008 Dr1 : 0
+0x00c Dr2 : 0
+0x010 Dr3 : 0
+0x014 Dr6 : 0
+0x018 Dr7 : 0
+0x01c FloatSave : _FLOATING_SAVE_AREA
+0x08c SegGs : 0
+0x090 SegFs : 0x3b
+0x094 SegEs : 0x23
+0x098 SegDs : 0x23
+0x09c Edi : 0x12f6a0
+0x0a0 Esi : 0x143948
+0x0a4 Ebx : 0
+0x0a8 Edx : 0x2901
+0x0ac Ecx : 0x12fe74
+0x0b0 Eax : 0
+0x0b4 Ebp : 0x12f6a0
+0x0b8 Eip : 0x401cb6 //异常发生时的指令地址
+0x0bc SegCs : 0x1b
+0x0c0 EFlags : 0x10246
+0x0c4 Esp : 0x12f620
+0x0c8 SegSs : 0x23
+0x0cc ExtendedRegisters : [512] "???"
现在回过头去看看handler
handler: //异常处理函数
mov ecx, [esp+0Ch] //取回调函数的第三个参数,也就是context
add dword ptr[ecx+0B8h], 2 //修改EIP,忽略异常 ,如果不加2的话,将导致循环执行异常指令
xor eax, eax
ret
参考资料
//-------------------------------------------------------------TIPS-------------------------------------------------------------------//
摘自于:《Windows系统异常处理机制的研究及应用》,作者:张明,徐万里
回调函数的原型:
EXCEPTION_DISPOSITION__cdecl _except_handler(
struct_EXCEPTION_RECORD *ExceptionRecord,
void *EstablisherFrame,
struct_CONTEXT *ContextRecord,
void *DispatcherContext
)
该函数的最重要的2 个参数是指向_EXCEPTION_RECORD结构的ExceptionRecord参数和指向_CONTEXT结
构的ContextRecord参数[2,4,5],前者主要包括异常类别编码、异常发生地址等重要信息;后者主要包括异常发生时的通用寄存器、调试寄存器和指令寄存器的值等重要的线程执行环境。而用于注册异常处理函数的典型汇编代码可表示如下:
PUSHhandler ; handler是新的异常处理函数地址
PUSHFS:[0] ; 指向原来的处理函数的地址压入栈内
MOVFS:[0],ESP ; 注册新的异常处理结构
//--------------------------------------------------------------------------------------------------------------------------------------------//
摘自于:http://www.mouseos.com/windows/SEH4.html
EXCEPTION_RECORD 是用来记录线程发生异常时的记录信息,在WinNT.h 定义为:
typedef struct _EXCEPTION_RECORD { |
这些信息重要的有:异常码,标志,地址等。异常码在 WinNT.h 中定义了一部分:
#define STATUS_WAIT_0 ((DWORD )0x00000000L) |
上现在非常常见的:ACCESS_VIOLATION 访问违例异常,它的值是0xC0000005
CONTEXT 结构
这个结构很简单但较长,我还是打算在这里贴出来,好有个直观的认识,它在WinNT.h 定义为:
typedef struct _CONTEXT { // DWORD ContextFlags; // DWORD Dr0; // FLOATING_SAVE_AREA FloatSave; // DWORD SegGs; // DWORD Edi; // DWORD Ebp; // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT; typedef CONTEXT *PCONTEXT; |
用来保存当线程发生异常时的 CPU 上下文环境。