粉丝提问:设计和实现一个TCP协议半连接的端口扫描程序
0赞某学生粉丝发来问题:
这个题目一看就知道这位同学是网络安全相关专业。
很多粉丝以为彭老师知识搞驱动的,
但是其实作为一个拥有多篇网络协议专利的老鸟,
网络知识还是比较擅长的!
应用层套接字、组网、网卡驱动都有所涉猎,
目前还缺Linux内核协议栈这块没深入研究,后期会补上。
一、题目总结
题目要求是扫描所有TCP半连接的端口,需要实现的功能如下:
攻击方启动任务1,循环向指定 服务器端+端口 发送SYN数据包,(端口从0开始递增)
如果该服务器上有服务打开了这个端口,就会回复SYN+ACK,此时服务端进入SYN_RCVD状态,
攻击方启动任务2,扫描收到的所有SYN+ACK数据包,如果客户端收到SYN+ACK,那么说明服务器改端口打开,任务2就可以将所有打开的端口信息打印出来
任务1使用socket API
任务2使用pcap库
二、TCP基础知识点
解决这个问题必须掌握以下几个知识点:
什么是TCP
TCP3次握手
什么是半连接
TCP、IP协议头
如何使用Libpcap库
线程、进程
整体来说对网络知识的基本功要求还是很高的。关于TCP/IP协议栈这些基础知识点的本文就不列举了。
下面主要强化下这个题目涉及的TCP的知识点。
1.TCP
首先就是我们必须了解TCP协议头:
序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题
确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题
控制位:ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接 SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定 FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位置为 1 的 TCP 段
与本题目相关的是最主要字段是控制位,控制位的操作最主要体现在3次握手和4次握手。
2. tcp三次握手
开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态:
第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SENT 状态
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态
第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输
3. tcp四次挥手
四次挥手过程:
客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态
服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态
等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态
客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭
客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭
更详细 tcp知识点可以参考下面文章:《28 张图,一次性说清楚 TCP》
4.TCP状态
TCP协议状态迁移图如下:
CLOSED:表示初始状态
LISTEN:表示服务器端的某个SOCKET处于监听状态,可以接受连接了
SYN_RCVD:表示接收到了SYN报文
SYN_SENT:表示客户端已发送SYN报文
ESTABLISHED:表示连接已经建立了
TIME_WAIT:表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了
CLOSING:表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。如果双方几乎在同时* close一个SOCKET的话,那么就出现了双方同时发送FIN报 文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接
CLOSE_WAIT:表示在等待关闭
5. 半连接/全连接
TCP半连接及全连接状态,在服务器的性能分析中,起着重要的作用,它通常是反应服务端的处理能力
1)半连接队列(syn queue)
客户端发送SYN包,服务端收到后回复SYN+ACK后,服务端进入SYN_RCVD状态,这个时候的socket会放到半连接队列。
2)全连接队列(accept queue)
当服务端收到客户端的ACK后,socket会从半连接队列移出到全连接队列。当调用accpet函数的时候,会从全连接队列的头部返回可用socket给用户进程。
全连接队列中存放的是已完成TCP三次握手的过程,等待被处理的连接,在客户端及服务端的状态均为 ESTABLISHED
三、 抓包举例
要想学好网络,抓包工具是必须掌握的。
下图是一口君通过抓包工具抓取的一个完整的 tcp 3次握手 + HTTP GET请求 + 4次握手 的完整通信数据包。
如何抓包,可以参考下面文章:
B站也有详细的教学视频:
《教你如何抓取网络中的数据包!黑客必备技能》
https://www.bilibili.com/video/BV1xr4y1T7cT/?vd_source=07570058a62e0e8a6cf489efac35cfec
四、 socket
关于socket API内容,大家可以的参考下面这篇文章
五、libpcap
libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。
libpcap主要由两部分组成:网络分接头(network tap)和数据过滤器(packet filter)。
网络分接头从网络设备驱动程序中收集数据进行拷贝,过滤器决定是否接收该数据包。
libpcap利用BSD packet filter(BPF)算法对网卡接收到的链路层数据包进行过滤。
libpcap的包捕获机制就是在数据链路层加一个旁路处理。当一个数据包到达网络接口时,libpcap首先利用已经创建的套接字从链路层驱动程序中获得该数据包的拷贝,再通过Tap函数将数据包发给BPF过滤器。
BPF过滤器根据用户已经定义好的过滤规则对数据包进行逐一匹配,匹配成功则放入内核缓冲区,并传递给用户缓冲区,匹配失败则直接丢弃。
如果没有设置过滤规则,所有数据包都将放入内核缓冲区,并传递给用户层缓冲区。
1. libpcap安装
在线安装
2. Libpcap的抓包流程:
查找网络设备:目的是发现可用的网卡,实现的函数为pcap_lookupdev(),如果当前有多个网卡,函数就会返回一个网络设备名的指针列表。
打开网络设备:利用上一步中的返回值,可以决定使用哪个网卡,通过函数pcap_open_live()打开网卡,返回用于捕捉网络数据包的秒数字。
获得网络参数:这里是利用函数pcap_lookupnet(),可以获得指定网络设备的IP地址和子网掩码。
编译过滤策略:Lipcap的主要功能就是提供数据包的过滤,函数pcap_compile()来实现。
设置过滤器:在上一步的基础上利用pcap_setfilter()函数来设置。
利用回调函数,捕获数据包:函数pcap_loop()和pcap_dispatch()来抓去数据包,也可以利用函数pcap_next()和pcap_next_ex()来完成同样的工作。
关闭网络设备:pcap_close()函数关系设备,释放资源。
3. 数据结构说明:
4. libcap库函数
关于libcap的详细讲解,后续会出文章,
本文只讲几个重要的函数。
六、设计方案
实现原理:
atach、cap进程运行在ubuntu中,要攻击的目的终端可以使网络中任意设备,只需要能ping通即可。本例在windows上测试,采用桥接模式将ubuntu的网口和windows的网口桥接起来。
atach进程主要功能:
创建tcp套接字
设置需要攻击的终端的ip+port,然后执行connect函数
connect成功,说明对方该端口可以使用
修改port值,重复前面3个步骤
cap进程主要功能:
通过eth0,抓取指定规则:host 192.168.0.116数据包
解析出以太头、tcp头,ip头、tcp头,判断tcp头中sync+ack位为1的所有数据包
打印出步骤2过滤出来的数据包
代码流程:
七、测试
其中atach是上攻击方,用于向指定ip发送sync包 cap 用于检测所有网卡收到的sync+ack数据包 程序运行在ubuntu中。
3. 启动网络调试助手
在windows上启动网络调试助手,
建立几个Tcp Server,端口号分别为55、56、57
4. 启动程序
1)首先启动cap
八、代码
代码已经同步到gitee,地址如下:
更多嵌入式、Linux、网络知识,后台留言加一口君好友!
原文链接:https://mp.weixin.qq.com/s/ZGEUNU35S8XHeFG8ymZNCQ
电子技术应用专栏作家 一口Linux