从scapy和wireshark学计算机网络

众所周知,计网被评为最困的计算机专业课,俗称计算机中的语文。👴看了《计算机网络-自顶向下方法》(后文简称CNTDA)之后,觉得翻译就像汤姆叔叔的烂苹果派一样糟糕,上帝啊,我发誓会狠狠踢他的屁股。建议带🔥去看英文原版。

但是👴最近接触的许多实验还是很好玩的,于是本文面向对计算机网络完全0基础的同学,试图通过全程动手实操学习计网。


主要工具:

  • wireshark是坠nb的网络封包分析软件。就是用来抓包的。

下载:https://www.wireshark.org/download.html 教程:https://www.javatpoint.com/wireshark

  • scapy库是python的网络编程库,可以让你细致入微的操纵网络流量。就是用来发包的。
    • //不要和爬虫库scrapy混淆

scapy文档:https://scapy.readthedocs.io/en/latest/ 中文版:https://www.osgeo.cn/scapy/introduction.html //有些翻译错误

最后有一个敲代码顺手的python环境和一个能正常监听的wireshark就可以了。


计网基本概念

💣包(package)

等等,啥是“抓包”?啥是“发包”?我只知道A大下包。

当然,下包不仅仅是一个 CSGO 术语,在计算机网络中,包(package)有多个近义词,包括:报文/数据报(Datagram),分组/封包(Packet)……根据语境不同而区分,但大致指的是同一件事情:即网络中真正流动着的东西,我们希望网络来传递的东西。只不过“包”是最通俗的叫法,那么抓包和发包就不难理解了。

你还想问,包到底长什么样?众所周知,快递由包装和里面的东西组成,其实网络上的封包也差不多,也大致都有两部分:

  • 包头,学名首部(Header)——快递包装上的标签,写着目的地址,联系电话,快递号等信息
  • 包体,学名载荷(Payload)——快递要运输的货物本身。某些语境下也喜欢称为报文。

当然,网络封包归根结底还是线性的比特序列,于是我们需要包头来识别这个封包的相关信息,就像看快递先看标签一样。

另外,一个协议的封包也可以成为另一个协议的载荷,后面你会看到诸如pkt.payload.payload.payload.payload的套娃用法,要理解这种套娃,还需要知道分层思想。

🍰分层(layering)

CNTDA 中用邮政系统类比计算机网络,这是最常用的例子,这里我们用快递物流网来举例。随便打开你的网购记录,你会发现快递物流大概经过以下过程:

  • 客户发货:把货物地址交给快递点
    • 快递网点揽件:包装货物而变成包裹;包裹被送往最近的中转中心
      • 中转中心运输:根据包裹目的地不同,分拣装车运输给不同的中转中心;若收到本片区的包裹,卸车分拣给不同的网点
    • 快递网点派送:按包裹的地址,快递员送货上门
  • 客户取件:拆箱,拿到货物,确认无误签收

你知道发一个快递要经历怎样的困难吗?你不知道,你只关心你自己。这里的重点是,客户不需要关心中转中心如何指挥重型货车或飞机,网点也只需要关心如何包装好客户的货物。快递网络明显的呈现出三层的分层架构,每一层之间只需要关心自己的工作,并和相邻的层交互。这就是应对复杂系统的组织方法——分层。

课本上会提到OSI七层模型或者TCP/IP五层模型,这里的模型全称是协议分层模型,又来新词了,别急,后面还有:

  • 协议(Protocol):同一层级内的交互规则。//横向
  • 服务(Service):不同层级间的交互规则。//纵向

每一层的工作,就是调用下层的接口,并为上层提供服务。接口(Interface)和服务的区别是,服务作为实体,由本层负责实现,暴露出接口供上层调用;而接口则是抽象的,本层并不知道下一层的可靠性。

由此你能否看出分层思想的优越性?每一层只关注自己的实现,于是大问题被分解成了小问题。好比一个总工作量100的问题,不了解分层思想的你只能10+10+10+……=100;而分层思想提供了乘法法则,于是你可以通过10*10=100,只需要完成20工作量。//个中思想也体现了OOP中的解耦。

上述类比中标注了一些对应关系:

  • 封包(Packaging):包装,货物→包裹。信息在层次间传递的过程就是封包/解封的过程。
  • 路由(Routing):分拣。根据包裹上的标签,决定包装的去向。

