如何利用Pafish侦测虚拟环境

本文介绍Pafish。Pafish是一个用来侦 测虚拟环境的开源工具,除了虚拟环境以外,Pafish还能侦测主流的沙箱和debug环境。由于Pafish是开源的,病毒作者们可以很容易的重用其代 码。Pafish的github主页在https://github.com/a0rtega/pafish。 目前是V05.3版。

运行Pafish以后,Pafish会对各项特征进行检查,如果没有发现匹配特征,就显示一个绿色的OK, 如果发现了,就显示一个红色的traced! 如图所示,Pafish检测到当前运行环境匹配virtualbox的各项特征。

virtualbox

看一下pafish的github目录,包含以下文件

github

基本上,Pafish用一个c文件来对应一个侦测模块。Pafish包含如下模块:

·基于cpu的侦测: cpu.c

·侦测 调试环境: debuggers.c

·基本侦测: gensandbox.c

·侦测主流商业虚拟环境:比如vitualbox,VMware等

3 模块介绍

3.1 基于cpu的侦测

由于架构差异,同样的代码在虚拟机的运行速度往往慢于实体机。Pafish用RDTSC指令来计算执行一段代码所花费的平均CPU运行周期数。RDTSC 指令可以以极小的代价获得高精度的 CPU 时钟周期数(Time Stamp Counter)。用法是,先后执行两次RDTSC,记下两个 64-bit 整数 ret 和 ret2,那么 ret2-ret1 代表了这期间 所花费的CPU 时钟周期数。

static inline unsigned long long rdtsc_diff() {
                             unsigned long long ret, ret2;
                             unsigned eax, edx;
                             __asm__ volatile("rdtsc" : "=a" (eax), "=d" (edx));
                             ret  = ((unsigned long long)eax) | (((unsigned long long)edx) << 32);
                             __asm__ volatile("rdtsc" : "=a" (eax), "=d" (edx));
                             ret2  = ((unsigned long long)eax) | (((unsigned long long)edx) << 32);
                             return ret2 - ret;
}

然后这个取差值的过程重复10次,得到平均值。如果这个平均周期差小于750的话,就认为是实体机环境,否则则认为是虚拟机环境。笔者估计Pafish选择750作为平均周期差的是根据实践经验。

int cpu_rdtsc() {
                             int i;
                             unsigned long long avg = 0;
                             for (i = 0; i < 10; i++) {
                                                          avg = avg + rdtsc_diff();
                                                          Sleep(500);
                             }
                             avg = avg / 10;
                             return (avg < 750 && avg > 0) ? FALSE : TRUE;
}

另一种方法是用cpuid指令得到cpu的名称和型号。如果名称中包含一些特殊的字符串,比如KVM, VMware等,则确定在虚拟环境中。

static inline void cpuid_vendor_00(char * vendor) {
                             int ebx, ecx, edx;
 
                             __asm__ volatile("cpuid" \
                                                                                       : "=b"(ebx), \
                                                                                         "=c"(ecx), \
                                                                                         "=d"(edx) \
                                                                                       : "a"(0x00));
                             sprintf(vendor  , "%c%c%c%c", ebx, (ebx >> 8), (ebx >> 16), (ebx >> 24));
                             sprintf(vendor+4, "%c%c%c%c", edx, (edx >> 8), (edx >> 16), (edx >> 24));
                             sprintf(vendor+8, "%c%c%c%c", ecx, (ecx >> 8), (ecx >> 16), (ecx >> 24));
                             vendor[12] = 0x00;
}
 
int cpu_known_vm_vendors(char * vendor) {
                             const int count = 4;
                             int i;
                             string strs[count];
                             strs[0] = "KVMKVMKVMKVM";
                             strs[1] = "Microsoft Hv";
                             strs[2] = "VMwareVMware";
                             strs[3] = "XenVMMXenVMM";
                             for (i = 0; i < count; i++) {
                                                          if (!memcmp(vendor, strs[i], 12)) return TRUE;
                             }
                             return FALSE;
}

3.2 侦测 调试环境:

