反调试技巧总结-原理和实现(三)
三、检测-专用调试器(FS_)这一部分是我比较喜欢的,但内容还不是很丰富,比如:
1、针对SoftIce的检测方法有很多,但由于我从没使用过Softice,也没有条件去测试,所以没有给出太多,有兴趣的可以自己查阅资料进行补充,针对softice网上资料较多,或查阅《软件加解密技术》。
2、同样,这里也没有给出windbg等等其它调试器的检测方法。
3、而针对Odplugin,也只给了几种HideOD的检测。事实上,目前OD的使用者通常都使用众多的强大插件,当OD的反调试越来越普遍时,自己设计几款常用的OD插件的反调试,将会是非常有效的反调试手段。
4、对VME的检测也只给出了两种,如想丰富这一部分可以参考Peter Ferrie的一篇anti-vme的文章(http://bbs.pediy.com/showthread.php?t=68411)。里面有非常多的anti-vme方法。
针对专用调试器的函数列表如下:
//find specific debugger
bool FS_OD_Exception_GuardPages();
bool FS_OD_Int3_Pushfd();
bool FS_SI_UnhandledExceptionFilter();
bool FS_ODP_Process32NextW();
bool FS_ODP_OutputDebugStringA();
bool FS_ODP_OpenProcess();
bool FS_ODP_CheckRemoteDebuggerPresent();
bool FS_ODP_ZwSetInformationThread();
bool FS_SI_Exception_Int1();
bool IsInsideVMWare_();
bool FV_VMWare_VMX();
bool FV_VPC_Exception();
int FV_VME_RedPill();//0:none,1:vmvare;2:vpc;3thers
3.1 FS_OD_Exception_GuardPages
“保护页异常”是一个简单的反调试技巧。当应用程序尝试执行保护页内的代码时,将会产生一个EXCEPTION_GUARD_PAGE(0x80000001)异常,但如果存在调试器,调试器有可能接收这个异常,并允许该程序继续运行,事实上,在OD中就是这样处理的,OD使用保护页来实现内存断点。
最开始实现时忘记了free申请的空间,多谢sessiondiy提醒。
SYSTEM_INFO sSysInfo;
LPVOID lpvBase;
BYTE * lptmpB;
GetSystemInfo(&sSysInfo);
DWORD dwPageSize=sSysInfo.dwPageSize;
DWORD flOldProtect;
DWORD dwErrorcode;
lpvBase=VirtualAlloc(NULL,dwPageSize,MEM_COMMIT,PAGE_READWRITE);
if(lpvBase==NULL)
return false;
lptmpB=(BYTE *)lpvBase;
*lptmpB=0xc3;//retn
VirtualProtect(lpvBase,dwPageSize,PAGE_EXECUTE_READ | PAGE_GUARD,&flOldProtect);
__try
{
__asmcall dword ptr;
VirtualFree(lpvBase,0,MEM_RELEASE);
return true;
}
__except(1)
{
VirtualFree(lpvBase,0,MEM_RELEASE);
return false;
}
3.2 FS_OD_Int3_Pushfd
这是个最近比较牛X的反调试,据称是vmp1.64里发现的,好像ttprotect里面也有使用,我没有验证。Pediy里有帖子详细讨论,我是看到gkend的分析,才搞懂一些。下面摘自gkend分析
代码:
int3,pushfd和int3,popfd一样的效果。只要修改int3后面的popfd为其他值,OD都能通过。老掉牙的技术又重新被用了。SEH异常机制的运用而已。 原理:在SEH异常处理中设置了硬件断点DR0=EIP+2,并把EIP的值加2,那么应该在int3,popfd后面的指令执行时会产生单步异常。但是OD遇到前面是popfd/pushfd时,OD会自动在popfd后一指令处设置硬件断点,而VMP的seh异常处理会判断是否已经设置硬件断点,如果已经有硬件断点就不产生单步异常,所以不能正常执行。
http://bbs.pediy.com/showthread.php?t=67737
大家也可以仔细研究下OD下的pushfd,popfd等指令,相信利用它们可以构造很多反调试,下面是我实现的一个,不过现在看起来有点没看懂,不知当时为什么用了两个int3。
__asm
{
push offset e_handler; set exception handler
pushdword ptr fs:
mov dword ptr fs:,esp
xor eax,eax;reset EAX invoke int3
int 3h
pushfd
nop
nop
nop
nop
pop dword ptr fs:;restore exception handler
add esp,4
test eax,eax; check the flag
je rf_label
jmp rt_label
e_handler:
push offset e_handler1; set exception handler
pushdword ptr fs:
mov dword ptr fs:,esp
xor eax,eax;reset EAX invoke int3
int 3h
nop
pop dword ptr fs:;restore exception handler
add esp,4
;EAX = ContextRecord
mov ebx,eax;dr0=>ebx
mov eax,dword ptr
;set ContextRecord.EIP
inc dword ptr ;
mov dword ptr ,ebx;dr0=>eax
xor eax,eax
retn
e_handler1:
;EAX = ContextRecord
mov eax,dword ptr
;set ContextRecord.EIP
inc dword ptr ;
mov ebx,dword ptr
mov dword ptr ,ebx;dr0=>eax
xor eax,eax
retn
rt_label:
xoreax,eax
inc eax
mov esp,ebp
popebp
retn
rf_label:
xor eax,eax
mov esp,ebp
pop ebp
retn
}
3.3 FS_SI_UnhandledExceptionFilter
这个针对SoftIce的反调试很简单,好像是SoftIce会修改UnhandledExceptionFilter这个函数的第一个字节为CC。因此判断这个字节是否为cc,就是一种检查softice的简便方法。
FARPROC Uaddr ;
BYTE tmpB = 0;
(FARPROC&) Uaddr =GetProcAddress ( GetModuleHandle("kernel32.dll"),"UnhandledExceptionFilter");
tmpB = *((BYTE*)Uaddr); // 取UnhandledExceptionFilter函数第一字节
tmpB=tmpB^0x55;
if(tmpB ==0x99) // 如该字节为CC,则SoftICE己加载
return true;
else
return false;
3.4 FS_ODP_Process32NextW
当我在调试FD_parentprocess时,感觉总是怪怪的,使用OD时运行Process32NextW总是返回失败,搞了一个晚上,才搞懂原来是OD的插件HideOD在作怪。当HideOD的Process32NextW的选项被选中时,它会更改Process32NextW的返回值,使其始终返回false,这主要是HideOD针对FD_parentprocess这个反调试的一个反反调试。但也正是这一点暴露的它的存在。
int OSVersion;
FARPROC Func_addr;
WORD tmpW;
//1.Process32Next
HMODULE hModule = GetModuleHandle("kernel32.dll");
(FARPROC&) Func_addr =GetProcAddress ( hModule,"Process32NextW");
if (Func_addr != NULL)
{
tmpW=*(WORD*)Func_addr;
OSVersion=myGetOSVersion();
switch(OSVersion)
{
case OS_winxp:
if(tmpW!=0xFF8B)//maybe option of Process32Next is selected.
return true;
break;
default:
if(tmpW==0xC033)
return true;
break;
}
}
但上面的代码并不完美,因为有跨平台问题,所以要先获得当前操作系统版本。目前只在win2k和winxp下进行了测试。
3.5 FS_ODP_OutputDebugStringA
同样,HIDEOD的OutputDebugStringA选项,也对OutputDebugStringA这个api做了处理,具体修改内容我记不得了,大家可以自己比对一下。我的代码如下:
int OSVersion;
FARPROC Func_addr;
WORD tmpW;
//2.OutputDebugStringA
HMODULE hModule = GetModuleHandle("kernel32.dll");
(FARPROC&) Func_addr =GetProcAddress ( hModule,"OutputDebugStringA");
if (Func_addr != NULL)
{
tmpW=*(WORD*)Func_addr;
OSVersion=myGetOSVersion();
switch(OSVersion)
{
case OS_winxp:
if(tmpW!=0x3468)//maybe option of OutputDebugStringAt is selected.
return true;
break;
default:
if(tmpW==0x01e8)
return true;
break;
}
}
return false;
3.6 FS_ODP_OpenProcess
这个据称这个是针对HideDebugger这个插件的,当这个插件开启时,它会挂钩OpenProcess这个函数,它修改了OpenProcess的前几个字节。因此检测这几个字节就可实现这个反调试。
FARPROC Func_addr;
BYTE tmpB;
//OpenProcess
HMODULE hModule = GetModuleHandle("kernel32.dll");
(FARPROC&) Func_addr =GetProcAddress ( hModule,"OpenProcess");
if (Func_addr != NULL)
{
tmpB=*((BYTE*)Func_addr+6);
if(tmpB==0xea)//HideDebugger Plugin of OD is present
return true;
}
return false;
3.7 FS_ODP_CheckRemoteDebuggerPresent
和前面提到的两个HideOD的反调试类似,不多说了。大家可以自行比对一下开启和不开启HideOD时,CheckRemoteDebuggerPresent函数的异同,就可以设计反这个插件的反调试了。
int OSVersion;
FARPROC Func_addr;
BYTE tmpB;
//2.CheckRemoteDebuggerPresent
HMODULE hModule = GetModuleHandle("kernel32.dll");
(FARPROC&) Func_addr =GetProcAddress ( hModule,"CheckRemoteDebuggerPresent");
if (Func_addr != NULL)
{
tmpB=*((BYTE*)Func_addr+10);
OSVersion=myGetOSVersion();
switch(OSVersion)
{
case OS_winxp:
if(tmpB!=0x74)//HideOD is present
return true;
break;
default:
break;
}
}
return false;
3.8 FS_ODP_ZwSetInformationThread
和前面提到的几个HideOD的反调试类似,大家可以自行比对一下开启和不开启HideOD时,ZwSetInformationThread函数的异同,就可以设计反这个插件的反调试了。
int OSVersion;
FARPROC Func_addr;
WORD tmpW;
BYTE tmpB0,tmpB1;
//2.CheckRemoteDebuggerPresent
HMODULE hModule = GetModuleHandle("ntdll.dll");
(FARPROC&) Func_addr =GetProcAddress ( hModule,"ZwSetInformationThread");
if (Func_addr != NULL)
{
tmpW=*((WORD*)Func_addr+3);
tmpB0=*((BYTE*)Func_addr+9);
tmpB1=*((BYTE*)Func_addr+10);
OSVersion=myGetOSVersion();
switch(OSVersion)
{
case OS_winxp:
if(tmpW!=0x0300)//HideOD is present
return true;
break;
case OS_win2k:
if((tmpB0!=0xcd)&&(tmpB1!=0x2e))
return true;
break;
default:
break;
}
}
return false;
3.9 FS_SI_Exception_Int1
通常int1的DPL为0,这表示"cd 01"机器码不能在3环下执行。如果直接执行这个中断将会产生一个保护错误,windows会产生一个EXCEPTION_ACCESS_VIOLATION (0xc0000005)异常。然而,如果SOFTICE正在运行,它挂钩了int1,并调整其DPL为3。这样SoftICE就可以在用户模式执行单步操作了。
当int 1发生时,SoftICE不检查它是由于陷阱标志位还是由软件中断产生,SoftICE总是去调用原始中断1的句柄,此时将会产生一个EXCEPTION_SINGLE_STEP (0x80000004)而不是EXCEPTION_ACCESS_VIOLATION (0xc0000005)异常,这就形成了一个简单的反调试方法。
__asm
{
push offset eh_int1; set exception handler
pushdword ptr fs:
mov dword ptr fs:,esp
xor eax,eax;reset flag(EAX) invoke int3
int 1h
pop dword ptr fs:;restore exception handler
add esp,4
cmp eax,0x80000004; check the flag
je rt_label_int1
jmp rf_label_int1
eh_int1:
mov eax,;
mov ebx,dword ptr ;
mov eax,dword ptr ;EAX = ContextRecord
mov dword ptr ,ebx;set flag (ContextRecord.EAX)
inc dword ptr ;set ContextRecord.EIP
inc dword ptr ;set ContextRecord.EIP
xor eax,eax
retn
}
3.10 FV_VMWare_VMX
这是一个针对VMWare虚拟机仿真环境的反调试,我从网上直接拷贝的代码。
VMWARE提供一种主机和客户机之间的通信方法,这可以被用做一种VMWare的反调试。Vmware将会处理IN (端口为0x5658/’VX’)指令,它会返回一个majic数值“VMXh”到EBX中。
当在保护模式操作系统的3环下运行时,IN指令的执行将会产生一个异常,除非我们修改了I/O的优先级等级。然而,如果在VMWare下运行,将不会产生任何异常,同时EBX寄存器将会包含’VMXh’,ECX寄存器也会被修改为Vmware的产品ID。
这种技巧在一些病毒中比较常用。
针对VME的反调试,在peter Ferrie的另一篇文章<<Attacks on More Virtual Machine Emulators>>中有大量的描述,有兴趣的可以根据这篇文章,将FV_反调试好好丰富一下。
bool IsInsideVMWare_()
{
bool r;
_asm
{
push edx
push ecx
push ebx
mov eax, 'VMXh'
mov ebx, 0 // any value but MAGIC VALUE
mov ecx, 10 // get VMWare version
mov edx, 'VX' // port number
in eax, dx // read port
// on return EAX returns the VERSION
cmp ebx, 'VMXh' // is it a reply from VMWare?
setz // set return value
pop ebx
pop ecx
pop edx
}
return r;
}
bool FV_VMWare_VMX()
{
__try
{
return IsInsideVMWare_();
}
__except(1) // 1 = EXCEPTION_EXECUTE_HANDLER
{
return false;
}
}
3.11 FV_VPC_Exception
这个代码我也是完整从网上拷贝下来的,具体原理在<<Attacks on More Virtual Machine Emulators>>这篇文章里也有详细描述。与VMWare使用一个特殊端口完成主机和客户机间通信的方法类似的是,VirtualPC靠执行非法指令产生一个异常供内核捕获。这个代码如下:
代码:
0F 3F x1 x20F C7 C8 y1 y2
由这两个非法指令引起的异常将会被应用程序捕获,然而,如果VirtualPC正在运行,将不会产生异常。X1,x2的允许的数值还不知道,但有一部分已知可以使用,如0A 00,11 00…等等。
__declspec(naked) bool FV_VPC_Exception()
{
_asm
{
push ebp
movebp, esp
movecx, offset exception_handler
push ebx
push ecx
push dword ptr fs:
movdword ptr fs:, esp
movebx, 0 // Flag
moveax, 1 // VPC function number
}
// call VPC
_asm __emit 0Fh
_asm __emit 3Fh
_asm __emit 07h
_asm __emit 0Bh
_asm
{
mov eax, dword ptr ss:
mov dword ptr fs:, eax
add esp, 8
test ebx, ebx
setz al
lea esp, dword ptr ss:
mov ebx, dword ptr ss:
mov ebp, dword ptr ss:
add esp, 8
jmp ret1
exception_handler:
mov ecx,
mov dword ptr , -1 // EBX = -1 -> not running, ebx = 0 -> running
add dword ptr , 4 // -> skip past the call to VPC
xor eax, eax // exception is handled
ret
ret1:
ret
}
}3.12 FV_VME_RedPill
这个方法似乎是检测虚拟机的一个简单有效的方法,虽然还不能确定它是否是100%有效。名字很有意思,红色药丸(为什么不是bluepill,哈哈)。我在网上找到了个ppt专门介绍这个方法,可惜现在翻不到了。记忆中原理是这样的,主要检测IDT的数值,如果这个数值超过了某个数值,我们就可以认为应用程序处于虚拟环境中,似乎这个方法在多CPU的机器中并不可靠。据称ScoobyDoo方法是RedPill的升级版。代码也是在网上找的,做了点小改动。有四种返回结果,可以确认是VMWare,还是VirtualPC,还是其它VME,或是没有处于VME中。
//return value:0:none,1:vmvare;2:vpc;3thers
unsigned char matrix;
unsigned char redpill[] =
"\x0f\x01\x0d\x00\x00\x00\x00\xc3";
HANDLE hProcess = GetCurrentProcess();
LPVOID lpAddress = NULL;
PDWORD lpflOldProtect = NULL;
__try
{
*((unsigned*)&redpill) = (unsigned)matrix;
lpAddress = VirtualAllocEx(hProcess, NULL, 6, MEM_RESERVE|MEM_COMMIT , PAGE_EXECUTE_READWRITE);
if(lpAddress == NULL)
return 0;
BOOL success = VirtualProtectEx(hProcess, lpAddress, 6, PAGE_EXECUTE_READWRITE , lpflOldProtect);
if(success != 0)
return 0;
memcpy(lpAddress, redpill, 8);
((void(*)())lpAddress)();
if (matrix>0xd0)
{
if(matrix==0xff)//vmvare
return 1;
else if(matrix==0xe8)//vitualpc
return 2;
else
return 3;
}
else
return 0;
}
__finally
{
VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);
}
页:
[1]