可以看到,每一层都有自己的“货物”,比如中转中心的载荷是满载包裹的长途货车而不是单个包裹。报文在每一层都被封装并交给下一层,要想得到原始报文只能一层一层解开,操作模式类似栈。由此协议分层模型也被简称为协议栈(Protocol stack)。

最后简单解释五层模型每一层的分工,自底向上顺序:

  • 物理层:对接物理介质,运输比特
    • 提供基于比特的通信路径
  • 链路层:将路径串联成链
    • 提供基于链路的接入、交付、和传输服务
  • 网络层:将链路编织成网
    • 提供任意两主机之间的通信
  • 运输层:将主机的通信分解为进程的通信
    • 提供进程间的逻辑通信
  • 应用层:实现用户需求
    • 向用户提供透明可靠的网络服务

偶剋!你已经了解了分层思想,下面来设计互联网吧!

⌚️开始实验

有关计网的学习顺序自古就有自顶向下还是自底向上的分歧,余以为只要理解了分层思想,顺序便不算很重要。本系列实验将遵从浅入深出的原则,从应用层逐步深入到链路层再返回应用层,同时难度不断加大。

实验来源:

  • 👴自己:0x10, 0x20
  • SEEDLab,雪城大学的信息安全课配套实验,网络安全部分。国内知名度不高所以值得一做。官方网站
  • CNTDA 实验:GIthub上抄的作业

实验代码仓库:lonelyuan/ComputerNetwork-exp (github.com)

实验编号规则:0xabn

  • a:层级:1 - 应用层;2 - 传输层;3 - 网络层;4 - 链路层;5 - 物理层
  • b:难度:0 - ⭐;1 - ⭐⭐;2 - ⭐⭐⭐;3 - ⭐⭐⭐⭐;4 - ⭐⭐⭐⭐⭐;
  • n:重复难度则再加一位编号

//【想看哪个没更新的可以催👴】

0x10 应用层: Server | ⭐

目标:用scapy/socket做一个静态服务器。

  • 实际上,python3已经自带了一个简易http服务器:
1
2
3
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [10/Nov/2021 14:51:57] "GET / HTTP/1.1" 200 -

此时浏览器访问:localhost:8000,如果当前目录下有index.html文件,浏览器即可显示该html文档。这条命令表明,调用python标准库http的server方法,默认会在8000端口启动一个http服务器。该服务器也是基于另一个python标准库socket编写的,本实验的目标即是自己实现类似的功能。

但首先我们需要了解一些前置知识:

  • C/S架构(client-server):互联网的基本模型。通信的双方通常分成两个角色:

    • 发起的一方称为客户端(C),即前端。
    • 接收的一方称为服务端(S),即后端。为了保证随时接收请求,服务端需要持久监听某通信端口
  • URL(统一资源标识符):俗称的网址,也就是互联网上的地址。

    • 完整语法:[协议名]://[用户名]:[密码]@[服务器地址]:[服务器端口号]/[路径]?[查询字符串]#[片段]
  • HTTP协议:应用层最普遍的文本协议之一。文本协议表示其所有内容都是可读的,其主要格式如下:

1
2
3
4
5
GET / HTTP/1.1\r\n                                           /* 一个状态行 */
Host: localhost\r\n                                          /* 多个首部行 */
...
Connection: close\r\n\r\n       /* 以两个CRLF(回车换行,编程时用\r\n表示)隔断 */
<html>... </html>                                              /* payload */
  • HTML:标记语言,用<>组织起网页的骨架。浏览器会把HTML源码渲染成好看的网页。
  • socket:逻辑通信的端点。
    • socket是逻辑通信的接口。上文提到网络层为运输层和应用层提供了点到点的逻辑通信服务,该服务的基本接口就是socket。
    • socket是通信端点的抽象。它将进程/应用和(主机host,端口port) 二元组绑定,于是通过 (host,port) 即可标记网络上的进程。
      • 一个主机有一个地址和多个端口。地址和端口的关系,就像房子和窗户的关系。
    • socket由操作系统提供。本实验用到的是python对socket的封装,但不管换什么语言本质上都是系统调用。
    • //其中文翻译“套接字”非常具有误导性,建议直接用英文单词。

最后,建议花5分钟通读《图解HTTP》前6章(或者《CNTDA》2.1-2.2节),你就能轻松理解上述术语。

Guidelines

  1. Socket通信