第一种方法很简单,只需要看一下IsDebuggerPresent的返回值。IsDebuggerPresent是一个kernel32.dll中的函数,通过它可以检测当前进程是否正在被调试(用户模式)。

int debug_isdebuggerpresent() {
                             if (IsDebuggerPresent())
                             return TRUE;
                             else
                             return FALSE;
}

第二种方法用到OutputDebugString。OutputDebugString用来把调试信息输出到调试器的输出窗口。但 是OutputDebugString只能在调试环境下执行。而在非调试环境下,OutputDebugString会产生一个错误。Pafish先设置 一个错误代码(99)作为基准值,然后调用OutputDebugString,再取出错误代码与99比较。如果运行在一个调试环境中,那么应该没有新错 误产生,所以取出的错误代码还是99。

int debug_outputdebugstring() {
                             DWORD err = 99; /* Random error */
                             SetLastError(err);
                             /* If we're been debugging, this shouldn't
                             drop an error. */
                             OutputDebugString("useless");
                             if (GetLastError() == err){
                                                          return TRUE;
                             }
                             else {
                                                          return FALSE;
                             }
}

3.3 基本侦测:

由于硬件的限制,很多人在创建虚拟机的时候往往只选择够用的配置,而不是高配置。(至少配置不会高于实体机)基本侦测是基于鼠标的移动,CPU的数 量,硬盘和内存的大小等等。比如用户在一段时间内没有移动鼠标,系统只有一个CPU,硬盘小于60G,内存小于1G,这些都可能说明是一个虚拟环境。这种 侦测方法不会非常准确,比如一个服务器可能没有配备鼠标,或者一个配置很差的旧电脑都会被误认为是虚拟环境。但是从统计上看来,还是有一定意义的。

int gensandbox_one_cpu_GetSystemInfo() {
                             SYSTEM_INFO siSysInfo;
                             GetSystemInfo(&siSysInfo);
                             return siSysInfo.dwNumberOfProcessors < 2 ? TRUE : FALSE;
}

第二种方法是如果当前系统的用户名包含sandbox,virus,malware等关键字,则认为是在虚拟机中,当然这个检测方法可以很容易的绕过。

int gensandbox_username() {
                             char username[200];
                             size_t i;
                             DWORD usersize = sizeof(username);
                             GetUserName(username, &usersize);
                             for (i = 0; i < strlen(username); i++) { /* case-insensitive */
                                                          username[i] = toupper(username[i]);
                             }
                             if (strstr(username, "SANDBOX") != NULL) {
                                                          return TRUE;
                             }
                             if (strstr(username, "VIRUS") != NULL) {
                                                          return TRUE;
                             }
                             if (strstr(username, "MALWARE") != NULL) {
                                                          return TRUE;
                             }
                             return FALSE;
}

3.4 侦测商业虚拟环境

除了基本的虚拟环境以外,Pafish还可以根据指纹侦测几种主流的虚拟机,包括sandboxie,Qemu,Virtualbox, Vmware and wine.侦测方法大同小异,这里只介绍针对virtualbox的侦测方法。

在原有的基础上,Virtualbox提供了一些增强功能,比如virtualbox guest Additions 和共享文件夹。virtualbox guest Additions可以自动调节窗口的分辨率,把实体机的字符串复制到虚拟机里面。而 共享文件夹解决了虚拟机和实体机共享文件的问题。这些增强功能极大的提高了virtualbox的易用性。然而,福兮祸所伏。若想使用 virtualbox guest Additions,用户需要在虚拟机上安装一些特殊的驱动和应用程序。而这些驱动和应用基本不可能出现在实体机上。所以Pafish可以查找这些驱动和 应用来判断当前运行环境是否是virtualbox。

查找注册表,看看有没有VirtualBox Guest Additions的字符串。

int vbox_reg_key3() {
                             return pafish_exists_regkey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Oracle\\VirtualBox Guest Additions");
}

查找有没有VirtualBox Guest Additions的相关进程 (vboxservice.exe 和vboxtray.exe)

查找右下角有没有关于VboxTrayTool的任务栏托盘窗口.

