ScareCrowL's blog

To maintain world peace

【习题】Tencent2016C (虚拟机检测技术)

题目来自:

2016腾讯游戏安全技术竞赛第二轮第2题——PC方向

文章吾爱链接:https://www.52pojie.cn/thread-701845-1-1.html

题目概要

要求说明:
编写一个【Tencent2016C.dll】,并导出多个接口函数:CheckVMWareX,CheckVirtualPCX,CheckVirtualBoxX,X为1-100之间的数字,比如CheckVMWare1,CheckVirtualPC8,…,CheckVirtualBox98。

CheckVMWareX系列函数功能:检测自己是否运行于VMWare中,是返回TRUE,否则返回FALSE。

自己一直用VMware,积累一下反虚拟机手段,希望调试病毒能少踩坑,适合新手看,VirtualPC和VirtualBox也都是差不多,以后有时间补上吧~

编译环境:
VS2015创建win32 dll ,静态编译->属性->C/C++->代码生成->运行库(MT)
VMware12 + win7 32位测试

《【习题】Tencent2016C (虚拟机检测技术)》

代码原理

执行特权指令检测

原理:在x86体系中,一些指令在获取硬件相关信息时不产生异常,如sidt、sgdt、sldt、cpuid等,而VMware因为性能原因并没有虚拟这些指令,所以意味着这些指令在vm虚拟机中和物理机中运行时会返回不同的结果。

然而在测试一些常用检测技术时发现部分已失效,应该是VMware更新导致的,先来看两个之前很常用的技术:

Redpill

  简单说,就是通过运行sidt指令获取IDT寄存器的值(IDT: 中断描述符表,可以简单理解为查找处理中断时所用的函数,共256项,如第3项就是我们常用的int3断点)。Redpill的作者测试说明虚拟机中的IDT地址通常位于0xFFXXXXXX,而在真实主机上位于0x80xxxxxx。所以可通过判断执行SIDT指令后返回的第一字节是否大于0xD0,判断是否在虚拟机中。同时这项技术必须满足运行在单核处理器上,因为每个核心只有一个IDT表~如果是多核切换就很难确定具体值了~

No Pill

  sgdt与sldt指令探测技术,依赖于LDT(局部描述符表)由处理器分配而非操作系统分配的事实。因为Windows正常情况下不使用LDT,但VM提供了LDT的虚拟化支持,结果就是:宿主机中ldt位置为0,而在虚拟机,非零。

简单看一下idt:
打开双机调试(建议Virtual KD,配置较简单)

《【习题】Tencent2016C (虚拟机检测技术)》

当前我们已经在调试虚拟机的操作系统了,所以直接看这三个表的地址~

《【习题】Tencent2016C (虚拟机检测技术)》

好吧,这哪里是虚拟机,不就是真机啊!看来VM这么多年没少打补丁~
代码也不用测试了,肯定不能成功啊,有兴趣的可以测试下低版本VM~
好在我们还有第三种特权指令可用~

查询I/O通信端口

原理:使用IN指令来读取特定端口的数据进行两机通讯,但由于IN指令属于特权指令,在处于保护模式下的真机上执行此指令时,除非权限允许,否则将会触发类型为”EXCEPTION_PRIV_INSTRUCTION”的异常,而在虚拟机中并不会发生异常,在指定功能号为0xA/10(获取VMware版本)时,会在EBX中返回其版本号“VMXH”;而当功能号为0x14时,可用于获取VMware内存大小,当大于0时则说明处于虚拟机中。代码分析如下:

//查询I/O通信端口
BOOL CheckVMWare1()
{
        BOOL bResult = TRUE;
        __try
        {
                __asm
                {
                        push   edx
                        push   ecx
                        push   ebx                //保存环境
                        mov    eax, 'VMXh'
                        mov    ebx, 0             //将ebx清零
                        mov    ecx, 10            //指定功能号,用于获取VMWare版本,为0x14时获取VM内存大小
                        mov    edx, 'VX'          //端口号
                        in     eax, dx            //从端口edx 读取VMware到eax
                        cmp    ebx, 'VMXh'        //判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
                        setz[bResult]             //为零 (ZF=1) 时设置字节
                        pop    ebx                //恢复环境
                        pop    ecx
                        pop    edx
                }
        }
        __except (EXCEPTION_EXECUTE_HANDLER)       //如果未处于VMware中,则触发此异常
        {
                bResult = FALSE;
        }
        return bResult;
}