要使用socket通信,通信双方都需要持有一个socket对象,其主要方法和生命周期如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  SERVER                CLIENT

 socket()              socket()
    │                     │
    ▼                     │
  bind((host,port))       │
    │                     │
    ▼                     │
 listen(num)              │
    │                     │
    ▼                     ▼
 accept()             connect((host,port))
    │                     │
    ├──►send()──►recv()◄──┤
    │                     │
    ├──►recv()◄──send()◄──┤
    │                     │
    ▼                     ▼
 close()               close()

于是我们可以建立起服务器代码的框架:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import socket

s = socket.socket() 
s.bind(('0.0.0.0', 8000)) #  绑定地址和端口
s.listen(5) # 开始监听,num表示最大连接数量

while True:
    c, addr = s.accept() # c是客户端socket
    print('[+] accepted:', addr)
    req = c.recv(1024) 
    print('[+] recieved:', req.decode('utf-8')) # 接收类型为字节对象bytes,要打印则应当编码为字符串
    res = http_handler(req) # 解析请求,返回响应
    c.send(res)
    c.close()
  • It’s worth noting that,服务端socket并没有发送任何数据!accept()方法将返回一个客户端socket对象,由这个socket执行数据的收发。这样做的原因是为了实现多路复用,即让服务器支持多个连接同时通信。
  • 于是我们可以看到,对每个TCP连接,都有一对socket存在于通信的两端。而服务端socket仅仅做了管理连接的工作,他们放在一个类里,是出于简化代码的考虑。(当然实现多路复用的方式不只有一种。

现在,你可以自己尝试编写socket客户端跟该服务器进行明文的通信。不过我们的目标是HTTP服务器,先复习一下HTTP协议格式,状态码,首部等知识吧。

  1. HTTP解析

如果编程能力尚可,你可以自己写HTTP类来把报文解析成对象。这里还是用现成的,scapy库提供的HTTPRequestHTTPResponse类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from scapy.layers.http import *
from scapy.all import *

def http_handler(req_str):
    req = HTTPRequest()
    req.do_dissect(req_str) # 解析请求
    print('[+] req: ', req.summary())
    # body = route(req.Path.decode()) # 路由函数
    body = "<h1>Hello~~~</h1>"
    res = HTTPResponse()
    res = HTTP() / res / body
    print('[+] res: ', res.summary())
    return raw(res)
  • do_dissect()方法将字符串解析为对象

  • HTTP()/res/body:scapy核心语法/,表示协议栈的堆叠,可以链式调用。

  • 这里的类型为:HTTP / HTTPResponse / Raw,之所以要这样三层表示,是因为HTTPResponse/HTTPRequest类仅仅是一个中间层,如果没有HTTP层,scapy会报warning。

  • raw()方法返回封包的字节数组,可以看到在socket之上,我们先把报文转化为对象,解析之后再返回报文。

  • 观察封包的常用方法还有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
In [2]: a = Ether()/IP(dst="www.wsnd.com")/TCP()

In [3]: a
Out[3]: <Ether  type=IPv4 |<IP  frag=0 proto=tcp dst=Net("www.wsnd.com/32") |<TCP  |>>>

In [4]: a.summary()
Out[4]: 'Ether / IP / TCP 0.0.0.0:ftp_data > Net("www.wsnd.com/32"):http S'

In [5]: a.show()
###[ Ethernet ]###
  dst       = ff:ff:ff:ff:ff:ff
  src       = 00:00:00:00:00:00
  type      = IPv4
###[ IP ]###
     version   = 4
	...
     proto     = tcp
     chksum    = None
     src       = 0.0.0.0
     dst       = Net("www.wsnd.com/32")
     \options   \
###[ TCP ]###
        sport     = ftp_data
        dport     = http
		...

In [6]: ls(a)
dst        : DestMACField                        = 'ff:ff:ff:ff:ff:ff' ('None')
src        : SourceMACField                      = '00:00:00:00:00:00' ('None')
type       : XShortEnumField                     = 2048            ('36864')
...

In [7]: raw(a)
Out[7]: b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00(\x00\x01\x00\x00@\x06\xc9\xa8\x00\x00\x00\x00H\ti\x1e\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xdeW\x00\x00'

In [8]: hexdump(a)
0000  FF FF FF FF FF FF 00 00 00 00 00 00 08 00 45 00  ..............E.
0010  00 28 00 01 00 00 40 06 C9 A8 00 00 00 00 48 09  .(....@.......H.
0020  69 1E 00 14 00 50 00 00 00 00 00 00 00 00 50 02  i....P........P.
0030  20 00 DE 57 00 00                                 ..W..

现在运行服务器,用浏览器访问localhost:8000,你可以看到大大的“Hello”了!

Task

下面的任务交给你,目标是尽量接近python自带http服务器的表现。

为了实现静态服务器,你需要根据访问的路径返回对应的内容。为此,请完善route()函数:

  • 访问根路径/将返回index.html
  • 使用os模块读取文件,注意文本文件和二进制文件(如图片)的处理
  • 用HTTP响应码进行错误处理,比如404 NOT FOUND302 REDIRECT

最后,在根目录(你在哪里运行你的服务器脚本,那里就是你的根目录)下放入任意文件,浏览器都可以访问其内容,如果不存在则会返回404。

Expand

  • 抓包观察访问你的网站和访问正常网站有什么区别。你会发现,本实验几乎没有讲解HTTP首部的细节,请自行了解诸如Content-Type:Content-Length:Transfer-Encoding:等首部,看看传输图片/压缩文件时的标准做法,以及在遇到大文件时如何实现分段运输。(尽管我们的实现很简陋,浏览器还是能正常工作,说明HTTP是相当健壮的协议)
  • 你的服务器是否有安全问题?你可以访问根目录之外的文件吗?如: /../../../../etc/passwd(linux下)
  • 服务器概念辨析:Web初学者容易对服务器概念感到迷惑。软件语境下,服务器指对外提供服务的程序,常用服务器如apache、nginx,tomcat等;硬件语境下则指运行着服务器软件的机器。
  • 我们实现的是静态网站,你可能疑惑是不是还有动态网站。当然有,区分动态和静态并不是网页会不会自己动,而是服务器上的数据是否可以动态的改变,而我们的服务器只能被动的显示文件,客户端无法做出任何更改。现代web框架诸如Springboot,Django之类当然是动态网站框架。

实际上,传统计算机网络并不关心应用层以上的东西,让我们向下看,探究socket背后的原理吧。

0x20 传输层:Socket | ⭐

  • 目标:用 scapy 实现TCP协议,以尽可能替换上一个实验使用的socket模块
    • // 你可能猜到了,下一个实验是不是要自己实现IP协议呀?恭喜你猜错了。
  • 前置:
    • 术语
      • TCP/UDP
      • TCP报文格式
      • 有限状态机
    • 完成本实验仅涉及《CNTDA》3.4-3.5节,如果理解有困难,建议先完成Wireshark 实验:TCP观察
  • 在上一个实验中,我们了解到socket是操作系统提供的系统调用,例如 Linux 中,创建socket对象返回的sock_fd本质上就是一个文件描述符,即建立连接后可以直接像文件一样读写,绑定端口后操作系统会保护该端口不被其他进程占用。而本实验关注运输层原理,所以绕过了操作系统,使用更底层的接口实现TCP。当然,是最简陋的一种实现。

Guidelines

  1. Socket实现原理

那么,socket里面到底有什么?首先,要保存地址端口等信息;其次,要有收发的两个缓冲区,这里可以用队列;然后,为了实现可靠运输,需要用到计时器来触发重传,需要变量标记滑动窗口;我们还用自动机思想来管理连接状态。Socket底层模型如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
         ┌─────Socket──────┐
┌─────┐  │  ────────────┐  │  ┌────┐
│     ├──┼─►    SendQ   ├──┼─►│    │
│     │  │  ────────────┘  │  │    │
│ App │  │     Buffers     │  │ IP │
│     │  │  ┌────────────  │  │    │
│     │◄─┼──┤   RecvQ    ◄─┼──┤    │
└─────┘  │  └────────────  │  └────┘
         ├────Variables────┤
         │    Status       │
         │    Timer        │
         │    SendBase     │
         │    NextSeq      │
         └─────────────────┘

这里就需要面向对象上场了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Socket:
    def __init__():
        self.SendQ = Queue()
        self.RecvQ = Queue()
        self.Status = Status.CLOSED
        self.Timer = Timer()
        self.SendBase = 0
        self.NextSeq = 0
    # SERVER
    def bind(addr):
        self.addr = addr
        pass
    def listen(num): 
        pass
    def accept():
        #return c, addr 
        pass
    # CLIENT
    def connect(addr):
        pass
    # BOTH
    def recv(length):
        #return data
        pass
    def send(data):
        pass
    def close():
        pass

下面逐个实现socket接口。

【to be continued】

  1. 定制TCP报文

【to be continued】

  1. 连接管理

了解了三次握手,就可以实现connect函数了

【to be continued】

Task

  1. 多路复用

目前的实现只能支持一个TCP连接,请实现listen(num)函数,调用时创建 num 对读写缓冲区,响应的为

  1. 完善TCP功能:
  • RTT
  • 可靠运输
  • 流量控制
  • 阻塞控制
  1. 拓展UDP到你的socket

Expand

【to be continued】

0x30 网络层: 路由追踪 | ⭐

Intro

术语:

  • IP层:IP协议,ICMP协议,路由协议
  • 路由追踪:请求某地址经过了那些路由器?

Guidelines

scapy实现了路由追踪函数,你可以钻研一下源码(很短),下面写一个自己的traceroute。

Task

下面用Ipython演示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
In [1]: from scapy.all import *

In [2]: target="www.amazon.com"

In [3]: ans, unans = sr(IP(dst=target,ttl=(1,30))/TCP(flags=0x2))
Begin emission:
Finished sending 30 packets.
.*****..**********..........................................................................^C
Received 92 packets, got 15 answers, remaining 15 packets

In [4]: for snd, rcv in ans:
   ...:     print(snd.ttl, rcv.src, isinstance(rcv.payload, TCP))
   ...:
1 11.206.119.46 False
2 11.110.80.173 False
3 10.102.15.74 False
4 11.73.2.241 False
5 124.160.189.101 False
6 219.158.97.2 False
7 219.158.34.190 False
8 69.192.14.38 True
9 219.158.24.134 False
10 219.158.10.30 False
11 69.192.14.38 True
12 69.192.14.38 True
13 69.192.14.38 True
14 69.192.14.38 True

下面讲解核心代码:

ans,unans=sr(IP(dst=target,ttl=(1,30),id=RandShort())/TCP(flags=0x2))

  • sr():send and receive,返回的两个参数分别是得到应答的数据包列表和未应答的包列表。
  • ttl=(4,30):ttl参数在IP层表示ICMP包的转发次数(跳数)。此外,传入tuple表示一个范围,sr函数将会为这个范围内的每个值生成一个发包。(如果有多个tuple参数,则会按笛卡尔积规则生成发包列表)
  • TCP(flags=0x2):在TCP头部设定flag字段的值,0x2对应ACK,即确认收到包。
  • 综合起来,这条代码将发送30个包,其ttl从1到30。并筛选返回ACK的包。
  • 这样根据IP层路由算法,到达ttl的包无论是否找到目标都会返回,直到找到目标,TCP层返回ACK。遍历ttl形成的列表即是经过的所有路由。

Expand

0x301 网络层: 欺骗ping | ⭐ | TODO

Intro

  • 来源:https://seedsecuritylabs.org/Labs_20.04/Files/ICMP_Redirect/ICMP_Redirect.pdf
  • 术语:

Guidelines

Task

Expand

0x41 链路层: ARP缓存投毒 | ⭐⭐ | TODO

https://seedsecuritylabs.org/Labs_20.04/Files/ARP_Attack/ARP_Attack.pdf

0x21 传输层: TCP攻击 | ⭐⭐ | TODO

https://seedsecuritylabs.org/Labs_20.04/Files/TCP_Attacks/TCP_Attacks.pdf

TCP协议 SYN泛洪 TCP reset TCP session hijacking反弹shell (重点)

0x31 网络层: NAT,DHCP和虚拟机 | ⭐⭐ | TODO

相信折腾过虚拟机的同学都绕不过这个问题:我的虚拟机怎么连不上网?本实验基于wmware虚拟机平台,讲解几种虚拟机网络模式及其原理。

0x13 应用层: DNS本地攻击 | ⭐⭐⭐ | TODO

https://seedsecuritylabs.org/Labs_20.04/Files/DNS_Local/DNS_Local.pdf

0x14 应用层: SSL协议和HTTPS | ⭐⭐⭐⭐ | TODO

0x15 应用层: 多线程Web代理服务器 | ⭐⭐⭐⭐⭐ | TODO

0x151 应用层: VPN | ⭐⭐⭐⭐⭐ | TODO

探究VPN原理

0x152 应用层: V2Ray协议学习 | ??? | TODO

有生之年研究一下Vmess等协议

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy