背景介绍

我们遇到过各种各样的 Shell,从协议上来看,最开始基于 TCP、UDP 的 Shell,到后来基于ICMP 的 Shell 。从依托工具上看,有 nc 反弹、telnet 反弹、SSH 端口转发等手段,极度猥琐的甚至还有利用 awk 的反弹 Shell。从语言上看,各种流行的语言都能用来写后门,从bash 到 3P(Perl Python PHP)再到 Ruby 和 Java ,大牛总是可以根据不同的环境情况选择不同的 Shell 来利用。

各种 Shell 都有它自己的优点和缺点,采用 TCP 和 UDP 的虽然功能强大,但是却受到了防火墙和杀毒软件的严格监控,Ruby 和 Java 写成的又不一定有相应的运行环境。

主角登场

我们今天介绍一个利用 DNS 协议进行通信的反弹 Shell,和 ICMP 反弹 Shell 的原理几乎相同,只是传输的协议变为了 DNS。

使用 DNS 请求来伪装通信进行命令控制带来的好处不言而喻,不管你做了多么严格的网络控制,你也要满足至少对一个服务器发起的 DNS 查询请求,那么就可以被攻击者恶意利用。利用 DNS 的想法并不新奇,在黑帽大会上也多次提到过 DNS 隧道,之前的 DNS 域传送漏洞也是一个利用方法,今天我们选取一个开源的工具进行学习–DNShell 。

有关于 DNS 的相关细节不再赘述,有想了解详细内容的移步相关的 RFC 文档,如 RFC 4034、RFC 3755等等。

准备工作

如作者所说,这是一个使用 Python 编写的、利用 DNS 作为命令控制信道的反弹 Shell。对于 Python 库的准备就不再多说了,依赖的环境应该是 Python 2.7,因为其在服务器端的代码中使用了 raw_input 和 .format() 。众所周知, raw_input 函数在 Python3 中被砍掉了,而 str.format() 则是在 Python 2.6 才加入的函数。

屏幕快照 2016-08-01 下午10.52.31.png

上图是我的 Package 页

from Crypto.Cipher import AES 引入错误,如果在装了 Crypto 后还是错误,就需要装pycrypto 这个库。

如果想修改代码到 Python3 下运行,遇到 import dns.resolver 引入错误,是需要装dnspython3 的。

DNS 查询

简单介绍一下如何利用 Python 来进行 DNS 查询,这也是核心的方法,DNS 作为信道进行隐蔽通信的核心就是把要传递的数据作为 DNS 请求的 hostname 部分。

import dns.resolver

myResolver = dns.resolver.Resolver()

myAnswers = myResolver.query(“google.com”, “A”)

for data in myAnswers:

print data

先创建一个实例,然后查询 Google.com 的 A 记录。以同样的方式,我们可以执行对 MX 和NS 的查询,只需要改动 A 的那部分就可以了,很简单。

当用它来进行反向 DNS 查找(主机名到 IP) 时,就不是简单的输入 IP 地址直接执行 A 记录查找。我们需要执行 PTR 查找,查找时要将待查找的 IP 地址逆向书写,并将 “.in addr.arpa”追加到它后面。

例如,为解析 IP 地址为 114.124.134.3 的主机名,我们使用的代码是:

myAnswers = myResolver.query("3.134.124.114.in-addr.arpa", "PTR")

DNS 解析程序也给我们指定我们自己的域名服务器的选项。这可以通过使用:

1. myResolver = dns.resolver.Resolver() 2. myResolver.nameservers = ['8.8.8.8', '8.8.4.4']

这里也引出了一种对抗的方法,我们将在后面进行说明。

分析与问题

屏幕快照 2016-08-01 下午10.58.12.png

上图将 DNShell 的一次命令控制过程进行了展示,由于两个文件代码量并不大,我们不进行逐段的详细解释和分析了,只对一些地方进行简要阐述,想彻底弄懂或者改写代码的请自行研究。

先运行服务器端,再执行被控制端,这一点在 GitHub 的项目主页上作者也有提醒。

Python 有两个内建的模块用于处理命令行参数,一个是 getopt 另一个是 optparse ,作者在这个代码中使用的是 optparse 模块用来解析命令行参数。

监听的端口是常见的 DNS 服务器端口 53,如果你的服务器恰好搭建了 DNS 服务,或者有程序占用这个端口,你就无法对这个端口进行监听了,必须先停止占用端口的程序。

程序使用了 base64 进行编码解码,使用 AES 进行加密解密,在程序两端都要更改密钥和向量来保证加密的安全性。

NXT 资源记录通过在域中创建所有字面上的所有者名称链,指出某个名称在域中不存在。它们同时也指出,一个已有名称当前有什么资源记录类型。

在加解密的时候,因为 AES 是分块加密的,在加解密时作者使用 lambda 表达式这种匿名函数来实现,十分简洁。

执行命令用的传统 subprocess 子进程的方法,如果要改进代码的话,这里还有提升空间可以解决很多问题,比如执行命令时的权限问题、 Windows 和 Linux 的命令不尽相同的问题等。

如果被控制端需要放在 Windows 上运行,不仅要考虑到其没有 Python 运行环境是否打包成exe 文件才能运行的问题、是否触发 UAC 的验证引起用户警觉,还有应该使用加壳、加密等手段来绕过像管家、360 等杀软的问题。

Nullege 是一个查询源代码和文档的好地方,和谷歌配合使用疗效显著。如果你对其中的某些函数感到陌生或者困惑,不但可以查官方文档,也可以在这里查找很多示例的源代码增进理解。

实际部署

屏幕快照 2016-08-01 下午11.00.28.png

服务器端放在了一台 VPS 上,如上图所示。

屏幕快照 2016-08-01 下午11.01.41.png

在被控制端脚本执行后,服务器端会出现一个 SHELL 的提示行,我们在这里输入命令

ipconfig -all 

屏幕快照 2016-08-01 下午11.02.20.png

在被控制端,我们可以清楚的看到,经过解码和解密,已经成功得到了 ipconfig -all 这个命令,(需要注意的是,我对代码进行了些微的改动,可能导致行号和作者版本的行号不相同,不过这并不影响什么)

在调试器中查看执行命令的 stdout 可以看到

屏幕快照 2016-08-01 下午11.03.09.png

即远程的命令可以成功执行。 

屏幕快照 2016-08-01 下午11.03.44.png

我们再输入退出的指令 quit 来测试一下

屏幕快照 2016-08-01 下午11.05.02.png

被控制端直接退出了,也完成了远程的命令。接着我们用 Wireshark 进行抓包

屏幕快照 2016-08-01 下午11.06.35.png

可以看到这条发送出去的 DNS 请求

屏幕快照 2016-08-01 下午11.07.42.png

返回的响应中,我们也确实看到了携带的数据

对抗方法

在 DNS 查询小节中,我们讲到了在查询时指定域名服务器的方法。这也是对抗使用 DNS 请求作为隐蔽通信信道的方法,在可能的情况下,使用自己搭建的 DNS 服务器,这样就可以直接得出:除了这一台 DNS 服务器要与外界进行 DNS 请求交互,其余服务器任何试图与外界DNS 服务器发起的请求都是恶意的。 

*本文原创作者:ArkTeam 楚子航