您的位置:首页 > 其它

利用Windows异常处理和RDTSC指令反调试学习

2012-05-15 17:10 281 查看
利用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_S***E_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 {

DWORD ExceptionCode;

DWORD ExceptionFlags;

struct _EXCEPTION_RECORD *ExceptionRecord;

PVOID ExceptionAddress;

DWORD NumberParameters;

ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

} EXCEPTION_RECORD;

这些信息重要的有:异常码,标志,地址等。异常码在
WinNT.h
中定义了一部分:

#define STATUS_WAIT_0 ((DWORD )0x00000000L)

#define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L)

#define STATUS_USER_APC ((DWORD )0x000000C0L)

#define STATUS_TIMEOUT ((DWORD )0x00000102L)

#define STATUS_PENDING ((DWORD )0x00000103L)

#define DBG_EXCEPTION_HANDLED ((DWORD )0x00010001L)

#define DBG_CONTINUE ((DWORD )0x00010002L)

#define STATUS_SEGMENT_NOTIFICATION ((DWORD )0x40000005L)

#define DBG_TERMINATE_THREAD ((DWORD )0x40010003L)

#define DBG_TERMINATE_PROCESS ((DWORD )0x40010004L)

#define DBG_CONTROL_C ((DWORD )0x40010005L)

#define DBG_PRINTEXCEPTION_C ((DWORD )0x40010006L)

#define DBG_RIPEXCEPTION ((DWORD )0x40010007L)

#define DBG_CONTROL_BREAK ((DWORD )0x40010008L)

#define DBG_COMMAND_EXCEPTION ((DWORD )0x40010009L)

#define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L)

#define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L)

#define STATUS_BREAKPOINT ((DWORD )0x80000003L)

#define STATUS_SINGLE_STEP ((DWORD )0x80000004L)

#define STATUS_LONGJUMP ((DWORD )0x80000026L)

#define STATUS_UNWIND_CONSOLIDATE ((DWORD )0x80000029L)

#define DBG_EXCEPTION_NOT_HANDLED ((DWORD )0x80010001L)

#define STATUS_ACCESS_VIOLATION ((DWORD )0xC0000005L)

#define STATUS_IN_PAGE_ERROR ((DWORD )0xC0000006L)

#define STATUS_INVALID_HANDLE ((DWORD )0xC0000008L)

#define STATUS_INVALID_PARAMETER ((DWORD )0xC000000DL)

#define STATUS_NO_MEMORY ((DWORD )0xC0000017L)

#define STATUS_ILLEGAL_INSTRUCTION ((DWORD )0xC000001DL)

#define STATUS_NONCONTINUABLE_EXCEPTION ((DWORD )0xC0000025L)

#define STATUS_INVALID_DISPOSITION ((DWORD )0xC0000026L)

#define STATUS_ARRAY_BOUNDS_EXCEEDED ((DWORD )0xC000008CL)

#define STATUS_FLOAT_DENORMAL_OPERAND ((DWORD )0xC000008DL)

#define STATUS_FLOAT_DIVIDE_BY_ZERO ((DWORD )0xC000008EL)

#define STATUS_FLOAT_INEXACT_RESULT ((DWORD )0xC000008FL)

#define STATUS_FLOAT_INVALID_OPERATION ((DWORD )0xC0000090L)

#define STATUS_FLOAT_OVERFLOW ((DWORD )0xC0000091L)

#define STATUS_FLOAT_STACK_CHECK ((DWORD )0xC0000092L)

#define STATUS_FLOAT_UNDERFLOW ((DWORD )0xC0000093L)

#define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L)

#define STATUS_INTEGER_OVERFLOW ((DWORD )0xC0000095L)

#define STATUS_PRIVILEGED_INSTRUCTION ((DWORD )0xC0000096L)

#define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL)

#define STATUS_DLL_NOT_FOUND ((DWORD )0xC0000135L)

#define STATUS_ORDINAL_NOT_FOUND ((DWORD )0xC0000138L)

#define STATUS_ENTRYPOINT_NOT_FOUND ((DWORD )0xC0000139L)

#define STATUS_CONTROL_C_EXIT ((DWORD )0xC000013AL)

#define STATUS_DLL_INIT_FAILED ((DWORD )0xC0000142L)

#define STATUS_FLOAT_MULTIPLE_FAULTS ((DWORD )0xC00002B4L)

#define STATUS_FLOAT_MULTIPLE_TRAPS ((DWORD )0xC00002B5L)

#define STATUS_REG_NAT_CONSUMPTION ((DWORD )0xC00002C9L)

#define STATUS_STACK_BUFFER_OVERRUN ((DWORD )0xC0000409L)

#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD )0xC0000417L)

#define STATUS_ASSERTION_FAILURE ((DWORD )0xC0000420L)

#if defined(STATUS_SUCCESS) || (_WIN32_WINNT > 0x0500) || (_WIN32_FUSION >= 0x0100)

#define STATUS_SXS_EARLY_DEACTIVATION ((DWORD )0xC015000FL)

#define STATUS_SXS_INVALID_DEACTIVATION ((DWORD )0xC0150010L)

上现在非常常见的:ACCESS_VIOLATION 访问违例异常,它的值是0xC0000005

CONTEXT 结构
这个结构很简单但较长,我还是打算在这里贴出来,好有个直观的认识,它在WinNT.h 定义为:

typedef struct _CONTEXT {
//

// The flags values within this flag control the contents of

// a CONTEXT record.

//

// If the context record is used as an input parameter, then

// for each portion of the context record controlled by a flag

// whose value is set, it is assumed that that portion of the

// context record contains valid context. If the context record

// is being used to modify a threads context, then only that

// portion of the threads context will be modified.

//

// If the context record is used as an IN OUT parameter to capture

// the context of a thread, then only those portions of the thread's

// context corresponding to set flags will be returned.

//

// The context record is never used as an OUT only parameter.

//
DWORD ContextFlags;
//

// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is

// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT

// included in CONTEXT_FULL.

//
DWORD Dr0;

DWORD Dr1;

DWORD Dr2;

DWORD Dr3;

DWORD Dr6;

DWORD Dr7;
//

// This section is specified/returned if the

// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.

//
FLOATING_S***E_AREA FloatSave;
//

// This section is specified/returned if the

// ContextFlags word contians the flag CONTEXT_SEGMENTS.

//
DWORD SegGs;

DWORD SegFs;

DWORD SegEs;

DWORD SegDs;
//

// This section is specified/returned if the

// ContextFlags word contians the flag CONTEXT_INTEGER.

//
DWORD Edi;

DWORD Esi;

DWORD Ebx;

DWORD Edx;

DWORD Ecx;

DWORD Eax;
//

// This section is specified/returned if the

// ContextFlags word contians the flag CONTEXT_CONTROL.

//
DWORD Ebp;

DWORD Eip;

DWORD SegCs; // MUST BE SANITIZED

DWORD EFlags; // MUST BE SANITIZED

DWORD Esp;

DWORD SegSs;
//

// This section is specified/returned if the ContextFlags word

// contains the flag CONTEXT_EXTENDED_REGISTERS.

// The format and contexts are processor specific

//
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
typedef CONTEXT *PCONTEXT;
用来保存当线程发生异常时的 CPU 上下文环境。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: