Skip to content

Commit aa10800

Browse files
authored
新增 tcpkill 工具的实现原理。
1 parent 2f0ba6e commit aa10800

1 file changed

Lines changed: 105 additions & 15 deletions

File tree

‎network/3_tcp/challenge_ack.md‎

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
![](https://img-blog.csdnimg.cn/ea1c6e0165f04232ab02046132e63d0f.jpg)
88

99

10-
大概意思是,一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 establish 状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?
10+
大概意思是,一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 Established 状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?
1111

1212
看过我的图解网络的读者都知道,TCP 连接是由「四元组」唯一确认的。
1313

@@ -38,10 +38,9 @@
3838

3939
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/est_syn.png)
4040

41+
**处于 Established 状态的服务端,如果收到了客户端的 SYN 报文(注意此时的 SYN 报文其实是乱序的,因为 SYN 报文的初始化序列号其实是一个随机数),会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK。**
4142

42-
**处于 Established 状态的服务端如果收到了客户端的 SYN 报文(注意此时的 SYN 报文其实是乱序的,因为 SYN 报文的初始化序列号其实是一个随机数),会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK。**
43-
44-
**接着,客户端收到这个 Challenge ACK,发现序列号并不是自己期望收到的,于是就会回 RST 报文,服务端收到后,就会释放掉该连接。**
43+
**接着,客户端收到这个 Challenge ACK,发现确认号(ack num)并不是自己期望收到的,于是就会回 RST 报文,服务端收到后,就会释放掉该连接。**
4544

4645
## RFC 文档解释
4746

@@ -52,23 +51,24 @@ RFC 793 文档里的第 34 页里,有说到这个例子。
5251
原文的解释我也贴出来给大家看看。
5352

5453
- When the SYN arrives at line 3, TCP B, being in a synchronized state,
55-
and the incoming segment outside the window, responds with an acknowledgment indicating what sequence it next expects to hear (ACK
54+
and the incoming segment outside the window, responds with an
55+
acknowledgment indicating what sequence it next expects to hear (ACK
5656
100).
5757
- TCP A sees that this segment does not acknowledge anything it
5858
sent and, being unsynchronized, sends a reset (RST) because it has
5959
detected a half-open connection.
6060
- TCP B aborts at line 5.
61-
- TCP A willcontinue to try to establish the connection;
61+
- TCP A willcontinue to try to Established the connection;
6262

6363
我就不瞎翻译了,意思和我在前面用中文说的解释差不多。
6464

6565
## 源码分析
66-
处于 establish 状态的服务端如果收到了客户端的 SYN 报文时,内核会调用这些函数:
66+
处于 Established 状态的服务端如果收到了客户端的 SYN 报文时,内核会调用这些函数:
6767

6868
```csharp
6969
tcp_v4_rcv
7070
-> tcp_v4_do_rcv
71-
-> tcp_rcv_established
71+
-> tcp_rcv_Establisheded
7272
-> tcp_validate_incoming
7373
-> tcp_send_ack
7474
```
@@ -78,7 +78,7 @@ tcp_v4_rcv
7878

7979
![](https://img-blog.csdnimg.cn/780bc02c8fa940c0a320a5916b216c21.png)
8080

81-
从上面的代码实现可以看到,处于 establish 状态的服务端,在收到报文后,首先会判断序列号是否在窗口内,如果不在,则看看 RST 标记有没有被设置,如果有就会丢掉。然后如果没有 RST 标志,就会判断是否有 SYN 标记,如果有 SYN 标记就会跳转到 syn_challenge 标签,然后执行 tcp_send_challenge_ack 函数。
81+
从上面的代码实现可以看到,处于 Established 状态的服务端,在收到报文后,首先会判断序列号是否在窗口内,如果不在,则看看 RST 标记有没有被设置,如果有就会丢掉。然后如果没有 RST 标志,就会判断是否有 SYN 标记,如果有 SYN 标记就会跳转到 syn_challenge 标签,然后执行 tcp_send_challenge_ack 函数。
8282

8383
tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个携带了正确序列号和确认号的 ACK 报文。
8484

@@ -98,27 +98,33 @@ tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个
9898

9999
这个思路很好,但是不要忘了还有个序列号的问题,你伪造的 RST 报文的序列号一定能被对方接受吗?
100100

101-
如果 RST 报文的序列号不能落在对方的滑动窗口内,这个 RST 报文会被对方丢弃的,就达不到关闭的连接的效果。
101+
如果 RST 报文的序列号不是对方期望收到的序列号,这个 RST 报文会被对方丢弃的,就达不到关闭的连接的效果。
102+
103+
举个例子,下面这个场景,客户端发送了一个长度为 100 的 TCP 数据报文,服务端收到后响应了 ACK 报文,表示收到了这个 TCP 数据报文。**服务端响应的这个 ACK 报文中的确认号(ack = x + 100)就是表明服务端下一次期望收到的序列号是 x + 100**。
104+
105+
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/rst合法.png)
102106

103-
所以,**要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足「四元组相同」和「序列号正好落在对方的滑动窗口内」这两个条件。**
107+
所以,**要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足「四元组相同」和「序列号是对方期望的」这两个条件。**
104108

105-
直接伪造符合预期的序列号是比较困难,因为如果一个正在传输数据的 TCP 连接,滑动窗口时刻都在变化,因此很难刚好伪造一个刚好落在对方滑动窗口内的序列号的 RST 报文。
109+
直接伪造符合预期的序列号是比较困难,因为如果一个正在传输数据的 TCP 连接,序列号都是时刻都在变化,因此很难刚好伪造一个正确序列号的 RST 报文。
110+
111+
### killcx 的工具
106112

107113
办法还是有的,**我们可以伪造一个四元组相同的 SYN 报文,来拿到“合法”的序列号!**
108114

109-
正如我们最开始学到的,如果处于 establish 状态的服务端,收到四元组相同的 SYN 报文后,**会回复一个 Challenge ACK,这个 ACK 报文里的「确认号」,正好是服务端下一次想要接收的序列号,说白了,就是可以通过这一步拿到服务端下一次预期接收的序列号。**
115+
正如我们最开始学到的,如果处于 Established 状态的服务端,收到四元组相同的 SYN 报文后,**会回复一个 Challenge ACK,这个 ACK 报文里的「确认号」,正好是服务端下一次想要接收的序列号,说白了,就是可以通过这一步拿到服务端下一次预期接收的序列号。**
110116

111117
**然后用这个确认号作为 RST 报文的序列号,发送给服务端,此时服务端会认为这个 RST 报文里的序列号是合法的,于是就会释放连接!**
112118

113119
在 Linux 上有个叫 killcx 的工具,就是基于上面这样的方式实现的,它会主动发送 SYN 包获取 SEQ/ACK 号,然后利用 SEQ/ACK 号伪造两个 RST 报文分别发给客户端和服务端,这样双方的 TCP 连接都会被释放,这种方式活跃和非活跃的 TCP 连接都可以杀掉。
114120

115121

116-
使用方式也很简单,只需指明客户端的 IP 和端口号。
122+
killcx 的工具使用方式也很简单,如果在服务端执行 killcx 工具,只需指明客户端的 IP 和端口号,如果在客户端执行 killcx 工具,则就指明服务端的 IP 和端口号。
117123

118124
```csharp
119125
./killcx <IP地址>:<端口号>
120126
```
121-
killcx 工具的工作原理,如下图。
127+
killcx 工具的工作原理,如下图,下图是在客户端执行 killcx 工具。
122128

123129
![](https://img-blog.csdnimg.cn/95592346a9a747819cd27741a660213c.png)
124130

@@ -136,6 +142,90 @@ killcx 工具的工作原理,如下图。
136142

137143
怎么样,很巧妙吧!
138144

145+
### tcpkill 的工具
146+
147+
除了 killcx 工具能关闭 TCP 连接,还有 tcpkill 工具也可以做到。
148+
149+
这两个工具都是通过伪造 RST 报文来关闭指定的 TCP 连接,但是它们拿到正确的序列号的实现方式是不同的。
150+
151+
- tcpkill 工具是在双方进行 TCP 通信时,拿到对方下一次期望收到的序列号,然后将序列号填充到伪造的 RST 报文,并将其发送给对方,达到关闭 TCP 连接的效果。
152+
- killcx 工具是主动发送一个 SYN 报文,对方收到后会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK,这时就可以拿到对方下一次期望收到的序列号,然后将序列号填充到伪造的 RST 报文,并将其发送给对方,达到关闭 TCP 连接的效果。
153+
154+
可以看到, 这两个工具在获取对方下一次期望收到的序列号的方式是不同的。
155+
156+
tcpkill 工具属于被动获取,就是在双方进行 TCP 通信的时候,才能获取到正确的序列号,很显然**这种方式无法关闭非活跃的 TCP 连接**,只能用于关闭活跃的 TCP 连接。因为如果这条 TCP 连接一直没有任何数据传输,则就永远获取不到正确的序列号。
157+
158+
killcx 工具则是属于主动获取,它是主动发送一个 SYN 报文,通过对方回复的 Challenge ACK 来获取正确的序列号,所以这种方式**无论 TCP 连接是否活跃,都可以关闭**。
159+
160+
接下来,我就用这 tcpkill 工具来做个实验。
161+
162+
在这里, 我用 nc 工具来模拟一个 TCP 服务端,监听 8888 端口。
163+
164+
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill1.png)
165+
166+
接着,在客户端机子上,用 nc 工具模拟一个 TCP 客户端,连接我们刚才启动的服务端,并且指定了客户端的端口为 11111。
167+
168+
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill2.png)
169+
170+
这时候, 服务端就可以看到这条 TCP 连接了。
171+
172+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill3.png)
173+
174+
注意,我这台服务端的公网 IP 地址是 121.43.173.240,私网 IP 地址是 172.19.11.21,在服务端通过 netstat 命令查看 TCP 连接的时候,则会将服务端的地址显示成私网 IP 地址 。至此,我们前期工作就做好了。
175+
176+
接下来,我们在服务端执行 tcpkill 工具,来关闭这条 TCP 连接,看看会发生什么?
177+
178+
在这里,我指定了要关闭的客户端 IP 为 114.132.166.90 和端口为 11111 的 TCP 连接。
179+
180+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill4.png)
181+
182+
可以看到,tcpkill 工具阻塞中,没有任何输出,而且此时的 TCP 连接还是存在的,并没有被干掉。
183+
184+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill5.png)
185+
186+
为什么 TCP 连接没用被干掉?
187+
188+
因为在执行 tcpkill 工具后,这条 TCP 连接并没有传输任何数据,而 tcpkill 工具是需要拦截双方的 TCP 通信,才能获取到正确的序列号,从而才能伪装出正确的序列号的 RST 报文。
189+
190+
所以,从这里也说明了,**tcpkill 工具不适合关闭非活跃的 TCP 连接**。
191+
192+
接下来,我们尝试在客户端发送一个数据。
193+
194+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill8.png)
195+
196+
可以看到,在发送了「hi」数据后,客户端就断开了,并且错误提示连接被对方关闭了。
197+
198+
此时,服务端已经查看不到刚才那条 TCP 连接了。
199+
200+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill7.png)
201+
202+
然后,我们在服务端看看 tcpkill 工具输出的信息。
203+
204+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill8.png)
205+
206+
可以看到, **tcpkill 工具给服务端和客户端都发送了伪造的 RST 报文,从而达到关闭一条 TCP 连接的效果**。
207+
208+
到这里我们知道了, 运行 tcpkill 工具后,只有目标连接有新 TCP 包发送/接收的时候,才能关闭一条 TCP 连接。因此,**tcpkill 只适合关闭活跃的 TCP 连接,不适合用来关闭非活跃的 TCP 连接**。
209+
210+
上面的实验过程,我也抓了数据包,流程如下:
211+
212+
![图片](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill9.png)
213+
214+
最后一个 RST 报文就是 tcpkill 工具伪造的 RST 报文。
215+
216+
## 总结
217+
218+
要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足「四元组相同」和「序列号是对方期望的」这两个条件。
219+
220+
今天给大家介绍了两种关闭 TCP 连接的工具:tcpkill 和 killcx 工具。
221+
222+
这两种工具都是通过伪造 RST 报文来关闭 TCP 连接的,但是它们获取「对方下一次期望收到的序列号的方式是不同的,也正因此,造就了这两个工具的应用场景有区别。
223+
224+
- tcpkill 工具只能用来关闭活跃的 TCP 连接,无法关闭非活跃的 TCP 连接,因为 tcpkill 工具是等双方进行 TCP 通信后,才去获取正确的序列号,如果这条 TCP 连接一直没有任何数据传输,则就永远获取不到正确的序列号。
225+
- killcx 工具可以用来关闭活跃和非活跃的 TCP 连接,因为 killcx 工具是主动发送 SYN 报文,这时对方就会回复 Challenge ACK ,然后 killcx 工具就能从这个 ACK 获取到正确的序列号。
226+
227+
完!
228+
139229
---
140230

141231
最新的图解文章都在公众号首发,别忘记关注哦!!如果你想加入百人技术交流群,扫码下方二维码回复「加群」。

0 commit comments

Comments
 (0)