利用虚拟硬件检测

网卡MAC地址检测

原理:网卡设备的MAC地址是唯一不变的(虽然也能物理修改)。
MAC地址的前三个字节标识一个提供商,所以一般情况下只需要找到VM固定的前三个字节就可以了~
ipconfig /all

《【习题】Tencent2016C (虚拟机检测技术)》

查询到虚拟机的MAC地址00-0C-29-38-8B-E1

//通过MAC地址检测
BOOL CheckVMWare2()
{
        string mac;
        getMacAddr(mac);            //API见附件,以下是3种常见标识 
        if (mac == "00-05-69" || mac == "00-0c-29" || mac == "00-50-56")
        {
                return TRUE;
        }
        else
        {
                return FALSE;
        }
}

CPUID检测

原理:CPUID指用户计算机当前的信息处理器的信息。CPUID 指令是从 Intel 486 处理器以后开始加入支持的(只要不是古董应该都OK )。当eax=1时,运行CPUID指令之后,ecx的高31位可以判断出是否在虚拟机中,如果ecx的高31位为0表示在虚拟机下,否则在宿主机中。

//3.CPUID检测
BOOL CheckVMWare3()
{
        DWORD dwECX = 0;
        bool b_IsVM = true;
        _asm
        {
                pushad;
                pushfd;
                mov eax, 1;
                cpuid;
                mov dwECX, ecx;
                and ecx, 0x80000000;        //取最高位
                test ecx, ecx;              //检测ecx是否为0
                setz[b_IsVM];               //为零 (ZF=1) 时设置字节
                popfd;
                popad;
        }
        if (b_IsVM)                          //宿主机
        {
                return FALSE;
        }
        else                                 //虚拟机
        {
                return TRUE;
        }
}
mov eax, 0
cpuid

上面代码中,eax为0获取,那么它将返回值是:

  • eax:最大的基本功能号
  • ebx:”Genu”
  • edx: “ineI”
  • ecx:”ntel”

这几个字符串组合起来就是 “GenuineIntel” 对于 AMD 的处理器来说,它返回的字符串是:”AuthenticAMD“,可对应判断处理器。

类似的,对于虚拟机CPUID还有另一种方式检测
eax为0x40000000时,运行CPUID后,ebx+ecx+edx=”VMWareVMWare”;
《【习题】Tencent2016C (虚拟机检测技术)》

BOOL CPUID2()
{
                DWORD dwECX = 0;
                bool isVM = true;
                DWORD dwReg[3] = { 0 };
                _asm {
                        pushad;
                        pushfd;
                        mov eax, 0x40000000;
                        cpuid;
                        mov dword ptr[dwReg], ebx;     //运行CPUID之后,ebx+ecx+edx=”VMWareVMWare”;
                        mov dword ptr[dwReg + 4], ecx;
                        mov dword ptr[dwReg + 8], edx;
                        popfd;
                        popad;
                }
}

通过主板序列号、型号、系统盘所在磁盘名称等其他硬件信息

原理:这里使用WMI的方式,连接COM接口,循环枚举所有的结果对象找到带有VMware的相关信息(类似的信息有很多,只列举一个)。

BOOL CheckVMWare4()
{
        string table = "Win32_DiskDrive";
        wstring wcol = L"Caption";
        string ret;
        ManageWMIInfo(ret, table, wcol);            //API见附件
        if (ret.find("VMware") != string::npos)
        {
                return TRUE;
        }
        else
        {
                return FALSE;
        }
}

通过虚拟机能够获取的其它特征信息检测

其实都和找MAC地址类似,就是要观察虚拟机,观察它的各种系统信息,设备信息,再找相关的信息。

搜索特定进程

原理:会有一些虚拟机特有的进程,可以通过检测这些进程是否存在来判断~

《【习题】Tencent2016C (虚拟机检测技术)》

显然我们可以从vmtoolsd.exe和vmacthlp.exe突破

//遍历进程
BOOL CheckVMWare3()
{
        DWORD ret = 0;
        PROCESSENTRY32 pe32;
        pe32.dwSize = sizeof(pe32);
        HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//拍摄快照
        if (hProcessSnap == INVALID_HANDLE_VALUE)
        {
                return FALSE;
        }
        BOOL bMore = Process32First(hProcessSnap, &pe32);            //获取第一个进程
        while (bMore)
        {
                if (wcscmp(pe32.szExeFile, L"vmtoolsd.exe") == 0)       //注意此处用了wcscmp(pe32.szExeFile是 WCHAR*)
                {
                        return TRUE;
                }

                bMore = Process32Next(hProcessSnap, &pe32);           //遍历下一个进程
        }
        CloseHandle(hProcessSnap);
        return FALSE;
}

