分析国外开源远控Quasar(类星体)的通讯建立过程
前言:
QuasarRat木马是一款开源的功能强大的远程工具。https://github.com/quasar/QuasarRAT。著名的 APT攻击组织 "摩诃草"曾长期使用此款工具。QuasarRat的控制端与被控端在建立通讯连接前,双方总共经历了3次握手过程,最后才成功建立连接。
一、控制端发起第一次握手
1、控制端与被控端建立连接后,触发数据包发送事件
控制端启动后执行代码
执行初始化函数InitializeServer()
创建QuasarServer类实例化对象
同时 注册包类型并序列化、注册连接状态事件、注册读取接收数据事件
包类型如下,其中GetAuthentication包是我们第一个要分析的
如果与被控端连接成功,控制端将主动发送GetAuthentication包
如何发送数据包,我们后面详谈。
2、控制端监听本地端口,建立连接
2.1 建立连接,触发发送GetAuthentication包指令
在控制端界面中设置启动监听
端口监听按钮对应代码如下
监听函数
进入监听函数
没有启动ipv6,执行如下代码,绑定端口,建立socket连接
继续向下执行,创建异步套接字操作,注册回调处理函数AcceptClient()
进入异步操作处理函数
控制端与被控端一旦连接成功,重点关注下面3段代码
1)第一段代码 这里主要考虑控制端的接收效率和用户体验,所以对成功建立的每一个连接,都创建一个连接实例,
异步接收数据
2)第二段代码 添加到被控端实例数组
注册三种触发事件,状态事件、接收数据事件和发送数据事件
被控端状态事件
接收数据事件
发送数据事件
3)改变与被控端的连接状态为true
这里将触发GetAuthentication包发送事件
因为子类QuasarServer注册了ClientState事件,事件触发后将执行OnClientState()函数
事件触发函数,将发送GetAuthentication包
2.2 连接建立成功, 发送GetAuthentication包
GetAuthentication包类
由创建的被控端实例负责发送GetAuthentication包
进入发送函数
GetAuthentication包经过序列化后,存入内存流对象
将内存流中数据写入待发字节数组payload
payload存入_sendBuffers字节数组队列
调用真正的发送函数
真正的发送函数
从_sendBuffers字节数组队列中取出待发数据payload
payload被组装成自定义格式的包
进入组装函数BuildPacket()
经过压缩混淆和AES加密
定义待发包长度:payload数据长度 + 固定的头长度(4字节)
packet字节数组中,前4个字节存储待发数据(payload)长度
从第5个字节开始存储待发数据(payload)
至此通信PAYLOAD包格式已经清楚了,如下:
2.3 进入调试模式
主要为了查看通讯PAYLOAD包中的内容,所以只在Send<T>(T packet)和Send(object state)两个函数中设置断点
GetAuthentication包序列化后,存储流中存入了106
106正是程序开始时,序列化所有包后,GetAuthentication包数组中的位置
106被赋值给payload字节数组
进入装包函数
payload经过混淆压缩后,数据变成14字节,内容如下:
payload经过加密后 ,数据变成64字节
定义待发包长度:payload数据长度 + 固定的头长度(4字节)
packet字节数组中,前4个字节存储待发数据(payload)长度
继续执行
从第5个字节开始存储待发数据(payload)
PAYLOAD包格式
控制端发送GetAuthentication包,经过序列化后,payload 数值为106,再经过压缩混淆和加密后,封装成长度是68字节的PAYLOAD包。
实际的通讯PAYLOAD包格式与前面分析的相同,如下
控制端发出payload包
二、被控端接收数据,完成第一次握手
1、建立连接
被控端执行后,进行一些初始化的操作,包括窗口隐藏、自启动等。
进入连接函数
设置连接的IP和端口
异步连接建立
2 初始化
在连接建立前,要执行一个初始化函数Initialize()
进入Initialize()函数,创建了QuasarClient类实例化对象
QuasarClient对象创建中,注册三种触发事件,状态事件、接收数据事件和异常事件
这里重要的只有接收数据事件
当接收到控制端发送的数据时OnClientRead()函数被触发
当接收到控制端发来的GetAuthentication包,执行下面语句
第一次握手成功,接下来被控端将主动请求建立第二次握手。发送被控端基本信息数据,包括操作系统版本、用户名,地理位置等等。我们放到后面分析
3 调试查看接收数据
在异步接收AsyncReceive()函数中设置断点
接收数据后,将返回收的字节数赋值给bytesTransferred变量
MSDN解释如下
bytesTransferred值等于68,68就是控制端发送GetAuthentication包的长度
使用bytesTransferred值定义接收数组received的大小,received用来存储接收到的数据
此时_readBuffer字节数组中已经存储了接收到的数据
将接收到的数据赋值给received数组
received数组进入_readBuffers字节数组队列
执行线程池队列中的方法AsyncReceive()
读取_readBuffers字节数组队列中的数据赋值给readBuffer数组
下面将分两步获取包数据 1)获取GetAuthentication包数据长度 2)获取GetAuthentication包数据
1)获取GetAuthentication包数据长度
因为BitConverter.ToInt32()会返回前4个字节,原因如下:
MSDN解释
此时_payloadLen变量中存储了GetAuthentication包数据的长度64
接下来改变处理状态,进入处理GetAuthentication包数据
2) 获取GetAuthentication包数据
从readBuffer数组中第5个字节开始,存储64个字节数据到_payloadBuffer变量
_payloadBuffer变量中已经存储了GetAuthentication包数据
解密_payloadBuffer后等于14字节
解压反混淆后,payload等于1字节,值是106
经过反序列化,获得GetAuthentication包
第一次握手成功:控制端和被控端的第一次握手
三、被控端发出第二次握手请求
1、发送GetAuthenticationResponse数据包
执行处理GetAuthentication包的函数HandleGetAuthentication()
生成GetAuthenticationResponse数据包,包括系统版本、国家、区域、城市、系统用户等基本信息
发送GetAuthenticationResponse数据包,调用send()函数
send()函数如下
序列化GetAuthenticationResponse包
序列化后,生成payload字节数组的长度是172字节,
部分内容,ascii解码如下
序列化后
通过上面获取的信息,推测序列化后自定义的数据包结构如下:
压缩混淆和加密后,payload字节数组长度是240字节
包长度244字节
通讯PAYLOAD包格式
发送GetAuthenticationResponse数据包
四、控制端接收被控端的第二次握手
接收244字节的包
_payloadLen存储GetAuthenticationResponse包长度值,等于240
_payloadBuffer存储GetAuthenticationResponse包数据,等于240字节
经过解密反混淆
获得172字节的序列化后的GetAuthenticationResponse包数据
反序列化后,获得原始的GetAuthenticationResponse数据包
接收数据后,触发OnClientRead()函数
GetAuthenticationResponse数据包处理函数,获取GetAuthenticationResponse数据包中的基础信息数据,并显示在控制端界面
控制端和被控端的第二次握手成功
五、控制端发出第三次握手
控制端发送SetAuthenticationSuccess数据包
经过压缩混淆和加密处理,SetAuthenticationSuccess包数据长度是68字节
发出SetAuthenticationSuccess包数据
六、被控端接收第三次握手
被控端接收到68字节收据
经过解密和反混淆,获得序列化后的SetAuthenticationSuccess包数据,值是59
反序列化后,获得 原始SetAuthenticationSuccess数据包
处理SetAuthenticationSuccess数据包
接收到 SetAuthenticationSuccess数据包后,将被控端的确认标识变量Authenticated设置为true
控制端和被控端的第三次握手成功
七 结论:
1、 类星体通讯建立的三次握手过程如图:
2 、类星体通讯包自定义格式
3 、自定义包格式
4目前网上多款开源木马都是根据 QuasarRAT改写的,比如

浙公网安备 33010602011771号