dnscat2协议

简介

本文描述dnscat2 协议

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

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

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

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

挑战

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

  • 每个消息都需要某种类型的回复
  • 重传、丢包和乱序非常普遍
  • 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遇到错误,根据严重性会采取以下措施:

  • 可以忽略的错误(重复的SYN包),回复一个空消息;对于TXT/A/AAAA, 显然是不回复;对于CNAME/MX/NS,因为域名是必须的,所以只回复一个域名。
  • 致命错误(例如未处理的异常), 应该回复带有描述信息的FIN包。

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应该不同,这是为了缓存而设置的。

数据类型

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

  • uint8_t - an 8-bit (one-byte) value
  • uint16_t - a 16-bit (two-byte) value
  • uint32_t - a 32-bit (four-byte) value
  • uint64_t - a 64-bit (eight-byte) value
  • ntstring - a null-terminated string (that is, a series of bytes with a NUL byte ("\0") at the end
  • byte[] - an array of bytes - if no size is specified, then it's the rest of the packet

MESSAGE_TYPE_SYN [0x00]

  • (uint16_t) packet_id
  • (uint8_t) message_type [0x00]
  • (uint16_t) session_id
  • (uint16_t) initial sequence number
  • (uint16_t) options
  • If OPT_NAME is set: (ntstring) 7.
Notes
  • 每个连接的初始化,都是通过一个客户端发送SYN开始,包含一个随机的session_id和随机的初始化序列号,以及请求选项。

  • 以下选项被定义:

    • OPT_NAME - 0X01[C->S]

      • 数据包包含一个额外的区域:session名,一个自由区域,可以包含可读数据
    • OPT_COMMAND - 0X20[C->S]

      • 命令会话,表示是一个命令隧道消息
    • OPT_ENCRYPTED - 0x40 [C-<S and S->C]

      • 协商加密方式
      • crypto_flags 未定义,为0
      • 公钥x和y为大数,直接转为16进制值,左侧填充0
  • 服务器回复SYN,包含初始序列号和选项

    • 如果客户端请求中包含OPT_ENCRYPTED,服务员也必须包含
  • session_id和初始号必须随机化,使得连接劫持攻击更加困难(两个序列号和session_id每个连接给我们大约48bits的熵)

  • packet_id对每个packet来说都应该不同,完全是为了阻止缓存。可以是递增的,双方都应该忽略这个值。

  • 如果服务器收到多个完全相同的SYN,则每个都要回复。

  • 如果服务器收到了有相同session_id的不同SYN,应该忽略。

错误状态
  • 如果客户端没有收到SYN应答,意味着请求包或者应答包丢失了,客户端可以选择重传或者生成新的SYN包或会话。

  • 如果客户端在收到MSG消息之前收到第二个相同session的SYN,需要当作有效来回复。

    • 有效意味着包含相同选项、序列号、相同名称、相同密钥。
  • 如果客户端和服务器在建立连接时收到SYN,应该丢弃。

MESSAGE_TYPE_MSG: [0x01]

  • (uint16_t) packet_id
  • (uint8_t) message_type [0x01]
  • (uint16_t) session_id
  • (uint16_t) seq
  • (uint16_t) ack
  • (byte[]) data
Notes

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

MESSAGE_TYPE_FIN: [0x02]

  • (uint16_t) packet_id
  • (uint8_t) message_type [0x02]
  • (uint16_t) session_id
  • (ntstring) reason
Notes

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

MESSAGE_TYPE_ENC: [0x03]

  • (uint16_t) packet_id
  • (uint8_t) message_type [0x03]
  • (uint16_t) session_id
  • (uint16_t) subtype
  • (uint16_t) flags
  • If subtype is ENC_SUBTYPE_INIT:
    • (byte[32]) public_key_x
    • (byte[32]) public_key_y
  • If subtype is ENC_SUBTYPE_AUTH:
    • (byte[32]) authenticator
Notes
  • 如果要使用加密连接,客户端应该立即发送ENC|INIT数据包
  • 服务器必须用包含相同子类型的ENC包回复ENC请求
    • 如果客户端选择加密,服务器也必须加密;客户端选择认证,服务器也必须认证。
  • 服务器必须用相同的密钥回复ENC|INIT包
  • 公钥和认证器编码为32字节的16进制字符串,左侧填充0

下面的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]

  • (uint16_t) packet_id
  • (uint8_t) message_type [0xFF]
  • (uint16_t) ping_id
  • (ntstring) data

Notes

ping_id应该和请求一致

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    JasonShi6306421阅读 1,231评论 0 1
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,946评论 1 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 1.这篇文章不是本人原创的,只是个人为了对这部分知识做一个整理和系统的输出而编辑成的,在此郑重地向本文所引用文章的...
    SOMCENT阅读 13,049评论 6 174
  • 我讨厌这笑里藏刀的人们 却不得不先给自己戴上笑面虎的面具 我讨厌这阴险狡诈的社会 却不得不先给自己套上安全的保护障...
    随匿阅读 211评论 0 2