目录
笔记
VMware痕迹:VMware虚拟环境在系统中遗留了很多的痕迹,特别是VMware Tools安装之后。可以通过操作系统的文件系统,注册表和进程列表中的标记痕迹探测VMware的存在。
识别进程:VMwareService.exe VMwareTray.exe VMwareUser.exe Service.exe等都是关于虚拟机的进程
识别MAC地址:00:0C:29
识别内存痕迹:搜索整个物理内存中VMware的字符串
查找漏洞指令:反虚拟机的X86指令:sidt,sgdt,sldt,smsw,str,in。cupid
使用Red Pill反虚拟机技术:通过sidt指令获取IDTR寄存器的值。调用sidt指令,将IDTR寄存器的值存储,IDTR由6个字节组成,其中第五个字节的偏移量包含IDT的内存地址,将这个值与0xff比较去判断VMware存在。
使用NO Pill技术:sgdt与sldt指令探测VMware是否存在。NO Pill依赖于LDT结构,正常情况下Windows不会使用LDT结构,而在虚拟机中是非0值。解决方法:防止sldt探测方法-->VM Setting Processors 勾选Disable Acceleration
查询I/O通信端口:经常在Storm worm和Phatdot蠕虫和僵尸程序中,in指令把源操作数指定的端口复制到目的操作数的内存地址。解决方法:NOP指令替换in指令
var_1C是一个内存地址,用来存放从VMware返回的所有响应。inc指令使用参数EAX(magic参数),ECX操作数,返回值在EBX中。如果magic和VMXh匹配,代码运行在虚拟机中,将值放到EBX中。
使用str指令:用来从寄存器中检索段选择子,段选择子指向当前运行的任务状态段(TSS)。str指令大返回值在虚拟机和宿主机中是不同的。
解决:1.IDApython,通过IDApython将这些指令找出来
from idautils import*
from idc import*
heads = Heads(SegStart(ScreenEA()),SegEnd(ScreenEA)))
antiVM = []
for i in heads:
if(GetMnem(i) == "sidt" or GetMnem(i) == "sgdt" or GetMnem(i) == "sldt" or GetMnem(i) == "smsw" or GetMnem(i) == "str" or GetMnem(i) == "in" or GetMnem(i) == "cupid"):
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)
2.ScoopyNG:探测VMware探测工具
调整设置:
directexec指定用户模式下的代码模拟执行,而不是在硬件上运行。
前四条设置被VMware后门命令使用,不能获取宿主系统的信息
总结:一个代码在一个条件跳转处过早终止
实验
Lab17-1
运行给定的python脚本,查找漏洞指令。对应结果可以看出有三处
No Pill,开始eax被初始化成一个向量DDCCBBAA,sldt指令的值放到了var_8的位置,然后又给到了eax位置。然后看一下哪了调用了这里的代码
查看初始向量的低序列位是否被设置为了0(在虚拟机中是非0值)。如果不是就执行退出
Red Pill,调用sidt指令,将IDTR寄存器的值存储,IDTR由6个字节组成,其中第五个字节的偏移量包含IDT的内存地址,将这个值与0xff比较去判断VMware存在。
str,将任务状态段(TSS)载入到一个4字节的本地变量var_418中。检测是不是FF。如果是VMware的话,第一个判断处edx是0,第二个判断处eax是40
Lab17-2
dll的导出函数,进入第一个安装函数
在调用完sub_10006196函数后,有两个分支,一个是检测到虚拟机。所以sub_10006196函数是一个检测虚拟机的函数
sub_10006196
in指令来查询I/O通信端口,因为VMware在虚拟机与宿主操作系统之间会使用虚拟的I/O端口进行通信。而在0x100061D6处,这个虚拟端口被保存到了edx中,而前一条指令中,则是将执行的动作,也就是0x0A(获取VMware的版本类型)保存到了ecx里面。在0x100061C7处,magic数0x564d5868(VMXh)被载入到了eax中。
在in指令后,恶意程序使用cmp指令来检查magic数的回应。比较的结果会保存在var_1C里面,最后在0x100061FA的位置,作为函数的返回值,送入al里面。 这个恶意程序似乎并不关心VMware的版本,它仅仅想用一个magic值来查看I/O通信端口的回应
上面代码中的jz指令用于决定是否执行虚拟机的检测。还使用了atoi函数将字符转化为数字。转化的内容是off_10019034,也就是“[This is DVM]5”。这个字符串会保存在eax里面,之后再与0x0D相加,意味着将字符串指针移动了13个字节,指向了字符“5”。之后利用atoi将字符“5”转化为数字的5。下面的test指令用于查看转化后的数字是否为0。那么依据这一点,如果我们将这个字符串修改为“[This is DVM]0”,那么恶意程序就不会执行虚拟机的检查了。
Lab17-3
第一处反虚拟机技术
in指令来查询I/O通信端口,因为VMware在虚拟机与宿主操作系统之间会使用虚拟的I/O端口进行通信。当在虚拟机中时会返回eax=1。
这个函数的返回值将会影响接下来代码的流程,所以可以修改test eax,eax为xor eax,eax
第二处反虚拟机技术
sub_4011C0的交叉引用图,指向了多个注册表函数,并且调用了自己本身
检查系统的驻留痕迹。如果说我们进一步检查sub_4011C0函数,可以发现它在循环遍历DeviceClasses
下的注册表子键,并且与vmware字符串进行匹配,以检测当前是否运行在虚拟机中。
第三处反虚拟机技术
GetAdaptersInfo函数可以用来获取网络适配器的相关信息,它需要Iphlpapi.dll这个动态链接库的支持。将dword_403114这个函数的起始地址重命名。查看交叉引用,进入函数sub_401670函数
sub_401670
开始的部分展示了一系列mov字节,把这里设置成一个27字节的数组,并命名为Byte_Array数组,便于以后分析。
第一次调用GetAdaptersInfo函数,压入的第一个参数为0(null),链表的大小在dwBytes中。调用函数后返回的是数据的大小
第二次调用GetAdaptersInfo函数,压入的第一个参数为是HeapAlloc的返回值,最后返回的是一个大小为dwBytes的IP_ADAPTER_INFO结构链表的一个指针。手动添加IP_ADAPTER_INFO结构体
Type是标准常量,0x6--->以太网,0x71--->802.11无线适配器。比较网络接口是以太网接口还是无线接口。然后检查适配器长度是否大于2。如果成功往下看
repe是一个串操作前缀,它重复串操作指令,每重复一次ECX的值就减一 一直到CX为0或ZF为0时停止。
cmpsb是字符串比较指令,把ESI指向的数据与EDI指向的数一个一个的进行比较。
repe cmpsb指令含义将IP_ADAPTER_INFO.Address的前三个字节与Byte_Array比较。其中{00,50,56}和{00,0C,29}都是默认VMware MAC地址的默认开始。比较了9个不同的MAC地址。这里是一个循环。
第四处反虚拟机技术
进入sub_401130
sub_401130
这里功能是遍历进程列表,猜测应该是比对虚拟机的字符串,但是这里并没有明显的关于虚拟机字符串。获取进程名后,调用sub_401060函数把字母转化为小写,然后再执行sub_401000函数。结果与arg_0(0xF30D12A5)进行比较。
进入sub_401000
这是一个简单的字符串哈希函数,给定参数vmware,返回0xF30D12A5。避免了明文的vmware字符串