转载的好兄弟的一篇文章,校验前台窗口绕过沙箱在最后
本公众号技术文章仅供参考! 文章仅用于学习交流,请勿利用文章中的技术对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。 |
Intro
在恶意软件开发领域中,沙箱对抗是一个经典的门类。
我们通常不希望我们的恶意软件在蓝队的沙箱中运行并被动态分析,因此识别沙箱环境并作出判断是极其重要的学问。
本篇wiki将以实践角度出发,手搓一个沙箱检测程序。
思路列举:
那么说到这里,可以依据哪些运行环境里的特征来判断呢?
1.vmtoolsd.exe进程--VMware Tools Core Service,虚拟机内的代表性进程
2.内存大小--低于8G的不太可能是办公机,而沙箱环境通常为了节省资源不会分配大内存
3.单位时间内鼠标移动量/前台应用程序窗口切换次数--鼠标动也不动,就是没装桌面环境的沙箱
4.域名--枚举一下当前机器的域名,是否与目标域中的机器匹配
5.时间--沙箱环境的复位频率
6.CPU类型--办公机器通常不会用冷门的CPU
7.Chrome,WeChat,QQ等常见PC机中的进程
8.DNS查询
........
以及一些用户侧的校验,这里可以延申一下:
1.我们希望用户通过双击可执行文件进行运行,而不是索引可执行文件路径之后用cmd或者powershell命令行进行运行。
2.我们希望限制到只允许存在本地管理员权限的用户打开并执行我们的恶意软件。
......
功能模块设计与演示:
我们首先写一个基础的C程序框架
#include<windows.h>
#include<stdio.h>
int main(){
return 0;
}
1.进程名称校验
接下来我们要写一个进程名称检测器,如果我们发现我们指定的进程在环境中运行,那么就退出进程。
这里的思路是:枚举全部进程-->匹配指定进程-->行为判断。
怎么枚举全部进程呢?我们需要对当前系统的进程拍摄一个快照,之后从中进行检索。这就是Windows最典型的进程枚举机制。
这里我们需要使用tlhelp32.h中的函数,以下是一个典型的进程枚举模块
BOOL bResult = FALSE;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//打印全部进程的快照
PROCESSENTRY32 pe32; //声明一个进程入口数据结构
pe32.dwSize = sizeof(PROCESSENTRY32);
if(Process32First(hSnap, &pe32)) {
do {
if(strcmp(pe32.szExeFile, app) == 0) { //循环对比进程名称,如果找到就把bResult设置为TRUE
bResult = TRUE;
pid = pe32.th32ProcessID;
printf("pid == %dn",pid); //打印对应进程的pid
break;
}
} while(Process32Next(hSnap, &pe32));
}
CloseHandle(hSnap); //关闭把手
那么我们现在的代码是:
#include<windows.h>
#include<stdio.h>
#include<tlhelp32.h>
BOOL CheckAppRunning(char *procname){
BOOL bResult = FALSE;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
DWORD pid = 0;
if(Process32First(hSnap, &pe32)) {
do {
if(strcmp(pe32.szExeFile, procname) == 0) {
bResult = TRUE;
pid = pe32.th32ProcessID;
printf("Processname ==%s : pid == %dn",procname,pid);
break;
}
} while(Process32Next(hSnap, &pe32));
}
CloseHandle(hSnap);
return bResult;
}
int main(){
if(!CheckAppRunning("vmtoolsd.exe")){
printf("NICE,it's not a Vm machinen");
}else{
printf("Alert! it a Vitual Machine ,and the vmtoolsd is enabled n");
printf("Let's drop the process,goodbyen");
ExitProcess(0); //退出当前进程,中止可执行文件的执行流
}
return 0;
}
这是运行效果:我们成功枚举到了vmtoolsd.exe进程,并且做出了合适的判断。
2.校验运行环境的内存大小
接下来我们判断一下当前环境的运行内存有多大。如果是2G这种,那么肯定是沙箱而不是办公机器。
这里的核心是用GetPhysicallyInstalledSystemMemory()函数枚举内存大小并通过引用返回。
这个函数是在sysinfoapi.h中,因此我们需要引入一下。
BOOL GetMemory(DWORD dwSize){
BOOL bResult = FALSE;
DWORD64 dwTotalMem;
GetPhysicallyInstalledSystemMemory(&dwTotalMem); //通过引用传递回总的RAM量,注意这里的单位为KB
if(dwTotalMem / (1024*1024) >= dwSize) //KB-->MB-->GB 单位转化
bResult = TRUE;
return bResult;
}
现在的代码为:
#include<windows.h>
#include<stdio.h>
#include<tlhelp32.h>
#include<sysinfoapi.h>
BOOL CheckAppRunning(char *procname){
BOOL bResult = FALSE;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
DWORD pid = 0;
if(Process32First(hSnap, &pe32)) {
do {
if(strcmp(pe32.szExeFile, procname) == 0) {
bResult = TRUE;
pid = pe32.th32ProcessID;
printf("Processname ==%s : pid == %dn",procname,pid);
break;
}
} while(Process32Next(hSnap, &pe32));
}
CloseHandle(hSnap);
return bResult;
}
BOOL GetMemory(DWORD dwSize){
BOOL bResult = FALSE;
DWORD64 dwTotalMem;
GetPhysicallyInstalledSystemMemory(&dwTotalMem);
if(dwTotalMem / (1024*1024) >= dwSize)
bResult = TRUE;
return bResult;
}
int main(){
if(!CheckAppRunning("vmtoolsd.exe")){
printf("NICE,it's not a Vm machinen");
}else{
printf("Alert! it a Vitual Machine ,and the vmtoolsd is enabled n");
printf("Let's Drop the process,goodbyen");
ExitProcess(0);
}
if(!GetMemory(8)){
printf("It's a small sandboxn");
}else{
printf("PC standard memoryn");
}
return 0;
}
编译并运行,发现我们的GetMemory函数未被调用,这是为什么呢?
很简单,因为ExitProcess(0)函数被调用了,执行到这一步时执行流就结束了。因此我们需要注释掉这行代码之后再跑一次,看看效果。注意,我的虚拟机是8G内存,所以这里会判断为是办公机器。
3.校验前台窗口的名称变化
Windows是基于图形化环境的操作系统,正常情况下用户在使用操作系统时会在多个应用程序的界面之间进行切换,此时前台窗口的名称也会随之变化。
我们想要确保用户使用鼠标打开该恶意软件,而且期望用户存在一定的界面交互。
为了针对这一点做校验,那么我们需要用到GetForegroundWindow()函数,它在winuser.h里被定义。
接下来我们手搓该功能模块。
BOOL MonitorForegroundWindows(){
BOOL bResult = FALSE; //初始化函数模块的返回值,此时交互为假
DWORD DW_MAX_SIZE = 256; //存放字符串的尺寸
DWORD MIN_COUNT = 100; //迭代次数
CHAR current[DW_MAX_SIZE]; //存放当前前台窗口名称的字符数组
DWORD passed = 0; //迭代变量
DWORD moved = 0; //交互次数
memset(current,0x00,DW_MAX_SIZE); //初始化字符数组的值
while(passed < MIN_COUNT){
HWND hwnd = GetForegroundWindow(); //枚举操作系统的此时的前台窗口,获得一个窗口句柄HWND
CHAR *title = (CHAR*)GlobalAlloc(GPTR,DW_MAX_SIZE + 1);//用于遍历迭代实时窗口名称的字符数组
GetWindowTextA(hwnd,title,DW_MAX_SIZE); //获取窗口句柄,之后通过API将窗口名称赋值给title
if(strcmp(title,current) != 0){ //当窗口名称发生变化时
strncpy(current,title,DW_MAX_SIZE); //重新赋值窗口名称
moved++; //用于记录变化次数的迭代参数值+1
}
printf("now the Foreground window is:%s and the passed is %dn",title,passed);//打印每次的窗口名、迭代值
strncpy(current,title,DW_MAX_SIZE); //重新赋值窗口名称
Sleep(100); //延时0.1秒,构成窗口期间隔
passed++; //迭代变量+1
GlobalFree(title); //释放堆栈
}
if(moved > 2){ //如果发生2次变化以上
printf("moved %d times between windowsn",moved); //打印变化次数
bResult = TRUE; //布尔值为真,即判断交互为真
}
return bResult;
}
此时的代码为:
#include<windows.h>
#include<stdio.h>
#include<tlhelp32.h>
#include<sysinfoapi.h>
#include<winuser.h>
BOOL CheckAppRunning(char *procname){
BOOL bResult = FALSE;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
DWORD pid = 0;
if(Process32First(hSnap, &pe32)) {
do {
if(strcmp(pe32.szExeFile, procname) == 0) {
bResult = TRUE;
pid = pe32.th32ProcessID;
printf("Processname ==%s : pid == %dn",procname,pid);
break;
}
} while(Process32Next(hSnap, &pe32));
}
CloseHandle(hSnap);
return bResult;
}
BOOL GetMemory(DWORD dwSize){
BOOL bResult = FALSE;
DWORD64 dwTotalMem;
GetPhysicallyInstalledSystemMemory(&dwTotalMem);
if(dwTotalMem / (1024*1024) >= dwSize)
bResult = TRUE;
return bResult;
}
BOOL MonitorForegroundWindows(){
BOOL bResult = FALSE;
DWORD DW_MAX_SIZE = 256;
DWORD MIN_COUNT = 100;
CHAR current[DW_MAX_SIZE];
DWORD passed = 0;
DWORD moved = 0;
memset(current,0x00,DW_MAX_SIZE);
while(passed < MIN_COUNT){
HWND hwnd = GetForegroundWindow();
CHAR *title = (CHAR*)GlobalAlloc(GPTR,DW_MAX_SIZE + 1);
GetWindowTextA(hwnd,title,DW_MAX_SIZE);
if(strcmp(title,current) != 0){
strncpy(current,title,DW_MAX_SIZE);
moved++;
}
printf("now the Foreground window is:%s and the passed is %dn",title,passed);
strncpy(current,title,DW_MAX_SIZE);
Sleep(100);
passed++;
GlobalFree(title);
}
if(moved > 2){
printf("moved %d times between windowsn",moved);
bResult = TRUE;
}
return bResult;
}
int main(){
if(!CheckAppRunning("vmtoolsd.exe")){
printf("NICE,it's not a Vm machinen");
}else{
printf("Alert! it a Vitual Machine ,and the vmtoolsd is enabled n");
printf("Let's Drop the process,goodbyen");
//ExitProcess(0);
}
if(!GetMemory(8)){
printf("It's a small sandboxn");
}else{
printf("PC standard memoryn");
}
if(!MonitorForegroundWindows()){
printf("It's now running by cmdlinen");
}else{
printf("interactions more,ready to payloads executedn");
}
return 0;
}
编译一下,运行看效果。
当我们在命令行中直接运行,且不切换前台窗口时,效果如下:
而如果我们在10秒的窗口期内频繁切换前台应用窗口,效果则为:
结语:
搭配不同的检测逻辑,就可以有针对性地投送载荷。同样,沙箱检测还可以有更多的玩法,但此处就不一一演示了。
原文始发于微信公众号(小惜渗透):bypass tips - 通过校验前台窗口绕过沙箱
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论