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

利用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指令:

RDTSCRead Time Stamp Count),将计算机启动以来的CPU运行周期数放到EDXEAX里面,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_RECORD0x0012f354指向_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 {
  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_SAVE_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 上下文环境。

你可能感兴趣的:(c,windows,exception,struct,float,byte)