我们遇到过各种各样的 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 才加入的函数。
上图是我的 Package 页
from Crypto.Cipher import AES 引入错误,如果在装了 Crypto 后还是错误,就需要装 pycrypto 这个库。如果想修改代码到 Python3 下运行,遇到 import dns.resolver 引入错误,是需要装 dnspython3 的。
简单介绍一下如何利用 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']
这里也引出了一种对抗的方法,我们将在后面进行说明。
上图将 DNShell 的一次命令控制过程进行了展示,由于两个文件代码量并不大,我们不进行逐段的详细解释和分析了,只对一些地方进行简要阐述,想彻底弄懂或者改写代码的请自行研究。
先运行服务器端,再执行被控制端,这一点在 GitHub 的项目主页上作者也有提醒。
Python 有两个内建的模块用于处理命令行参数,一个是 getopt 另一个是 optparse ,作者在这个代码中使用的是 optparse 模块用来解析命令行参数。
监听的端口是常见的 DNS 服务器端口 53,如果你的服务器恰好搭建了 DNS 服务,或者有程序占用这个端口,你就无法对这个端口进行监听了,必须先停止占用端口的程序。
程序使用了 base64 进行编码解码,使用 AES 进行加密解密,在程序两端都要更改密钥和向量来保证加密的安全性。
NXT 资源记录通过在域中创建所有字面上的所有者名称链,指出某个名称在域中不存在。它们同时也指出,一个已有名称当前有什么资源记录类型。
在加解密的时候,因为 AES 是分块加密的,在加解密时作者使用 lambda 表达式这种匿名函数来实现,十分简洁。
执行命令用的传统 subprocess 子进程的方法,如果要改进代码的话,这里还有提升空间可以解决很多问题,比如执行命令时的权限问题、 Windows 和 Linux 的命令不尽相同的问题等。
如果被控制端需要放在 Windows 上运行,不仅要考虑到其没有 Python 运行环境是否打包成exe 文件才能运行的问题、是否触发 UAC 的验证引起用户警觉,还有应该使用加壳、加密等手段来绕过像管家、360 等杀软的问题。
Nullege 是一个查询源代码和文档的好地方,和谷歌配合使用疗效显著。如果你对其中的某些函数感到陌生或者困惑,不但可以查官方文档,也可以在这里查找很多示例的源代码增进理解。
服务器端放在了一台 VPS 上,如上图所示。
在被控制端脚本执行后,服务器端会出现一个 SHELL 的提示行,我们在这里输入命令
ipconfig -all
这个命令,(需要注意的是,我对代码进行了些微的改动,可能导致行号和作者版本的行号不相同,不过这并不影响什么)
在调试器中查看执行命令的
stdout 可以看到
即远程的命令可以成功执行。
我们再输入退出的指令 quit 来测试一下
被控制端直接退出了,也完成了远程的命令。接着我们用 Wireshark 进行抓包
可以看到这条发送出去的 DNS 请求
返回的响应中,我们也确实看到了携带的数据
在 DNS 查询小节中,我们讲到了在查询时指定域名服务器的方法。这也是对抗使用 DNS 请求作为隐蔽通信信道的方法,在可能的情况下,使用自己搭建的 DNS 服务器,这样就可以直接得出:除了这一台 DNS 服务器要与外界进行 DNS 请求交互,其余服务器任何试图与外界DNS 服务器发起的请求都是恶意的。
*本文原创作者:ArkTeam 楚子航,本文属FreeBuf原创奖励计划,未经许可禁止转载