通过注册表检测

虚拟机中有非常多的虚拟硬件(不只是网卡,还有打印机、鼠标等等,都可以判断)
《【习题】Tencent2016C (虚拟机检测技术)》

以VMwareHostOpen.exe为例~

//通过注册表检测
BOOL CheckVMWare5()
{
        HKEY hkey;
        if (RegOpenKey(HKEY_CLASSES_ROOT, L"\\Applications\\VMwareHostOpen.exe", &hkey) == ERROR_SUCCESS) 
        {
                return TRUE;        //RegOpenKey函数打开给定键,如果存在该键返回ERROR_SUCCESS
        }
        else
        {
                return FALSE;
        }
}

通过特定服务检测

《【习题】Tencent2016C (虚拟机检测技术)》

//通过特定服务检测
BOOL CheckVMWare7()
{
        int menu = 0;
        SC_HANDLE SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);        //打开服务控制管理器
        if (SCMan == NULL)
        {
                cout << GetLastError() << endl;
                printf("OpenSCManager Eorror/n");
                return -1;
        }
        LPENUM_SERVICE_STATUSA service_status;
        DWORD cbBytesNeeded = NULL;
        DWORD ServicesReturned = NULL;
        DWORD ResumeHandle = NULL;
        service_status = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
        bool ESS = EnumServicesStatusA(SCMan,                                                                                //遍历服务
                SERVICE_WIN32,
                SERVICE_STATE_ALL,
                (LPENUM_SERVICE_STATUSA)service_status,
                1024 * 64,
                &cbBytesNeeded,
                &ServicesReturned,
                &ResumeHandle);
        if (ESS == NULL)
        {
                printf("EnumServicesStatus Eorror/n");
                return -1;
        }
        for (int i = 0; i < ServicesReturned; i++)
        {
                if (strstr(service_status[i].lpDisplayName, "VMware Tools") != NULL)
                {
                        return TRUE;
                }
        }
        CloseServiceHandle(SCMan);
        return FALSE;}

通过特定文件路径检测

//文件路径检测
BOOL CheckVMWare8()
{
        if (PathIsDirectory(L"C:\\Program Files\\VMware\\") == 0)        
        {
                return FALSE;
        }
        else
        {
                return TRUE;
        }
}

补位:时间差检测

泉哥的经典文章中有一种时间差检测的方式,感觉思路很好,反调试中可以很好的使用,但经过测试发现在虚拟机和主机中指令执行时间并没有明显区别,也应该是VM后面做过优化了~

原理:通过运行一段特定代码,然后比较这段代码在虚拟机和真实主机之中的相对运行时间,以此来判断是否处于虚拟机之中。可以通过RDTSC指令来实现,RDTSC指令是用于将计算机启动以来的CPU运行周期数存放到EDX:EAX里面,其中EDX是高位,而EAX是低位。

两者之间的运行时间明显差别很多,在虚拟机中的运行速度远不如真实主机的,一般情况下,当它的运行时间大于0xFF时,就可以确定它处于虚拟机之中了,代码如下:

//通过时间差检测
BOOL CheckVMWareTmp()
{
                __asm
        {
                rdtsc                   //RDTSC指令将计算机启动以来的CPU运行周期数存放到EDX:EAX里面,其中EDX是高位,而EAX是低位。
                xchg ebx, eax          //测试此条指令运行时间
                rdtsc
                sub eax, ebx           //时间差
                cmp eax, 0xFF
                jg detected
        }
        return FALSE;
detected:
        return TRUE;
}

其它思路

1.搜索物理内存中的VMware字符串
2.搜索特定的驱动模块(.sys)
3.检测0环的系统内核对象,痕迹很多,甚至是内核设备的符号链接也发现了特征
如图:
WinObj查看

《【习题】Tencent2016C (虚拟机检测技术)》

《【习题】Tencent2016C (虚拟机检测技术)》

总之方法很多,要是本着多多益善的思路,组合起来混在SEH里面,再加上花指令,TT,头大~

Anti反虚拟机

好吧,最重要的还是怎么反虚拟机检测啊,新手有这么几个思路:

查找反虚拟机的IDA 脚本

