dnscat2协议

2018-08-12  本文已影响0人  jinx1663

简介

本文描述dnscat2 协议

我称之为dnscat2协议,尽管严格来讲,它不仅仅局限于dnscat或DNS。我需要一种逻辑连接协议,它可以在不确保可靠性且带宽被极度限制的多种底层连接、数据报之上建立。

本协议是为dnscat设计的,采用轮询机制——客户端发送数据包,服务器作出回应,服务器无法知道是哪个客户端发送的数据包,也无法知道如何初始化一个连接,因此这些问题在dnscat2协议中都要考虑。

本协议基于数据报,包含一个16bits的session_id, 可用于追踪多种底层链路之上的连接,并处理底层的丢包、重复和乱序问题。

以下我会给出一些细节——我们需要什么来完成这些工作、连接如何工作的描述、消息中使用的常量和消息本身的结构

挑战

借助DNS协议很有挑战性!下面列举了一些问题:

DNS传输协议和dnscat协议都将这些考虑在内,不像其它DNS隧道协议,我不依赖于使用TCP传输层来保证可靠性——dnscat协议有能力在DNS协议之上建立原始连接。

DNS运送协议

dnscat协议是一个独立的协议,可以被应用在任何轮询协议之上,例如DNS、HTTP、ICMP/Ping等——我将这些协议都看作运送协议。在这些不同的协议之上需要将数据稍稍包装一下。本节主要介绍如何将DNS协议作为一个运送通道。

编码

所有的数据被编码成字符串的16进制表示,例如: "AAA" 变为"414141"

域名中的所有点号都应被忽略,因此,"41.4141"、"414.141"和"414141"是完全一样的。

除此之外,不区分大小写,因此"5b"和"5B"也是相同的。客户端和服务器都要处理大小写的不敏感性,因为有些软件会改变请求的大小写!

发送/接收

客户端可以选择是否扩展域名(用户必须有此域名的权威DNS服务器)或者加一个"dnscat."前缀,消息格式如下:

<encoded data>.<domain>
or

<tag>.<encoded data>

任何不符合上述形式的数据,或者是不支持的记录类型,或者dnscat服务器无法识别的扩展域名,服务器可以丢弃或者转发至上层DNS服务器。

dnscat2服务器必须用正确格式的DNS应答报文回复,置位无错误位,并包含一个或多个answer。如果有多个answer,每个answer的每一个字节必须是一字节的序列号(中间DNS服务器很可能会改变记录的顺序)

应答记录类型必须和请求记录类型一致,应答的具体的编码方式取决于记录类型。

DNS记录类型

dnscat服务器支持绝大多数DNS消息类型:TXT, MX, CNAME, A和AAAA,这些类型的请求消息都被封装为DNS记录,不同类型的应答报文格式稍有不同。

TXT应答报文就是16进制编码的数据。理论上,TXT记录可以包含二进制数据,但是Windows DNS客户端会NULL字符截断,因此需要编码。

CNAME和MX记录在请求时是相同编码的:都有一个tag前缀或者域名后缀。这是必要的,因为中间DNS服务器不会转发不正确的域名。MX记录类型在DNS中也有额外的区域——优先级区域,可以随机设置,客户端应该忽略。

最后,A记录和AAAA记录,有点像TXT,就是原始数据,没有前缀后缀。但有两点补充:一是应答报文长度很短(A记录4B,AAAA记录16B),因此需要多个answer,不幸的是,DNS层次体系会
重新排列answer, 因此每个记录必须包含一个字节的序号作为前缀,数值并不重要,只要可以排列得到原始的顺序即可。

二是没有明确的方法可以获取应答报文的长度,因为应答报文实际上是分块的。因此,数据本身的长度也要用一个字节来存储,放在消息头部。如果消息结尾不够一个块的大小,需要填充。

A记录应答报文格式:

0.9.<byte1>.<byte2> 1.<byte3><byte4><byte5> 2.<byte6><byte7><byte8> 3.<byte9>.<pad>.<pad>

0表示块序号, 9表示数据长度, 2和3表示块序号

Errors

如果server遇到错误,根据严重性会采取以下措施:

dnscat协议

在上面我定义了一种DNS运送协议,这个协议解决了如何通过DNS报文传送数据。下面我正式介绍dnscat协议。

dnscat 协议

连接

这里的"连接"指的是客户端和服务器之间的逻辑会话。一个连接以SYN包开始,之后包含若干个MSG包,最后以FIN包结束。每个连接用一个唯一的16bits的session_id标识,注意不要将SYN/FIN和tcp连接中的概念混淆,这里指的完全是dnscat中的概念。

总结一下:客户端发送SYN包给server, server回复SYN包,这样一个连接就建立了。客户端发送MSG包给server, server回复MSG包。当客户端决定终止连接,客户端发送FIN,server回复之;当server决定终止连接,它使用FIN包回复来自客户端的MSG包,客户端不再回复。

SYN包中的flags区域在client和server之间交换,这些flag影响整个会话过程。

