题目概要
要求说明:
编写一个【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位测试
代码原理
执行特权指令检测
原理:在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,而在虚拟机,不为0。同时对于GTR,虚拟机中应为0xFFXXXXXX , 否则为真机。
简单看一下idt:
打开双机调试(建议Virtual KD,配置较简单)
当前我们已经在调试虚拟机的操作系统了,所以直接看这三个表的地址~
好吧,这哪里是虚拟机,不就是真机啊!看来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
查询到虚拟机的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”;
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地址类似,就是要观察虚拟机,观察它的各种系统信息,设备信息,再找相关的信息。
搜索特定进程
原理:会有一些虚拟机特有的进程,可以通过检测这些进程是否存在来判断~
显然我们可以从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;
}
通过注册表检测
虚拟机中有非常多的虚拟硬件(不只是网卡,还有打印机、鼠标等等,都可以判断)
以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;
}
}
通过特定服务检测
//通过特定服务检测
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查看
总之方法很多,要是本着多多益善的思路,组合起来混在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插件
python大法好啊
我们来逆一波自己的程序好了
目标:反调第一种in指令
首先加载程序,因为代码在dll中,我们对loadLibraryEx下断,几次F9看到加载Tencent2016C.dll后alt+E
此时把IDA基址改为当前DLL加载基址0X72690000
现在直接在od中搜索0x726910D8就能定位in的位置了
F9运行
对于cpuid指令同理
修改虚拟机相关信息
目标:反调MAC地址
经常看到很多改MAC地址的软件,大部分应该是改了注册表,手动修改:
win+r -> 输入regedit->注册表
定位到网卡处:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class{4D36E972-E325-11CE-BFC1-08002bE10318}
新建一个字符串值,命名为NetworkAddress,内容是新的MAC地址
"网络连接"中重启本地连接即可
调整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,虚拟机 -> 设置 -> 处理器 -> 禁用二进制翻译加速 可能在某些时候会有帮助