1.异常-SEH
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void AD_BreakPoint()
{
printf("SEH : BreakPoint\n");
__asm {
// install SEH
push handler
push DWORD ptr fs:[0]
mov DWORD ptr fs:[0], esp
//INT 3 指令是CPU中断命令,在用户模式的调试器啥都不做(经过调试发现不会触发ntdll.dll中的KiUserExceptionDispatcher)。
// generating exception
int 3
// 1) debugging
// go to terminating code
mov eax, 0xFFFFFFFF
jmp eax // process terminating!!!
// 2) not debugging
// go to normal code
handler:
mov eax, dword ptr ss:[esp+0xc]
mov ebx, normal_code
mov dword ptr ds:[eax+0xb8], ebx
xor eax, eax
retn
normal_code:
// remove SEH
pop dword ptr fs:[0]
add esp, 4
}
printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
AD_BreakPoint();
return 0;
}
注意:OD的异常选项 忽略int 3 中断必须不选,strongOD插件选项最好不用,才能进入指令
mov eax, 0xFFFFFFFF
jmp eax
OD不能在int 3下断(F2),否则会循环进入异常地址 int 3.
2.异常-SetUnhandledExceptionFilter
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
LPVOID g_pOrgFilter = 0;
LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS pExcept)
{
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)g_pOrgFilter);
// 8900 MOV DWORD PTR DS:[EAX], EAX
// FFE0 JMP EAX
pExcept->ContextRecord->Eip += 4;
return EXCEPTION_CONTINUE_EXECUTION;
}
void AD_SetUnhandledExceptionFilter()
{
printf("SEH : SetUnhandledExceptionFilter()\n");
//SetUnhandledExceptionFilter()来注册新的Top Level Exception Filter回调函数。
//触发异常时,系统在前面没有处理异常的情况下,会调用Kernel32.dll中的
//UnhandledExceptionFilter()函数。UnhandledExceptionFilter()会利用
//ntdll.dll中的NtQueryInformationProcess()来判断是否被调试,
//若判断在被调试,异常给调试器(调试器无法处理异常,进程终止)。
//若判断未被调试,则调用Top Level Exception Filter回调函数。
g_pOrgFilter = (LPVOID)SetUnhandledExceptionFilter(
(LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);
__asm {
xor eax, eax;
mov dword ptr [eax], eax
jmp eax
}
printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
AD_SetUnhandledExceptionFilter();
return 0;
}
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void DynAD_RDTSC()
{
//Timing Check 技术通过计算运行时间的差异来反调试,反模拟。
//1.Counter based method
// RDTSC 汇编指令(RD==READ TSC == Time Stamp Counter)
// x86 CPU中存在一个名为TSC(Time Stamp Counter,时间戳计数器)的64位寄存器。
// CPU对每个Clock Cycle(时钟周期)计数,然后保存到TSC.RDTSC是一条汇编指令,
// 用来将TSC值读入EDX:EAX寄存器)。
// OD中的插件Olly Advanced->options 反调试2 Anti-RDSTC(基于驱动模式可以绕过),此功能有可能造成死机。
// kernel32!QueryPerformanceCounter()/ntdll!NtQueryPerformanceCounter()
// kernel32!GetTickCount()
//2.Time based method
// timeGetTime()
// _ftime()
DWORD dwDelta = 0;
printf("Timing Check (RDTSC method)");
__asm {
pushad
//0F31 rdtsc
rdtsc
push edx
push eax
//用于消耗时间的循环(实际代码相当复杂)
xor eax, eax
mov ecx, 0x3e8
_LOOP_START:
inc eax
loop _LOOP_START
rdtsc
pop esi // eax
pop edi // edx
// check high order bits
cmp edx, edi
ja _DEBUGGER_FOUND
// check low order bits
sub eax, esi
mov dwDelta, eax
cmp eax, 0xffffff
jb _DEBUGGER_NOT_FOUND
// debugger found -> crash!!!
_DEBUGGER_FOUND:
xor eax, eax
mov [eax], eax
// debugger not found
_DEBUGGER_NOT_FOUND:
popad
}
printf(" : delta = %X (ticks)\n", dwDelta);
printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
DynAD_RDTSC();
return 0;
}
4.陷阱标志
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void DynAD_SingleStep()
{
//陷阱标志指EFLAGS寄存器的第9个(Index8)比特位。
//TF值设置为1时,CPU将进入单步执行(Single Step)模式。单步直行模式中,CPU执行1条指令后即
//触发一个EXCEPTION_SINGLE_STEP(0x80000004)异常,异常地址为下一条指令。然后陷阱标志会
//自动清零。
printf("Trap Flag (Single Step)\n");
__asm {
// install SEH
push handler
push DWORD ptr fs:[0]
mov DWORD ptr fs:[0], esp
//因无法修改EFLAGS,故通过栈修改
pushfd
or dword ptr ss:[esp], 0x100
popfd
//执行完nop指令后,才触发EXCEPTION_SINGLE_STEP异常,OD在nop指令使用F7,F8,F9都可以。
//即KiUserExceptionDispatcher的异常地址指向 mov eax,0xFFFFFFFF
//1)若为正常运行,则运行前面注册过的SEH
//2)若为调试运行,则继续执行以下指令,不管使用F7,F8,F9都断在mov eax,0xFFFFFFFF
nop
// 1) debugging
// go to terminating code
mov eax, 0xFFFFFFFF
jmp eax // process terminating!!!
// 2) not debugging
// go to normal code
handler:
mov eax, dword ptr ss:[esp+0xc]
mov ebx, normal_code
mov dword ptr ds:[eax+0xb8], ebx
xor eax, eax
retn
normal_code:
// remove SEH
pop dword ptr fs:[0]
add esp, 4
}
printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
DynAD_SingleStep();
return 0;
}
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void DynAD_INT2D()
{
//INT 2D 原为内核模式中用来触发异常的指令,也可以在用户模式下触发异常。但程序
//调试运行时不会触发异常,只是忽略。
//INT 2D指令会造成两个有趣的现象
//1.在调试模式中执行INT2D指令后(F7,F8),下条指令的第一个字节将被忽略,后一个字节会被识别为新的指令继续执行。此特性,可用于代码混淆。
//2.在调试模式中执行INT2D指令后(F7,F8),程序不会停在其下条指令开始的地方,而是一直运行,直到遇到断点(原有的代码字节顺序被打乱,OD的BUG)。
BOOL bDebugging = FALSE;
__asm {
// install SEH
push handler
push DWORD ptr fs:[0]
mov DWORD ptr fs:[0], esp
//可以在执行到此条指令时,修改EFlags寄存器TF=1,然后就能进入SEH处理函数
int 0x2d
nop
mov bDebugging, 1
jmp normal_code
handler:
mov eax, dword ptr ss:[esp+0xc]
mov dword ptr ds:[eax+0xb8], offset normal_code
mov bDebugging, 0
xor eax, eax
retn
normal_code:
// remove SEH
pop dword ptr fs:[0]
add esp, 4
}
printf("Trap Flag (INT 2D)\n");
if( bDebugging ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
DynAD_INT2D();
return 0;
}
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
DWORD g_dwOrgChecksum = 0xF5934986;
int _tmain(int argc, TCHAR* argv[]);
void DynAD_Checksum()
{
BOOL bDebugging = FALSE;
DWORD dwSize = 0;
printf("Checksum\n");
__asm {
mov ecx, offset _tmain
mov esi, offset DynAD_Checksum
sub ecx, esi // ecx : loop count (buf size)
xor eax, eax // eax : checksum
xor ebx, ebx
_CALC_CHECKSUM:
movzx ebx, byte ptr ds:[esi]
add eax, ebx
rol eax, 1
inc esi
loop _CALC_CHECKSUM
cmp eax, g_dwOrgChecksum
je _NOT_DEBUGGING
mov bDebugging, 1
_NOT_DEBUGGING:
}
if( bDebugging ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
}
int _tmain(int argc, TCHAR* argv[])
{
DynAD_Checksum();
return 0;
}
总结:
1. CC int3 指令(注意两者之间没有空格)。
2.CD 2D int 2d 指令
3.
pushfd
or [esp],100
popfd
使EFlags寄存器TF==1.
此3种方法,都是利用了调试器特有的性质(1.忽略 2.一直运行,直到断点。 3.断在执行指令后的一条指令,都没有触发ntdll!KiUserExceptionDispatcher())。