来自《恶意代码分析》
包括了各种特权指令和CPUID:

from idautils import *
from idc import *

heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA()))
antiVM = []
for i in heads:
        if (GetMnem(i) == "in" or GetMnem(i) == "cpuid" or GetMnem(i) == "sidt" or GetMnem(i) == "sgdt" or GetMnem(i) == "sldt" or GetMnem(i) == "smsw" or GetMnem(i) == "str"):
                antiVM.append(i)

print "Number of potential Anti-VM instructions: %d" % (len(antiVM))

for i in antiVM:
        SetColor(i, CIC_ITEM, 0x0000ff)
        Message("Anti-VM: %08x\n" % i)

File->Script file 加载py插件

《【习题】Tencent2016C (虚拟机检测技术)》

python大法好啊
我们来逆一波自己的程序好了

目标:反调第一种in指令
首先加载程序,因为代码在dll中,我们对loadLibraryEx下断,几次F9看到加载Tencent2016C.dll后alt+E

《【习题】Tencent2016C (虚拟机检测技术)》

此时把IDA基址改为当前DLL加载基址0X72690000

《【习题】Tencent2016C (虚拟机检测技术)》

现在直接在od中搜索0x726910D8就能定位in的位置了

《【习题】Tencent2016C (虚拟机检测技术)》

F9运行

《【习题】Tencent2016C (虚拟机检测技术)》

对于cpuid指令同理

修改虚拟机相关信息

目标:反调MAC地址
经常看到很多改MAC地址的软件,大部分应该是改了注册表,手动修改:
win+r -> 输入regedit->注册表
定位到网卡处:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class{4D36E972-E325-11CE-BFC1-08002bE10318}

《【习题】Tencent2016C (虚拟机检测技术)》

新建一个字符串值,命名为NetworkAddress,内容是新的MAC地址
“网络连接”中重启本地连接即可

《【习题】Tencent2016C (虚拟机检测技术)》

调整VM设置

VMware Tools中有一些未文档化的功能可以减轻反虚拟机技术探测,下面的代码放到VMware的.vmx文件的最后面:

isolation.tools.getPtrLocation.disable = "TRUE" 
isolation.tools.setPtrLocation.disable = "TRUE" 
isolation.tools.setVersion.disable = "TRUE" 
isolation.tools.getVersion.disable = "TRUE" 
monitor_control.disable_directexec = "TRUE" 
monitor_control.disable_chksimd = "TRUE" 
monitor_control.disable_ntreloc = "TRUE" 
monitor_control.disable_selfmod = "TRUE" 
monitor_control.disable_reloc = "TRUE" 
monitor_control.disable_btinout = "TRUE" 
monitor_control.disable_btmemspace = "TRUE" 
monitor_control.disable_btpriv = "TRUE" 
monitor_control.disable_btseg = "TRUE"

  其中directexec可以使用户模式下的代码被模拟执行而非直接在硬件上运行,因此可以ANTI一些反虚拟机技术。前四条设置被VMware后门命令使用,它们的作用是使得运行在Guest系统中的VMware Tools不能获取宿主系统的信息。这些设置会禁用VMware Tools的一些有用功能,并可能对虚拟机性能有严重负面影响(测试发现速度会明显降低,建议其它技术无效时再使用)

另:
1.monitor_control.restrict_backdoor = “true”
2.开启vmware workstation,虚拟机 -> 设置 -> 处理器 -> 禁用二进制翻译加速 可能在某些时候会有帮助

   总体而言感觉IDA插件的方式比较实用,比如有病毒在申请的空间中放入反虚拟机代码,我们可以在空间申请后DUMP下来,在IDA中搜索,再在OD中定位,这样能节省不少时间~
  而许多对硬件和信息的探测想anti就比较难了,没什么思路,总不能把虚拟机CPU、文件路径、服务名等等都改了吧,最好的方式,应该还是逆向分析破解吧 ~比如,一段代码在一个条件跳转处过早终止,可能就会用到反虚拟机技术~我们已经清楚正向怎么检测了,逆起来当然有思路喽~ 路漫漫其修远兮啊~ 大部分技术很多年没变化了,有新加入的,也有的随着VMware 更新已无法使用,和网上的一些资料多少会有冲突,查漏补缺,方便需要的朋友参照,文中测试如有不准确处,请各位指出~谢谢

参考资料

《恶意代码分析实战》 诸葛建伟 姜辉 张光凯译
《虚拟机检测技术剖析》 林桠泉

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注