admin 发表于 2010-9-28 20:32:30

反调试技巧总结-原理和实现(三)

三、检测-专用调试器(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]
查看完整版本: 反调试技巧总结-原理和实现(三)