int vbox_traywindow() {
                             HWND h1, h2;
                             h1 = FindWindow("VBoxTrayToolWndClass", NULL);
                             h2 = FindWindow(NULL, "VBoxTrayToolWnd");
                             if (h1 || h2) return TRUE;
                             else return FALSE;
}

查找是否存在以下文件

"C:\\WINDOWS\\system32\\vboxdisp.dll";
"C:\\WINDOWS\\system32\\vboxhook.dll";
"C:\\WINDOWS\\system32\\vboxmrxnp.dll";
"C:\\WINDOWS\\system32\\vboxogl.dll";
"C:\\WINDOWS\\system32\\vboxoglarrayspu.dll";
"C:\\WINDOWS\\system32\\vboxoglcrutil.dll";
"C:\\WINDOWS\\system32\\vboxoglerrorspu.dll";
"C:\\WINDOWS\\system32\\vboxoglfeedbackspu.dll";
"C:\\WINDOWS\\system32\\vboxoglpackspu.dll";
"C:\\WINDOWS\\system32\\vboxoglpassthroughspu.dll";
"C:\\WINDOWS\\system32\\vboxservice.exe";
"C:\\WINDOWS\\system32\\vboxtray.exe";
"C:\\WINDOWS\\system32\\VBoxControl.exe";
"C:\\program files\\oracle\\virtualbox guest additions\\";
"C:\\WINDOWS\\system32\\drivers\\VBoxMouse.sys";
"C:\\WINDOWS\\system32\\drivers\\VBoxGuest.sys";
"C:\\WINDOWS\\system32\\drivers\\VBoxSF.sys";
"C:\\WINDOWS\\system32\\drivers\\VBoxVideo.sys";

用WNetGetProviderName检查共享文件夹的网络类型名称,如果是”VirtualBox Shared Folders”,则检查到virtualbox的共享文件夹

int vbox_network_share() {
                             unsigned long pnsize = 0x1000;
                             char provider[pnsize];
                             int retv = WNetGetProviderName(WNNC_NET_RDR2SAMPLE, provider, &pnsize);
                             if (retv == NO_ERROR) {
                                                          if (lstrcmpi(provider, "VirtualBox Shared Folders") == 0) {
                                                                                       return TRUE;
                                                          }
                                                          else {
                                                                                       return FALSE;
                                                          }
                             }
                             return FALSE;
}

以上,我们可以看到如果在虚拟机上安装了virtualbox guest Additions 和共享文件夹,该虚拟机的其他进程可以很容易的发现其相关的窗口,文件,驱动,服务信息。这就相当于李鬼在脸上写着“我不是李逵,我是李鬼”。那么,是不 是不安装virtualbox guest Additions 和共享文件夹就安全了呐?答案是未必。一些默认的硬件信息仍然可以被作为指纹用于检测virtualbox的存在。比如virtualbox默认的网卡 MAC地址前缀为08:00:27,这前3字节是virtualbox分配的唯一标识符OUI,以供其虚拟网卡使用。

int vbox_mac() {
                             /* VirtualBox mac starts with 08:00:27 */
                             return pafish_check_mac_vendor("\x08\x00\x27");
}

另外,还可以读取注册表信息,通过检测特定的硬件信息,比如SCSI, systembiosversion, videobiosversion, ACPI,如果这些信息 包含VBOX关键字,那么可以断定是virtualbox虚拟环境。

int vbox_reg_key1() {
                             return pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 0\\Scsi Bus 0\\Target Id 0\\Logical Unit Id 0", "Identifier", "VBOX");
}

还可以检测其系统bios生成日期,如果是1999年6月23号,那么很可能是virtualbox

int vbox_reg_key10() {
                             return pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System", "SystemBiosDate", "06/23/99");
}

4. 总结

综上,Pafish给出了一些很实用的检测虚拟机 的方法。虚拟的毕竟就是虚拟的,总归会留下蛛丝蚂迹。不过很多人认为我们也可以利用虚拟机检测来免疫病毒,比如在实体机上设置一些虚假信息让病毒误认为这 个实体机是一个虚拟机,从而跳过“做坏事”的阶段。对于这个观点,有人认为靠谱,有人认为不靠谱,大家怎么看?

发表评论

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