Havoc远控源码剖析(协议篇)

开发 前端
CobaltStrike、Sliver​常用的基本RSA+AES​模式都没有实现到,甚至AES​密钥同加密包一同发送。这个水准属于是有点让人失望了,希望在Agent端能够让人改观。

近期阅读了一款开源远控Havoc的源码,留下了一些笔记,干脆发出来一起学习一下,这个远控据说使用了很多高端免杀技术,比如Ekko,Ziliean,FOLIAGE睡眠混淆,返回地址欺骗,Indirect SysCall,Etw Patch,堆加密等等。

前言

FullSessionGraph.jpegFullSessionGraph.jpeg

先简单说说它TeamServer端是用golang写的,Agent端是C写的,UI是C++基于QT的,具体使用还是很多BUG,但抱着学习的心态来看看。

目录结构

首先想着看看这个C2协议这块咋样,于是读读TeamServer端的代码,看看目录结构,命名还是很清晰明了的。

Pasted image 20230906161038.pngPasted image 20230906161038.png

话不多说,直接进入主题,远控提供了HTTP(S)、SMB的Agent,SMB是内网中继直连用的,直接来看HTTP(S)方面的代码。。

握手前校验

首先只有POST请求会被处理,其他请求都是直接跳fake404页面。

h.GinEngine.POST("/*endpoint", h.request)
h.GinEngine.GET("/*endpoint", h.fake404)

request里首先是对请求的header头进行判断,不符合直接跳fake404。

Pasted image 20230906160945.pngPasted image 20230906160945.png

具体的Header头定义在havoc.yaotl中。

Pasted image 20230906160453.pngPasted image 20230906160453.png

然后是检查url、ua,同样是不符合跳fake404,默认配置的url是这样子的。

Uris = [
    "/funny_cat.gif",
    "/index.php",
    "/test.txt",
    "/helloworld.js"
]

Pasted image 20230906161244.pngPasted image 20230906161244.png

数据包的处理

经过一连串的判断后,来到parseAgentRequest函数,开始对Body内容进行判断。

Pasted image 20230906161529.pngPasted image 20230906161529.png

在ParseHeader中,最终是返回Header结构。

type Header struct {
	Size       int
	MagicValue int
	AgentID    int
	Data       *parser.Parser
}

Pasted image 20230906162130.pngPasted image 20230906162130.png

NewParser时,试图将body内容赋值给Parser结构的buffer中,至于bigEndian默认是true。

type Parser struct {
	buffer    []byte
	bigEndian bool
}

这里似乎将数据包分为了三种情况:

p.Length()小于4的情况下,直接丢弃该包,返回空的Response;

p.Length()等于4的情况下,直接将所有data复制到Header.Data中;

p.Length()大于4的情况下,将从末尾分别切出Size、MagicValue、AgentID,各为4个字节;

所以一个正常数据包的结构大致应该如下所示。

Pasted image 20230906172119.pngPasted image 20230906172119.png

然后如果切出的MagicValue等于DEMON_MAGIC_VALUE,也就是0xDEADBEEF。

Pasted image 20230906170312.pngPasted image 20230906170312.png

意味着是普通Deomon,否则是第三方Agent...等会,它是不是忘了什么?加解密呢?这不是白给么,建议做blueteam的小伙伴加一下流量规则。

Agent注册

继续跟进到DemonAgent,进来直接查AgentID;

if Teamserver.AgentExist(Header.AgentID){
	...
}else{
	...
}

函数内容是迭代Teamserver中所有的Agent,true的话就是已经存在,先看false情况,也就是注册的功能。

Pasted image 20230906171621.pngPasted image 20230906171621.png

再次切掉一个CommandID,如果CommandID等于agent.DEMON_INIT也就是99,就意味着是注册包,然后切掉RequestID丢掉,进入注册流程。

Agent = agent.ParseDemonRegisterRequest(Header.AgentID, Header.Data, ExternalIP)
if Agent == nil {
    return Response, false
}
go Agent.BackgroundUpdateLastCallbackUI(Teamserver)

接着从末尾切出AESKey和AESIv,并调用Parser.DecryptBuffer对Parser.buffer进行解密,解密完的结果放回buffer里。

Pasted image 20230906204004.pngPasted image 20230906204004.png

所以数据包具体应该是这样的。

Pasted image 20230906204313.pngPasted image 20230906204313.png

至于解密出来的buffer,据官方说法如下。

[ Agent ID     ] 4 bytes <-- this is needed to check if we successfully decrypted the data
			[ Host Name    ] size + bytes
			[ User Name    ] size + bytes
			[ Domain       ] size + bytes
			[ IP Address   ] 16 bytes?
			[ Process Name ] size + bytes
			[ Process ID   ] 4 bytes
			[ Parent  PID  ] 4 bytes
			[ Process Arch ] 4 bytes
			[ Elevated     ] 4 bytes
			[ Base Address ] 8 bytes
			[ OS Info      ] ( 5 * 4 ) bytes
			[ OS Arch      ] 4 bytes
			..... more

如果注册成功,将返回这个Agent的AgentID。

Pasted image 20230906205319.pngPasted image 20230906205319.png

心跳包

回到AgentExist,如果已经注册,进入心跳包流程。

/* get our agent instance based on the agent id */
Agent = Teamserver.AgentInstance(Header.AgentID)
Agent.UpdateLastCallback(Teamserver)

先根据ID查出对象,更新心跳时间,同样切出Command和RequestID。

Command = uint32(Header.Data.ParseInt32())
RequestID = uint32(Header.Data.ParseInt32())

这里有点小混乱,划分了第一次post的包和重连的包,如果是第一次提交,先进行解密(小声叨叨:那重连的包不用解密了?)

Pasted image 20230906210103.pngPasted image 20230906210103.png

然后判断命令,是任务回显还是GET_JOB。

Pasted image 20230906210153.pngPasted image 20230906210153.png

如果是GET_JOB,就从Agent.GetQueuedJobs()拿任务,又分别对COMMAND_PIVOT、COMMAND_SOCKET、COMMAND_FS、COMMAND_MEM_FILE额外追加了一些参数,尤其是COMMADN_PIVOT内,如果是DEMON_PIVOT_SMB_COMMAND另外特殊处理。另外三个还没有开发完毕,是空着的。

Pasted image 20230906211059.pngPasted image 20230906211059.png

没有细看,大意是对内网SMB Agent进行Socks代理时的特殊处理。

小结

算了,通讯协议这部分大概就看到这里了,总结一下,其通讯协议整体来说是不那么可靠的。

CobaltStrike、Sliver常用的基本RSA+AES模式都没有实现到,甚至AES密钥同加密包一同发送。这个水准属于是有点让人失望了,希望在Agent端能够让人改观。

本文作者:t43, 转载请注明来自FreeBuf.COM

责任编辑:武晓燕 来源: ​FreeBuf.COM
相关推荐

2017-11-06 05:52:52

2010-08-05 17:43:27

RIP协议

2010-01-09 15:14:29

Windows 7多点触摸

2022-09-27 18:56:28

ArrayList数组源代码

2010-06-30 16:00:01

FTP协议

2010-06-11 14:51:34

IS-IS路由协议

2022-03-24 14:40:31

开发Harmony鸿蒙

2015-07-16 10:56:01

2023-01-10 13:48:50

ContainerdCRI源码

2022-04-29 14:56:40

通话应用源码剖析

2022-08-04 08:44:31

Android系统攻击链

2012-12-27 10:44:05

2010-06-18 13:53:22

AMF协议

2011-08-24 09:46:33

NetBIOS协议NetBEUI协议

2015-07-16 15:52:30

2021-03-24 07:16:57

RocketMQ源码解析Topic

2010-07-07 10:21:35

IGRP路由协议

2021-09-05 17:22:08

Strview.js工具js
点赞
收藏

51CTO技术栈公众号