大部分情况下,意外的数据包会被忽略.

client和server都允许处理多个会话,client经常和server同时展开多个会话;server则可以和不同的client同时展开会话。

一个完整的连接过程如图:

+----------------+
| Client  Server |
+----------------+
|  SYN -->  |    |
|   |       v    |
|   |  <-- SYN   |
|   v       |    |
|  MSG -->  |    |
|   |       v    |
|   |  <-- MSG   |
|   v       |    |
|  MSG -->  |    |
|   |       v    |
|   |  <-- MSG   |
|  ...     ...   |
|  ...     ...   |
|  ...     ...   |
|   |       |    |
|   v       |    |
|  FIN -->  |    |
|           v    |
|      <-- FIN   |
+----------------+

server决定终止连接:

+----------------+
| Client  Server |
+----------------+
|  SYN -->  |    |
|   |       v    |
|   |  <-- SYN   |
|   v       |    |
|  MSG -->  |    |
|   |       v    |
|   |  <-- MSG   |
|   v       |    |
|  MSG -->  |    |
|   |       v    |
|   |  <-- FIN   |
|   v            |
| (nil)          |
+----------------+

收到意外的MSG,server回复FIN:

+----------------+
| Client  Server |
+----------------+
|  MSG -->  |    |
|   |       v    |
|   |  <-- FIN   |
|   v            |
| (nil)          |
+----------------+

server收到意外的FIN,忽略之。

+----------------+
| Client  Server |
+----------------+
|  FIN -->  |    |
|           v    |
|         (nil)  |
+----------------+

SEQ/ACK号

SEQ(sequence 序列)和ACK(acknowledgement确认)号和TCP中的概念十分相似。在连接初始阶段,client和server都选择一个随机的ISN(initial sequence number初始序列号),并发送给对方。

client的SEQ号就是server的ACK号,反之亦然。这样双方都知道下一个应收到的序号是多少。

在一个会话过程中,双方会相互发送数据,当更多的数据排队等待发送,想像你正在将这些数据移动到已发送数据列表中,当消息发送出去之后,系统应当注意自己的序列号和字符队列来决定应该发送什么。如果有还未被对方确认的数据在等待,这些数据应该被重传,直到和当前的序列号匹配。

当收到消息后,接收方必须将消息中的序列号和自己的确认号进行对比,如果序列号比确认号小,说明收到的是重复数据,ACK可能丢失了,没有被对方收到,必须重新发送ACK。如果序列号比确认号大,数据应该被缓存或是悄悄丢弃(当对方发送多个数据包进行测速时),如果相等,则进一步处理。

当消息进一步处理时,接收方根据收到的字节数增加ACK号,并将新的ACK、SEQ和等待的数据发送出去。

当发送方收到对方的ACK,会增加自己的SEQ,从新的SEQ处开发发送数据。

你要知道双方都会持续确认对方发送的数据(通过增加对方的SEQ号),同时发送自己的数据并更新自己的SEQ(通过对方的ACK)

命令协议

在dnscat协议之上有一个称为命令协议的协议。如果在SYN头部设置了OPT_COMMAND位,所有的消息都会被当作命令消息,必须遵循命令协议。

关于命令协议的详细信息请查看command_protocol.md.

加密/签名

待完成

常量

/* Message types */
#define MESSAGE_TYPE_SYN    (0x00)
#define MESSAGE_TYPE_MSG    (0x01)
#define MESSAGE_TYPE_FIN    (0x02)
#define MESSAGE_TYPE_ENC    (0x03)
#define MESSAGE_TYPE_PING   (0xFF)

/* Encryption subtypes */
#define ENC_SUBTYPE_INIT    (0x00)
#define ENC_SUBTYPE_AUTH    (0x01)

/* Options */
#define OPT_NAME            (0x01)
#define OPT_COMMAND         (0x20)

Messages

本节解释了如何为消息类型编码,所有的区域采用大端编码方式,整个数据包通过DNS运送协议传送。运送协议负责处理数据包大小,数据包大小是已知的。

所有的消息包含一个16位的packet_id, 每个packet_id应该不同,这是为了缓存而设置的。

数据类型

正如上面提到的,所有的区域大端编码(网络字节序)。以下数据类型被用到:

MESSAGE_TYPE_SYN [0x00]

Notes
错误状态

MESSAGE_TYPE_MSG: [0x01]

Notes

如果SYN包含OPT_COMMAND,数据区域使用命令协议

MESSAGE_TYPE_FIN: [0x02]

Notes

一旦FIN发送,client or server就不太回应任何消息。

MESSAGE_TYPE_ENC: [0x03]

Notes

下面的Ruby代码用于转换整数到字符串:

[bn.to_s(16).rjust(32*2, "\0")].pack("H*")

反之:

[bn.to_s(16).rjust(32*2, "\0")].pack("H*")

MESSAGE_TYPE_PING: [0xFF]

Notes

ping_id应该和请求一致

上一篇下一篇

猜你喜欢

热点阅读