first commit

This commit is contained in:
2026-04-07 20:49:20 +08:00
commit cd80bbe528
43 changed files with 18366 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,121 @@
version: "3"
services:
victim:
image: handsonsecurity/seed-ubuntu:large
container_name: victim-10.9.0.5
tty: true
cap_add:
- ALL
sysctls:
- net.ipv4.conf.all.accept_redirects=1
privileged: true
networks:
net-10.9.0.0:
ipv4_address: 10.9.0.5
command: bash -c "
ip route add 192.168.60.0/24 via 10.9.0.11 &&
tail -f /dev/null
"
attacker:
image: handsonsecurity/seed-ubuntu:large
container_name: attacker-10.9.0.105
tty: true
cap_add:
- ALL
privileged: true
volumes:
- ./volumes:/volumes
networks:
net-10.9.0.0:
ipv4_address: 10.9.0.105
command: bash -c "
ip route add 192.168.60.0/24 via 10.9.0.11 &&
tail -f /dev/null
"
malicious-router:
image: handsonsecurity/seed-ubuntu:large
container_name: malicious-router-10.9.0.111
tty: true
cap_add:
- ALL
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.send_redirects=0
- net.ipv4.conf.default.send_redirects=0
- net.ipv4.conf.eth0.send_redirects=0
privileged: true
volumes:
- ./volumes:/volumes
networks:
net-10.9.0.0:
ipv4_address: 10.9.0.111
command: bash -c "
ip route add 192.168.60.0/24 via 10.9.0.11 &&
tail -f /dev/null
"
HostB1:
image: handsonsecurity/seed-ubuntu:large
container_name: host-192.168.60.5
tty: true
cap_add:
- ALL
networks:
net-192.168.60.0:
ipv4_address: 192.168.60.5
command: bash -c "
ip route del default &&
ip route add 10.9.0.0/24 via 192.168.60.11 &&
tail -f /dev/null
"
HostB2:
image: handsonsecurity/seed-ubuntu:large
container_name: host-192.168.60.6
tty: true
cap_add:
- ALL
networks:
net-192.168.60.0:
ipv4_address: 192.168.60.6
command: bash -c "
ip route del default &&
ip route add 10.9.0.0/24 via 192.168.60.11 &&
tail -f /dev/null
"
Router:
image: handsonsecurity/seed-ubuntu:large
container_name: router
tty: true
cap_add:
- ALL
sysctls:
- net.ipv4.ip_forward=1
networks:
net-10.9.0.0:
ipv4_address: 10.9.0.11
net-192.168.60.0:
ipv4_address: 192.168.60.11
command: bash -c "
ip route del default &&
ip route add default via 10.9.0.1 &&
tail -f /dev/null
"
networks:
net-192.168.60.0:
name: net-192.168.60.0
ipam:
config:
- subnet: 192.168.60.0/24
net-10.9.0.0:
name: net-10.9.0.0
ipam:
config:
- subnet: 10.9.0.0/24

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python3
from scapy.all import *
print("LAUNCHING MITM ATTACK.........")
def spoof_pkt(pkt):
newpkt = IP(bytes(pkt[IP]))
del(newpkt.chksum)
del(newpkt[TCP].payload)
del(newpkt[TCP].chksum)
if pkt[TCP].payload:
data = pkt[TCP].payload.load
print("*** %s, length: %d" % (data, len(data)))
# Replace a pattern
newdata = data.replace(b'seedlabs', b'AAAAAAAA')
send(newpkt/newdata)
else:
send(newpkt)
f = 'tcp'
pkt = sniff(iface='eth0', filter=f, prn=spoof_pkt)

View File

View File

@@ -0,0 +1,49 @@
#version: "3"
services:
attacker:
image: handsonsecurity/seed-ubuntu:large
container_name: seed-attacker
tty: true
cap_add:
- ALL
privileged: true
volumes:
- ./volumes:/volumes
network_mode: host
hostA:
image: handsonsecurity/seed-ubuntu:large
container_name: hostA-10.9.0.5
tty: true
cap_add:
- ALL
networks:
net-10.9.0.0:
ipv4_address: 10.9.0.5
command: bash -c "
/etc/init.d/openbsd-inetd start &&
tail -f /dev/null
"
hostB:
image: handsonsecurity/seed-ubuntu:large
container_name: hostB-10.9.0.6
tty: true
cap_add:
- ALL
networks:
net-10.9.0.0:
ipv4_address: 10.9.0.6
command: bash -c "
/etc/init.d/openbsd-inetd start &&
tail -f /dev/null
"
networks:
net-10.9.0.0:
name: net-10.9.0.0
ipam:
config:
- subnet: 10.9.0.0/24

View File

View File

@@ -0,0 +1,76 @@
#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
unsigned short in_cksum (unsigned short *buf, int length) {
unsigned short *w = buf;
int nleft = length;
int sum = 0;
unsigned short temp=0;
while (nleft > 1) { sum += *w++; nleft -= 2; }
if (nleft == 1) { *(u_char *)(&temp) = *(u_char *)w ; sum += temp; }
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
void send_raw_ip_packet(struct iphdr* ip) {
struct sockaddr_in dest_info;
int enable = 1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable));
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = ip->daddr;
sendto(sock, ip, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_info, sizeof(dest_info));
close(sock);
}
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
struct iphdr *ip = (struct iphdr *)(packet + 14); // Skip Ethernet header (14 bytes)
if (ip->protocol != IPPROTO_ICMP) return;
struct icmphdr *icmp = (struct icmphdr *)(packet + 14 + (ip->ihl * 4));
if (icmp->type == 8) { // Echo request
printf("Intercepted ICMP Echo Request from %s to %s\n", inet_ntoa(*(struct in_addr *)&ip->saddr), inet_ntoa(*(struct in_addr *)&ip->daddr));
char buffer[1500];
int ip_header_len = ip->ihl * 4;
int icmp_len = ntohs(ip->tot_len) - ip_header_len;
memcpy(buffer, ip, ntohs(ip->tot_len));
struct iphdr *new_ip = (struct iphdr *)buffer;
struct icmphdr *new_icmp = (struct icmphdr *)(buffer + ip_header_len);
// Swap addresses
new_ip->saddr = ip->daddr;
new_ip->daddr = ip->saddr;
new_ip->ttl = 64;
// Change to Echo Reply
new_icmp->type = 0;
new_icmp->checksum = 0;
new_icmp->checksum = in_cksum((unsigned short *)new_icmp, icmp_len);
printf("Sending spoofed ICMP Echo Reply from %s back to %s...\n", inet_ntoa(*(struct in_addr *)&new_ip->saddr), inet_ntoa(*(struct in_addr *)&new_ip->daddr));
send_raw_ip_packet(new_ip);
}
}
int main() {
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "icmp";
handle = pcap_open_live("br-c031fbf1a197", BUFSIZ, 1, 1000, errbuf);
pcap_compile(handle, &fp, filter_exp, 0, PCAP_NETMASK_UNKNOWN);
pcap_setfilter(handle, &fp);
printf("C-based Sniff-and-Spoof active...\n");
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle);
return 0;
}

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
from scapy.all import *
def spoof_reply(pkt):
# Only respond to ICMP Echo Requests
if ICMP in pkt and pkt[ICMP].type == 8:
print(f"Intercepted ICMP Echo Request from {pkt[IP].src} to {pkt[IP].dst}")
# Build spoofed ICMP Echo Reply
ip = IP(src=pkt[IP].dst, dst=pkt[IP].src)
icmp = ICMP(type=0, id=pkt[ICMP].id, seq=pkt[ICMP].seq)
# Add payload if present
payload = pkt[Raw].load if Raw in pkt else b""
new_pkt = ip/icmp/payload
print(f"Sending spoofed reply from {pkt[IP].dst} to {pkt[IP].src}...")
send(new_pkt, verbose=0)
print("Sniff-and-Spoof active on br-c031fbf1a197...")
# Filter: icmp echo-request
sniff(iface='br-c031fbf1a197', filter='icmp and icmp[icmptype]=8', prn=spoof_reply)

Binary file not shown.

View File

@@ -0,0 +1,39 @@
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
printf("Got a packet\n");
}
int main() {
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "icmp";
bpf_u_int32 net;
// Step 1: Open pcap session on the interface
handle = pcap_open_live("br-c031fbf1a197", BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device: %s\n", errbuf);
return 2;
}
// Step 2: Compile filter_exp into BPF code
if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 2;
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 2;
}
// Step 3: Capture packets
printf("Sniffing ICMP packets using C and libpcap...\n");
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle);
return 0;
}

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python3
from scapy.all import *
def print_pkt(pkt):
pkt.show()
print("Sniffing ICMP packets from spoofed IP 1.2.3.4...")
pkt = sniff(iface='br-c031fbf1a197', filter='icmp and src host 1.2.3.4', prn=print_pkt, count=1)

Binary file not shown.

View File

@@ -0,0 +1,81 @@
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
/* Checksum calculation function */
unsigned short in_cksum (unsigned short *buf, int length) {
unsigned short *w = buf;
int nleft = length;
int sum = 0;
unsigned short temp=0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&temp) = *(u_char *)w ;
sum += temp;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
void send_raw_ip_packet(struct iphdr* ip) {
struct sockaddr_in dest_info;
int enable = 1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock < 0) {
perror("Socket creation failed");
return;
}
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable));
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = ip->daddr;
if (sendto(sock, ip, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_info, sizeof(dest_info)) < 0) {
perror("Sendto failed");
} else {
printf("Spoofed ICMP packet sent.\n");
}
close(sock);
}
int main() {
char buffer[1500];
memset(buffer, 0, 1500);
struct iphdr *ip = (struct iphdr *) buffer;
struct icmphdr *icmp = (struct icmphdr *) (buffer + sizeof(struct iphdr));
// Construct ICMP Header
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->un.echo.id = htons(1234);
icmp->un.echo.sequence = htons(1);
icmp->checksum = 0;
icmp->checksum = in_cksum((unsigned short *)icmp, sizeof(struct icmphdr));
// Construct IP Header
ip->version = 4;
ip->ihl = 5;
ip->ttl = 64;
ip->saddr = inet_addr("1.2.3.4");
ip->daddr = inet_addr("10.9.0.5");
ip->protocol = IPPROTO_ICMP;
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr));
send_raw_ip_packet(ip);
return 0;
}

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
from scapy.all import *
print("Spoofing ICMP echo request from 1.2.3.4 to 10.9.0.5...")
a = IP()
a.src = '1.2.3.4'
a.dst = '10.9.0.5'
b = ICMP()
p = a/b
send(p)

Binary file not shown.

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python3
from scapy.all import *
target = "8.8.8.8"
print(f"Traceroute to {target}...")
for i in range(1, 31):
pkt = IP(dst=target, ttl=i) / ICMP()
reply = sr1(pkt, verbose=0, timeout=1)
if reply is None:
print(f"{i}: * * *")
elif reply.type == 3: # Destination unreachable
print(f"{i}: {reply.src} (Unreachable)")
break
else:
print(f"{i}: {reply.src}")
if reply.src == target:
print("Reached target!")
break

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -0,0 +1,153 @@
#let times = "Times LT Pro"
#let times = "Times New Roman"
#let song = (times, "Noto Serif CJK SC")
#let hei = (times, "Noto Sans CJK SC")
#let kai = (times, "Noto Serif CJK SC")
#let xbsong = (times, "Noto Serif CJK SC")
#let fsong = (times, "Noto Serif CJK SC")
#let code = (times, "JetBrains Mono")
#let nudtlabpaper(title: "",
author: "",
id: "",
training_type:"",
grade: "",
major: "",
department: "",
advisor: "",
jobtitle: "",
lab: "",
date: "",
header_str: "",
body) = {
// Set the document's basic properties.
set document(author: author, title: title)
set page(
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
)
// Title row.
v(158pt)
align(center)[
#block(text(weight: 700, size: 30pt, font: hei, tracking: 15pt, "网络安全"))
]
align(center)[
#block(text(weight: 700, size: 30pt, font: song, tracking: 15pt, "本科实验报告"))
]
v(103pt)
pad(
left: 1em,
right: 1em,
grid(
columns: (80pt, 1fr),
rows: (17pt, auto),
text(weight: 700, size: 16pt, font: song, "实验名称:"),
align(center, text(weight: "regular", size: 16pt, font: song, title)),
text(""),
line(length: 100%)
)
// #block(text(weight: 700, 1.75em, title))
// underline(text(weight: 700, size: 16pt, font: song, title))
)
// Author information.
v(82.5pt)
grid(
columns: (0.25fr, 0.25fr, 0.25fr, 0.25fr),
rows: (15pt, 8pt, 15pt, 8pt, 15pt, 8pt, 15pt, 8pt, 15pt),
text(size: 14pt, font: song, tracking: 10pt, "学员姓名"),
align(center, text(size: 14pt, font: song, author)),
text(size: 14pt, font: song, tracking: 54pt, "学号"),
align(center, text(size: 14pt, font: times, id)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
text(size: 14pt, font: song, tracking: 9pt, "培养类型"),
align(center, text(size: 14pt, font: song, training_type)),
text(size: 14pt, font: song, tracking: 54pt, "年级"),
align(center, text(size: 14pt, font: times, grade)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
text(size: 14pt, font: song, tracking: 54pt, "专业"),
align(center, text(size: 14pt, font: song, major)),
text(size: 14pt, font: song, tracking: 9pt, "所属学院"),
align(center, text(size: 14pt, font: song, department)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
text(size: 14pt, font: song, tracking: 9pt, "指导教员"),
align(center, text(size: 14pt, font: song, advisor)),
text(size: 14pt, font: song, tracking: 54pt, "职称"),
align(center, text(size: 14pt, font: song, jobtitle)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
text(size: 14pt, font: song, tracking: 20pt, "实验室"),
align(center, text(size: 14pt, font: song, lab)),
text(size: 14pt, font: song, tracking: 9pt, "实验时间"),
align(center, text(size: 14pt, font: song, date)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
)
v(50.5pt)
align(center, text(font: hei, size: 15pt, "国防科技大学教育训练部制"))
pagebreak()
set page(
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
numbering: "i",
number-align: center,
)
v(14pt)
align(center)[
#block(text(font: hei, size: 14pt, "《本科实验报告》填写说明"))
]
v(14pt)
text("")
par(first-line-indent: 2em, text(font: song, size: 12pt, "实验报告内容编排应符合以下要求:"))
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "1采用A421cm×29.7cm白色复印纸单面黑字。上下左右各侧的页边距均为3cm缺省文档网格字号为小4号中文为宋体英文和阿拉伯数字为Times New Roman每页30行每行36字页脚距边界为2.5cm页码置于页脚、居中采用小5号阿拉伯数字从1开始连续编排封面不编页码。"))
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "2报告正文最多可设四级标题字体均为黑体第一级标题字号为4号其余各级标题为小4号标题序号第一级用“一、”、“二、”……第二级用“”、“” ……第三级用“1.”、“2.” ……第四级用“1”、“2” ……,分别按序连续编排。"))
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "3正文插图、表格中的文字字号均为5号。"))
pagebreak()
set page(
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
numbering: "1",
number-align: center,
)
set heading(numbering: "1.1")
// set text(font: hei, lang: "zh")
show heading: it => box(width: 100%)[
#v(0.50em)
#set text(font: hei)
#counter(heading).display()
// #h(0.5em)
#it.body
]
// Main body.
set par(justify: true)
body
}
#let para(t) = par(first-line-indent: 2em, text(font: song, size: 10.5pt, t))

View File

@@ -0,0 +1,168 @@
#import "labtemplate.typ": *
#show: nudtlabpaper.with(title: "数据包嗅探与伪造实验",
author: "程景愉",
id: "202302723005",
training_type: "无军籍",
grade: "2023",
major: "网络工程",
department: "计算机学院",
advisor: "柳林",
jobtitle: "教授",
lab: "307-208",
date: "2026.04.07",
header_str: "《网络安全》实验报告",
)
#set page(header: [
#set par(spacing: 6pt)
#align(center)[#text(size: 11pt)[《网络安全》实验报告]]
#v(-0.3em)
#line(length: 100%, stroke: (thickness: 1pt))
],)
#outline(title: "目录",depth: 3, indent: 2em)
#pagebreak()
#outline(
title: [图目录],
target: figure.where(kind: image),
)
#pagebreak()
= 实验目的
#para[
本实验旨在深入理解网络协议栈底层的通信机制掌握数据包嗅探Sniffing与伪造Spoofing的关键技术。通过本实验学员应达到以下目标
]
- 掌握使用 Scapy 库进行快速原型开发,实现自定义协议包的捕获与重构。
- 理解原始套接字Raw Socket的工作原理掌握在 C 语言中手动构造 IP ICMP 报文头的方法。
- 熟悉 libpcap 库的使用,了解 Berkeley Packet Filter (BPF) 过滤语法。
- 深入理解局域网内 ARP 协议与 ICMP 协议的交互逻辑,能够分析并解释嗅探与伪造中的异常现象。
- 掌握在 Docker 虚拟化网络环境下进行网络安全实验的基本流程。
= 实验原理
== 数据包嗅探Sniffing
#para[
数据包嗅探是指在不影响网络正常运行的前提下捕获网络中流动的数据包并进行分析。其技术核心在于将网卡设置为“混杂模式”Promiscuous Mode从而使其能够接收所有流经物理链路的帧而不仅仅是发往本机的帧。
]
- *libpcap & Scapy*: libpcap 是底层捕获库Scapy 是基于 Python 的高级库。它们通过内核提供的 `AF_PACKET` 套接字直接读取网卡驱动层的原始二进制流。
- *BPF 过滤器*: 使用 BPF 语法(如 `icmp`, `tcp port 23`)在内核态进行报文过滤,大幅减少了用户态处理不必要数据的开销。
== 数据包伪造Spoofing
#para[
伪造技术允许攻击者构造并发送具有任意首部字段(如源 IP、校验和等的数据包。
]
- *IP_HDRINCL*: 在使用原始套接字时,通过设置 `IP_HDRINCL` 选项,告知操作系统协议栈不要自动填充 IP 头部,而是由应用程序自行构造。
- *Checksum 计算*: 传输层和网络层协议通常要求对头部和数据进行校验。计算方法为将数据按 16 位求和并取反,这是确保伪造包被受害者接受的关键。
== ARP 与嗅探的交互
#para[
在以太网局域网中ICMP 通信依赖于 ARP 地址解析。如果目标 IP 在本地子网内但物理地址MAC未知发送方必须先进行 ARP 广播。若 ARP 失败,则后续的 ICMP 包无法被构造和发出,这对“嗅探并回复”类的攻击构成了逻辑约束。
]
= 实验环境
== 实验平台
#align(center)[#table(
columns: (auto, auto),
rows:(2em, 2em, 2em),
inset: 10pt,
align: horizon+center,
table.header([*硬件/软件*], [*详细配置*]),
"物理机", "ThinkPad T14 Gen4",
"操作系统", "CachyOS (Linux kernel 6.x)",
"虚拟化环境", "Docker Engine 24.x & Docker Compose",
"实验镜像", "SEED-Ubuntu 20.04 (handsonsecurity/seed-server)",
)]
== 网络拓扑与部署
#para[
本实验通过 Docker 创建了一个 `net-10.9.0.0/24` 的独立网络。攻击者Attacker容器运行在 `host` 网络模式下,可以直接访问宿主机的虚拟网桥接口(如 `br-c031fbf1a197`),从而监听整个实验网络的流量。
]
#figure(
image("dockerps.png", width: 90%),
caption: [使用 Docker Compose 部署的实验环境状态],
)
= 实验步骤及结果
== 任务集 1使用 Scapy 进行嗅探与伪造
=== 任务 1.1A:权限与基础嗅探
#para[
编写 `sniffer.py`。关键逻辑是调用 `sniff()` 函数并指定接口名。由于涉及原始套接字操作,必须使用 `root` 权限。
]
#figure(
image("task1.1a_root.png", width: 85%),
caption: [Root 权限下成功捕获 Host B 发往 Host A ICMP ],
)
#para[
*权限对比*:不带 `sudo` 运行时,内核拒绝创建套接字,抛出 `PermissionError`
]
#figure(
image("task1.1a_user.png", width: 85%),
caption: [非特权用户运行失败截图],
)
=== 任务 1.1BBPF 过滤器应用
#para[
通过设置复杂的 BPF 字符串,实现精准捕获。例如 `tcp and src host 10.9.0.6 and dst port 23` 专门针对 Telnet 握手包。
]
#figure(
image("task1.1b_tcp.png", width: 85%),
caption: [精准捕获来自 10.9.0.6 TCP 端口 23 连接请求],
)
#figure(
image("task1.1b_subnet.png", width: 85%),
caption: [捕获整个 10.9.0.0/24 网段的 ARP 解析过程],
)
=== 任务 1.2 & 1.3:伪造报文与 Traceroute
#para[
伪造包的核心在于重载的 `/` 运算符:`IP(src='1.2.3.4')/ICMP()`。Traceroute 则是通过步进增加 `ttl` 字段。
]
#figure(
image("task1.2_result.png", width: 85%),
caption: [验证伪造:嗅探器显示源 IP 1.2.3.4 的报文],
)
#figure(
image("task1.3_result.png", width: 85%),
caption: [自定义 Traceroute 成功探测到外部多跳节点],
)
=== 任务 1.4:嗅探与伪造结合
#para[
该任务实现了“反应式伪造”。实验中对 `10.9.0.99` Ping 失败,证实了 ARP 解析是局域网嗅探攻击的前提条件。而对 `1.2.3.4` 的成功伪造则展示了跨网段通信中网关转发的特性。
]
#figure(
image("task1.4_result.png", width: 85%),
caption: [嗅探并回复程序对不同类型目标的处理结果],
)
== 任务集 2使用 C 语言进行嗅探与伪造
=== 任务 2.1:基于 libpcap 的嗅探
#para[
C 代码中,通过 `pcap_open_live` 捕获数据。相比 PythonC 语言需要手动偏移指针来跳过以太网头部14 字节)以获取 IP 包。
]
#figure(
image("task2.1_result.png", width: 85%),
caption: [C 语言嗅探程序捕获 ICMP 原始数据],
)
=== 任务 2.2:基于 Raw Socket 的伪造
#para[
`spoofer.c` 中,手动构造了 `struct iphdr` `struct icmphdr`。注意校验和计算前必须将对应字段置 0否则结果将错误。
]
#figure(
image("task2.2_result_c.png", width: 85%),
caption: [C 语言程序成功通过 Raw Socket 发出伪造 IP 报文],
)
=== 任务 2.3:综合实现
#para[
这是实验中最具挑战性的部分。程序在监听到 ICMP Echo Request 时,实时提取其 ID Sequence原样封装进伪造的 Echo Reply 中发回。
]
#figure(
image("task2.3_result_c.png", width: 85%),
caption: [C 语言综合程序:实现对 ICMP 请求的实时伪造响应],
)
= 实验总结
#para[
本次实验在 *ThinkPad T14 Gen4* 物理机上圆满完成。实验不仅巩固了对 Scapy C 语言网络编程的理解,更通过对 ARP 和网关机制的分析揭示了网络攻击与防御中协议层级间的依赖关系。Scapy 的灵活性极大提高了实验效率,而 C 语言实现则让我对报文校验和Checksum及内存对齐等底层细节有了深刻认知。这为后续学习防火墙原理及入侵检测系统打下了坚实基础。
]

16969
Sniffing_Spoofing/main.pdf Normal file

File diff suppressed because it is too large Load Diff

369
Sniffing_Spoofing/main.typ Normal file
View File

@@ -0,0 +1,369 @@
#import "labtemplate.typ": *
#show: nudtlabpaper.with(title: "数据包嗅探与伪造实验",
author: "程景愉",
id: "202302723005",
training_type: "无军籍",
grade: "2023",
major: "网络工程",
department: "计算机学院",
advisor: "柳林",
jobtitle: "教授",
lab: "307-208",
date: "2026.04.07",
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
]
#outline(title: "目录",depth: 3, indent: 2em)
#pagebreak()
#outline(
title: [图目录],
target: figure.where(kind: image),
)
#pagebreak()
= 实验目的
#para[
网络安全是信息时代最为重要的议题之一而数据包嗅探Sniffing与伪造Spoofing则是网络安全领域最为基础且极为重要的技术手段。通过本次实验我们期望深入理解网络协议栈底层的通信机制掌握在局域网环境中进行数据包捕获与分析的技术方法并学会构造并发送具有任意首部字段的伪造数据包。本实验旨在达成以下具体目标
]
- 掌握使用 Scapy 库进行快速原型开发实现自定义协议包的捕获与重构。Scapy Python 语言中功能最为强大的网络数据包处理库,它允许用户以交互式方式构造、发送、捕获并解析各类网络协议数据包。本次实验将使用 Scapy 完成基础的嗅探任务、BPF 过滤规则的应用、自定义 IP ICMP 数据包的构造与发送、以及实现一个能够对 ICMP Echo Request 进行实时伪造响应的嗅探-伪造联动程序。
- 理解原始套接字Raw Socket的工作原理掌握在 C 语言中手动构造 IP ICMP 报文头的方法。原始套接字绕过了操作系统协议栈的常规处理流程,允许应用程序直接访问 IP 层乃至链路层的数据。本实验将使用 C 语言从零开始构造 IP 头和 ICMP 头,并计算符合 RFC 792 规范的 ICMP 校验和,从而实现与 Scapy 等效的数据包伪造功能。
- 熟悉 libpcap 库的使用,了解 Berkeley Packet FilterBPF过滤语法的基本规则与应用场景。libpcap Unix/Linux 系统下进行网络数据包捕获的事实标准接口,它提供了与具体网卡无关的统一 API。本次实验将学习如何使用 libpcap 打开网络接口、编译并安装 BPF 过滤器、以及遍历捕获的数据包结构。
- 深入理解局域网内 ARP 协议与 ICMP 协议的交互逻辑能够分析并解释嗅探与伪造中的异常现象。在以太网环境中ICMP 通信依赖于 ARP 协议完成 IP 地址到 MAC 地址的映射。如果目标 IP 尚未完成 ARP 解析,则即使向该地址发送伪造的 ICMP 包,也将面临"有去无回"的困境。这一约束条件对于理解网络攻击的可行性边界具有重要意义。
- 掌握在 Docker 虚拟化网络环境下进行网络安全实验的基本流程。传统网络安全实验往往需要在真实网络环境中进行,存在破坏生产网络的风险。通过 Docker Compose 搭建的隔离实验网络,我们可以在安全的虚拟环境中自由地尝试各种攻击与防御技术,而无需担心对外部网络造成影响。
= 实验原理
== 数据包嗅探Sniffing技术原理
=== 混杂模式与网络监听
#para[
数据包嗅探是指在不影响网络正常运行的前提下,捕获网络中流动的数据包并进行分析的技术手段。其核心原理在于将网卡设置为"混杂模式"Promiscuous Mode。在普通模式下网卡仅接收发往本机 MAC 地址的以太网帧,以及广播帧和组播帧;而在混杂模式下,网卡会接收流经同一物理链路的所有帧,而不论其目标地址为何。这一特性使得攻击者能够"听到"局域网内的全部通信流量,从而为流量分析和中间人攻击等场景提供了技术基础。
]
#para[
需要特别指出的是,混杂模式仅在共享介质(如使用集线器连接的网络,或开启了端口镜像的交换机环境)下才能嗅探到其他主机的流量。在现代交换式以太网环境中,未经特殊配置的普通主机无法直接嗅探到同一广播域内其他主机的通信内容,这也是交换机相比集线器在安全性上的优势所在。
]
=== libpcap 与 Scapy 架构分析
#para[
libpcapPacket Capture Library是类 Unix 系统下进行网络数据包捕获的标准库。它通过内核提供的 `AF_PACKET` 套接字接口直接读取网卡驱动层的原始二进制数据流。libpcap 的核心 API 包括:`pcap_open_live()` 用于打开网络接口并创建捕获句柄;`pcap_compile()` 用于将 BPF 过滤表达式编译为可执行的过滤器程序;`pcap_setfilter()` 用于将编译后的过滤器安装到内核态;`pcap_loop()` `pcap_next()` 用于实际捕获数据包。
]
#para[
Scapy 是由 Philippe Biondi 开发的基于 Python 的高级网络库,其设计理念与 libpcap 截然不同。Scapy 将各种网络协议抽象为 Python 对象,用户可以通过重载的"/"运算符(称为"堆叠"操作)以极其直观的方式组合各层协议头。例如,`IP(src="1.2.3.4")/ICMP()` 表示构造一个源 IP 1.2.3.4 ICMP 数据包。这种"协议堆叠"的抽象方式极大地简化了复杂数据包的构造过程,使得研究人员和安全工程师能够快速验证各种网络协议行为。
]
=== BPF 过滤器工作原理
#para[
Berkeley Packet FilterBPF Linux 内核提供的一种高效的数据包过滤机制。与在用户态对每个数据包进行判断的朴素方法不同BPF 允许用户将过滤规则编译为内核态执行的虚拟机指令。当数据包到达网卡时,内核中的 BPF 虚拟机直接根据这些指令决定是否将数据包传递到用户态程序。这一设计大幅减少了不必要的数据拷贝和上下文切换开销,是现代网络抓包工具(如 tcpdump、Wireshark的性能基础。
]
#para[
BPF 过滤表达式使用类汇编语言的操作码和伪指令。例如,`icmp` 指令会检查数据包的协议字段是否为 ICMP`src host 10.9.0.6` 指令会检查源 IP 地址是否为指定地址;逻辑运算符如 `and``or``not` 用于组合多个条件。复杂的过滤表达式如 `tcp and src host 10.9.0.6 and dst port 23` 可以在内核态精确定位感兴趣的流量。
]
== 数据包伪造Spoofing技术原理
=== IP_HDRINCL 与原始套接字
#para[
伪造技术允许攻击者构造并发送具有任意首部字段(如源 IP、校验和等的数据包。在 Linux 系统中使用原始套接字Raw Socket是实现数据包伪造的主要手段。创建原始套接字时需要指定 `IPPROTO_RAW` 或具体的协议号(如 `IPPROTO_ICMP`),这会使套接字绕过传输层的自动头部填充机制。
]
#para[
`IP_HDRINCL` 是一个极为关键的套接字选项。当设置 `IP_HDRINCL = 1` 时,操作系统协议栈知道应用程序将自行构造完整的 IP 头部,不会再自动填充或修改 IP 头中的字段。这意味着我们可以将源 IP 地址设为任意值(称为 IP 欺骗),从而实现"匿名"通信或身份伪装。然而需要注意的是,伪造源 IP 地址会导致响应包无法送达,因此这种技术通常只适用于单向通信场景。
]
=== 校验和Checksum计算
#para[
传输层和网络层协议通常要求对首部和数据进行校验以确保数据完整性。ICMP 协议的校验和计算方法定义于 RFC 792其算法可描述如下将待校验的数据按 16 位字长划分,所有 16 位字进行二进制求和,然后将得到的 32 位和的高 16 位与低 16 位再次相加,重复这一过程直至进位消失,最后对结果取反即得到校验和。
]
#para[
在实际实现中需要注意一个常见陷阱:在计算校验和之前,必须将校验和字段本身置零。否则,如果该字段原本就包含一个非零值(例如从捕获的数据包中复制而来),则计算得到的校验和将是错误的。正确的做法是在构造 ICMP 报文时,先将 `checksum` 字段设为 0再调用校验和计算函数这是确保伪造包被目标主机正确接受的关键步骤。
]
== ARP 与 ICMP 的交互逻辑
=== ARP 地址解析机制
#para[
在以太网局域网中IP 层的通信最终需要转换为链路层的帧传输。当主机需要向同一子网内的另一个 IP 地址发送数据包时,必须首先知道该 IP 对应的 MAC 地址。ARPAddress Resolution Protocol协议正是用来解决这一问题的。在发送 ICMP Echo Request 之前,主机会首先广播 ARP 请求,询问"谁拥有 IP 10.9.0.5,请告诉我你的 MAC 地址"。目标主机收到请求后会回复 ARP 响应,其中包含其 MAC 地址。此后,发送方才能构造包含正确链路层目的地址的以太网帧。
]
=== ARP 失败对 ICMP 通信的影响
#para[
如果目标 IP 地址在本地子网内但尚未完成 ARP 解析(即发送方尚不知道目标的 MAC 地址),则任何尝试向该地址发送 IP 数据包的行为都将失败。这是因为以太网帧必须包含正确的目的 MAC 地址,而该地址只能通过 ARP 获取。反过来,如果目标 IP 地址位于不同子网(需经网关转发),则发送方只需知道网关的 MAC 地址即可将数据包发出,与目标主机是否可达无关。
]
#para[
这一约束条件对"嗅探并回复"类型的攻击具有重要影响。若攻击者试图伪造 ICMP Echo Reply 响应来自 10.9.0.99 的请求,则必须确保该 IP 在本地网络中可达(即已通过 ARP 成功解析)。若 10.9.0.99 不存在或 ARP 解析失败,则即使伪造包能够发出,也无法获得真实的请求包,从而陷入"盲目伪造"的困境。
]
= 实验环境
== 实验平台
#align(center)[#table(
columns: (auto, auto),
rows:(2em, 2em, 2em),
inset: 10pt,
align: horizon+center,
table.header([*硬件/软件*], [*详细配置*]),
"物理机", "ThinkPad T14 Gen4",
"操作系统", "CachyOS (Linux kernel 6.19.11)",
"虚拟化环境", "Docker Engine 24.x & Docker Compose",
)]
#align(center)[#table(
columns: (auto, auto),
rows:(2em, 2em),
inset: 10pt,
align: horizon+center,
table.header([*实验工具*], [*版本/说明*]),
"Scapy", "基于 Python3 的高级数据包处理库",
"libpcap", "Linux 内核 Packet Capture 库",
"GCC", "GNU 编译器套件(用于编译 C 语言程序)",
"实验镜像", "SEED-Ubuntu 20.04 (handsonsecurity/seed-server)",
)]
== 网络拓扑与部署
#para[
本实验通过 Docker Compose 创建了一个独立虚拟网络 `net-10.9.0.0/24`以模拟隔离的局域网环境。该网络内包含两台主机Host A Host B分别拥有独立的 IP 地址。此外还有一台攻击者容器Attacker运行在 `host` 网络模式下,可以直接访问宿主机的虚拟网桥接口(如 `br-c031fbf1a197`),从而监听整个实验网络的流量。这种"混杂模式"的网络配置使得攻击者容器能够同时扮演两个角色:一是作为虚拟网络的一部分与其他容器通信;二是作为宿主机的代理,直接访问物理网卡或虚拟网桥设备。
]
#figure(
image("dockerps.png", width: 90%),
caption: [使用 Docker Compose 部署的实验环境状态,展示了各容器的网络配置与运行状态],
)
#align(center)[#table(
columns: (auto, auto, auto),
rows:(2em, 2em, 2em),
inset: 10pt,
align: horizon+center,
table.header([*主机角色*], [*IP地址*], [*说明*]),
"Attacker攻击者", "host 网络模式", "可直接访问宿主机网卡与虚拟网桥",
"Host A", "10.9.0.5", "实验网络中的普通主机",
"Host B", "10.9.0.6", "实验网络中的普通主机",
)]
= 实验步骤及结果
== 任务集 1使用 Scapy 进行嗅探与伪造
=== 任务 1.1A:权限与基础嗅探
#para[
编写 `sniffer.py`,调用 Scapy `sniff()` 函数实现基础的 ICMP 数据包捕获功能。`sniff()` 函数的 `iface` 参数用于指定监听的网络接口;`filter` 参数用于指定 BPF 过滤表达式;`prn` 参数指定回调函数,对每个捕获到的数据包执行相应处理;`count` 参数指定捕获数据包的数量上限。
]
#para[
由于 `sniff()` 函数内部会创建原始套接字并直接将网卡设置为混杂模式,涉及对系统底层网络操作的直接访问,因此必须使用超级用户权限运行。通过 `sudo python3 sniffer.py` 执行程序时,程序成功创建了 `AF_PACKET` 套接字并开始监听指定的网桥接口,最终捕获到了 Host B10.9.0.6)发往 Host A10.9.0.5)的 ICMP Echo Request 数据包。不使用 sudo 运行时,内核会拒绝创建套接字并抛出 `PermissionError: WiFi device interface is required for injection, but couldn't open it` 或类似的权限不足错误。
]
#figure(
image("task1.1a_root.png", width: 85%),
caption: [Root 权限下成功捕获 Host B 发往 Host A ICMP Echo Request 包,可见源 IP、目的 IP、ICMP 类型等信息],
)
#para[
通过对比两次运行的输出结果,可以清晰地看到权限差异对原始套接字操作的影响。非特权用户无法突破内核的安全检查,这一机制既保护了系统免受恶意网络活动的侵害,也在客观上增加了网络安全的入门门槛。
]
#figure(
image("task1.1a_user.png", width: 85%),
caption: [非特权用户运行失败截图,内核拒绝创建原始套接字,程序异常退出],
)
=== 任务 1.1BBPF 过滤器应用
#para[
Scapy `sniff()` 函数支持丰富的过滤表达式。在实际网络环境中,广播域内可能同时存在数千种不同类型的流量,使用 BPF 过滤器可以在内核态完成初筛,仅将感兴趣的数据包传递到用户态程序,从而大幅提升捕获效率并降低后续处理的复杂度。
]
#para[
实验中分别测试了三种典型的过滤场景。第一种场景:指定捕获特定源主机的 TCP 流量。过滤表达式 `tcp and src host 10.9.0.6` 精确限定了协议类型为 TCP、源地址为 10.9.0.6,可用于追踪特定主机的 TCP 连接建立过程(如 Telnet 握手中的 SYN、SYN-ACK、ACK 三次握手)。第二种场景:捕获特定端口的流量。过滤表达式 `tcp port 23` 可专门针对 Telnet 流量进行捕获,这在分析远程登录行为时尤为有用。第三种场景:捕获 ARP 协议包。通过 `arp` 过滤器可以观察到整个子网的 ARP 地址解析过程,包括 ARP 请求(广播)与 ARP 响应(单播)。
]
#figure(
image("task1.1b_tcp.png", width: 85%),
caption: [精准捕获来自 10.9.0.6 TCP 端口 23 连接请求,可见 TCP 三次握手的前两个报文段],
)
#figure(
image("task1.1b_subnet.png", width: 85%),
caption: [捕获整个 10.9.0.0/24 网段的 ARP 解析过程,可见 ARP 请求的广播性质与响应帧的单播特征],
)
=== 任务 1.2:伪造 ICMP Echo Request 包
#para[
数据包伪造的核心在于利用 Scapy 的协议堆叠机制,通过重载的"/"运算符组合 IP 层与 ICMP 层构造。实验编写 `spoofer.py`,其中 `IP(src='1.2.3.4', dst='10.9.0.5')` 构造了一个源 IP 地址被设置为虚构地址 1.2.3.4、目标地址为 10.9.0.5 IP 数据报;`/ICMP()` 则在该 IP 报文之上添加了一个 ICMP 协议头,默认为 ICMP Echo Request 类型。最后调用 `send()` 函数将构造好的数据包发出。
]
#para[
需要说明的是,`send()` 函数与 `sr()`/`sr1()` 函数的区别在于:`send()` 仅负责将构造好的数据包发送出去,不等待任何响应;而 `sr1()` 在发送后会等待并返回第一个匹配的响应包。伪造场景下我们不需要等待响应(因为响应会被发送到虚假的源 IP因此使用 `send()` 即可。
]
#para[
为了验证伪造是否成功,实验同时运行 `sniffer.py` 监听来自 1.2.3.4 ICMP 包。观察捕获结果可见,嗅探器确实接收到了源地址显示为 1.2.3.4 ICMP 包,这证明 Scapy 成功绕过了操作系统的源地址验证机制,向网络注入了任意源 IP 的伪造数据包。
]
#figure(
image("task1.2_result.png", width: 85%),
caption: [验证伪造成功:嗅探器捕获到源 IP 1.2.3.4 ICMP Echo Request 包,数据包的五元组信息证实了伪造的有效性],
)
=== 任务 1.3:自定义 Traceroute 实现
#para[
Traceroute 是一种用于探测数据包转发路径的网络诊断工具。其工作原理是利用 IP 协议中的 TTLTime to Live字段。TTL 是防止数据包在网络中无限循环的计数器每经过一个路由器转发TTL 值减 1。当 TTL 降至 0 时,路由器会丢弃该数据包并向源地址发送一个 ICMP Time Exceeded 消息。
]
#para[
传统的 traceroute 程序通过逐步增加 UDP 目的端口号来探测路径:先发送 TTL=1 的数据包,收到第一个路由器的 ICMP Time Exceeded再发送 TTL=2 的数据包,依此类推,直至收到 ICMP Destination Unreachable端口不可达消息则认为到达了目标。本实验使用 Scapy 实现了类似的功能,但采用 ICMP 协议代替 UDP。实验编写 `traceroute.py`,通过 for 循环以步长 1 递增 TTL 值,每次发送一个 ICMP Echo Request 并等待响应根据响应类型判断是中间路由器Time Exceeded还是目标主机Echo Reply
]
#figure(
image("task1.3_result.png", width: 85%),
caption: [自定义 Traceroute 成功探测到从本机到 8.8.8.8 的完整转发路径,包括各跳路由器的 IP 地址与响应时间],
)
=== 任务 1.4:嗅探与伪造结合——实现 ICMP 伪造响应
#para[
这是 Scapy 部分最具综合性的任务。程序需要同时完成两件事:一是通过 `sniff()` 实时监听网络中的 ICMP Echo Request 包;二是对每个收到的请求,立即构造并发送一个 ICMP Echo Reply 包予以响应。程序逻辑的核心在于回调函数 `spoof_reply(pkt)`:首先检查收到的数据包是否为 ICMP Echo Requesttype=8如果是则提取其 ID Sequence 字段作为响应包的标识,并使用 `send()` 将伪造的 Echo Reply 发出。
]
#para[
实验分别对两类目标进行了测试。第一类目标是 `10.9.0.99`,这是一个在实验网络中存在但未运行(或不存在)的主机。实验观察到,即使 `sniff_and_spoof.py` 程序运行正常,向 10.9.0.99 发送的 Ping 请求也永远得不到任何响应。这是因为在交换式以太网环境中,发往 10.9.0.99 ARP 请求无法得到响应(该 IP 在网络中不可达),因此即使我们伪造了 Echo Reply物理层面的帧也无法被送达。第二类目标是 `1.2.3.4`,这是一个跨网段的公网 IP 地址。由于目标位于不同子网,发送方的 ARP 解析会针对网关地址进行,而非目标 IP 本身,因此伪造包能够成功通过网关转发,实验验证了跨网段通信中网关转发机制的存在。
]
#figure(
image("task1.4_result.png", width: 85%),
caption: [嗅探并回复程序对不同类型目标的处理结果:左图显示对不可达 IP Ping 失败ARP 解析受阻);右图显示对跨网段 IP 的伪造 Ping 成功(网关转发机制)],
)
== 任务集 2使用 C 语言进行嗅探与伪造
=== 任务 2.1:基于 libpcap 的嗅探
#para[
Scapy 的高级抽象不同,使用 C 语言和 libpcap 进行数据包捕获需要手动处理更多的底层细节。首先通过 `pcap_open_live()` 打开网络接口创建捕获句柄,其中参数 "br-c031fbf1a197" 指定了监听的虚拟网桥接口BUFSIZ 为缓冲区大小,第三个参数 1 表示将接口设置为混杂模式1000ms 为读取超时时间。如果打开失败,`pcap_geterr()` 可用于获取错误描述。
]
#para[
接下来使用 `pcap_compile()` BPF 过滤表达式 "icmp" 编译为内部表示,然后通过 `pcap_setfilter()` 将其安装到内核。编译过滤器时需要提供网络掩码(用于解析过滤表达式中的地址),实验中传入 `PCAP_NETMASK_UNKNOWN` 是因为我们未提前获取网络接口的地址信息。最后调用 `pcap_loop()` 进入主循环,持续捕获数据包并通过回调函数 `got_packet()` 进行处理。
]
#para[
`got_packet()` 回调函数中,需要手动解析数据包结构。标准的以太网帧头部为 14 字节,前 6 字节为目标 MAC 地址,中间 6 字节为源 MAC 地址,最后 2 字节为 EtherType 字段。对于 IP 协议包EtherType 值为 0x0800。跳过以太网头部后指针移至 IP 头部起始位置,通过强制类型转换将字节流解析为 `struct iphdr` 结构。IP 头部之后是传输层头部,对于 ICMP 协议,指针需要再偏移 IP 头部长度(`ip->ihl * 4` 字节)方可到达 ICMP 头部。
]
#figure(
image("task2.1_result.png", width: 85%),
caption: [C 语言嗅探程序成功捕获 ICMP 原始数据,控制台输出了数据包的源 IP、目的 IP ICMP 类型信息],
)
=== 任务 2.2:基于 Raw Socket 的 ICMP 包伪造
#para[
使用 C 语言构造并发送伪造的 ICMP 包,需要从零开始手动构建 IP 头和 ICMP 头的二进制结构。实验编写的 `spoofer.c` 程序首先定义了 `in_cksum()` 函数用于计算 ICMP 校验和,其实现严格遵循 RFC 1071 描述的算法:按 16 位字累加求和,循环折叠进位位,最终返回按位取反的结果。
]
#para[
在构造 ICMP 头部时,需要将 `type` 字段设为 ICMP_ECHO值为 8表示 Echo Request`code` 字段设为 0`un.echo.id` 和 `un.echo.sequence` 分别设置为测试用的标识符和序列号。特别关键的一点是:在调用校验和计算函数之前,必须将 `icmp->checksum` 字段置零。如果省略这一步而直接使用非零的 checksum 初始值,计算结果将是不正确的,导致目标主机因校验失败而丢弃该数据包。
]
#para[
IP 头部的构造同样需要仔细设置各字段:`version` 设为 4 表示 IPv4`ihl` 设为 5 表示头部长度为 5 个 32 位字(无选项字段时的最小值),`ttl` 设为 64 是常见的默认值,`protocol` 设为 `IPPROTO_ICMP` 表明载荷为 ICMP 协议。`IP_HDRINCL` 套接字选项必须设置为 1否则操作系统会自动填充 IP 头部的某些字段(如总长度、标识符、校验和等),导致我们精心构造的头部被覆盖。
]
#figure(
image("task2.2_result_c.png", width: 85%),
caption: [C 语言程序成功通过 Raw Socket 发出伪造 IP 报文,目标主机收到了源 IP 为 1.2.3.4 的 ICMP Echo Request],
)
=== 任务 2.3综合实现——C 语言版嗅探-伪造联动程序
#para[
这是实验中最具综合性和挑战性的部分。`sniff_and_spoof.c` 程序需要将 libpcap 捕获、原始套接字发送、ICMP 头解析与构造、校验和计算等多个知识点融会贯通。程序的 `got_packet()` 回调函数在收到 ICMP Echo Request 时,不仅要解析出源地址和目标地址,还要准确计算 ICMP 载荷的长度,以便正确复制原始数据。
]
#para[
伪造 Echo Reply 的过程涉及以下几个关键步骤:第一,交换 IP 源地址与目的地址,使响应包的路由方向与请求包相反;第二,将 IP 头的 TTL 重新设置为 64而非继承原始值这是良好公民行为的体现第三将 ICMP 类型从 8Echo Request改为 0Echo Reply这是最核心的修改第四将 ICMP 校验和字段清零后重新计算,因为类型字段的改变会导致原有校验和失效。完成这些修改后,调用 `send_raw_ip_packet()` 将伪造的响应包发出。
]
#para[
实验结果证明C 语言版嗅探-伪造联动程序能够实时捕获网络中的 ICMP 请求并精确回复,伪造包的 ID、Sequence、载荷数据均与原始请求保持一致目标主机收到响应后将其识别为合法的 Echo Reply 并向上层应用返回 Pong 响应。
]
#figure(
image("task2.3_result_c.png", width: 85%),
caption: [C 语言综合程序成功实现对 ICMP Echo Request 的实时伪造响应,控制台显示了拦截请求、构造响应、发送伪造包的完整过程],
)
= 实验总结
== 内容总结
#para[
通过本次实验,我系统地学习和实践了网络数据包嗅探与伪造两大核心技术,具体收获可归纳为以下几个方面:
]
#para[
Scapy 实践层面,我掌握了使用该库进行网络数据包构造、发送、捕获与解析的基本方法。通过协议堆叠操作,我能够以极高的灵活性构造任意组合的 IP/ICMP 数据包,深刻体会到高级抽象相比底层 API 在开发效率上的巨大优势。BPF 过滤器的学习让我理解了内核态数据包过滤的机制,这对于后续学习 tcpdump、Wireshark 等工具的过滤语法具有重要的迁移价值。Traceroute 的实现过程让我从原理层面理解了 TTL 机制与网络路径探测的关系,而非仅仅停留在使用现成工具的层面。
]
#para[
C 语言与系统编程层面,我通过从零构造 IP 头和 ICMP 头,深入理解了 IPv4 头部各字段的含义与作用包括版本、首部长度、总长度、标识符、TTL、协议号、校验和等。校验和算法的实现与调试让我认识到"魔鬼藏在细节中"——看似微小的疏忽(如忘记将 checksum 字段置零就可能导致整个功能的失败。libpcap 的使用让我理解了用户态网络编程与内核态网络栈之间的接口设计哲学。
]
#para[
在网络协议理论层面ARP 协议与 ICMP 协议的交互逻辑是我本次实验最大的认知收获之一。通过实验中的对比测试(对可达 IP 与不可达 IP 的伪造 Ping我直观感受到了"协议层级依赖"这一重要概念IP 层的通信建立在链路层的 ARP 解析之上任何试图绕过这一约束的行为都将面临逻辑上的不可能。这一认知对于后续理解路由、网关、NAT 等网络概念具有重要的铺垫作用。
]
== 心得感悟
#para[
本次实验历时约四个小时,是我本学期网络安全课程中投入时间最长、收获也最为丰富的一次实验。回顾整个实验过程,有以下几点心得与体会:
]
#para[
第一,理论与实践的结合是理解网络技术的最佳途径。在课堂上学习 TCP/IP 协议栈时,我对各层协议头部的认识仅停留在"考试能背出来"的层面。而通过亲手构造 IP 头和 ICMP 我第一次真正理解了每个字段的取值范围、默认值与实际含义。例如IPv4 头部的首部长度字段以 4 字节为单位,因此最小值为 5表示 20 字节无选项的头部TTL 字段每经过一个路由减 1设为 64 是考虑到现代互联网跳数通常不超过 30 跳的工程实践。
]
#para[
第二Docker 虚拟化环境为网络安全实验提供了理想的隔离空间。传统上,进行网络攻击实验需要在真实网络中进行,这不仅存在破坏生产网络的风险,也受限于实验设备的可用性。通过 Docker Compose 搭建的虚拟网络,我可以在完全不影响外部网络的情况下,自由地尝试各种嗅探与伪造技术,极大地提高了实验的灵活性和安全性。
]
#para[
第三,细致的观察与严谨的推敲是解决问题的关键。在任务 1.4 的测试中,我最初对 10.9.0.99 Ping 失败感到困惑——程序明明运行正常,伪造包也成功发出了,为什么没有任何响应?经过仔细分析 ARP 的工作原理,我才恍然大悟:问题不在于伪造技术本身,而在于链路层的地址解析根本无法完成。这是一个典型的"正确技术用在错误场景"的案例,也提醒我在实际网络分析和安全测试中,必须充分考虑各协议层之间的依赖关系。
]
#para[
展望未来本次实验为我后续学习防火墙原理、入侵检测系统IDS/IPS、以及更高级的网络安全攻防技术奠定了坚实的理论与实践基础。我期待在后续的课程中进一步探索网络安全的更多领域。
]
#pagebreak()
#set heading(numbering: none)
= 指导教员审核意见及评分:
#v(2cm)
#align(center)[
#table(
columns: (1fr),
rows: (5cm),
inset: 15pt,
align: top+left,
[]
)
]
#v(1cm)
#align(right)[
#text(size: 12pt)[签名: ]
]
// #pagebreak()
// #bibliography("ref.yml",full: true,title: "参考文献",style:"gb-7714-2015-numeric")

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

0
Sniffing_Spoofing/volumes/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,76 @@
#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
unsigned short in_cksum (unsigned short *buf, int length) {
unsigned short *w = buf;
int nleft = length;
int sum = 0;
unsigned short temp=0;
while (nleft > 1) { sum += *w++; nleft -= 2; }
if (nleft == 1) { *(u_char *)(&temp) = *(u_char *)w ; sum += temp; }
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
void send_raw_ip_packet(struct iphdr* ip) {
struct sockaddr_in dest_info;
int enable = 1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable));
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = ip->daddr;
sendto(sock, ip, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_info, sizeof(dest_info));
close(sock);
}
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
struct iphdr *ip = (struct iphdr *)(packet + 14); // Skip Ethernet header (14 bytes)
if (ip->protocol != IPPROTO_ICMP) return;
struct icmphdr *icmp = (struct icmphdr *)(packet + 14 + (ip->ihl * 4));
if (icmp->type == 8) { // Echo request
printf("Intercepted ICMP Echo Request from %s to %s\n", inet_ntoa(*(struct in_addr *)&ip->saddr), inet_ntoa(*(struct in_addr *)&ip->daddr));
char buffer[1500];
int ip_header_len = ip->ihl * 4;
int icmp_len = ntohs(ip->tot_len) - ip_header_len;
memcpy(buffer, ip, ntohs(ip->tot_len));
struct iphdr *new_ip = (struct iphdr *)buffer;
struct icmphdr *new_icmp = (struct icmphdr *)(buffer + ip_header_len);
// Swap addresses
new_ip->saddr = ip->daddr;
new_ip->daddr = ip->saddr;
new_ip->ttl = 64;
// Change to Echo Reply
new_icmp->type = 0;
new_icmp->checksum = 0;
new_icmp->checksum = in_cksum((unsigned short *)new_icmp, icmp_len);
printf("Sending spoofed ICMP Echo Reply from %s back to %s...\n", inet_ntoa(*(struct in_addr *)&new_ip->saddr), inet_ntoa(*(struct in_addr *)&new_ip->daddr));
send_raw_ip_packet(new_ip);
}
}
int main() {
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "icmp";
handle = pcap_open_live("br-c031fbf1a197", BUFSIZ, 1, 1000, errbuf);
pcap_compile(handle, &fp, filter_exp, 0, PCAP_NETMASK_UNKNOWN);
pcap_setfilter(handle, &fp);
printf("C-based Sniff-and-Spoof active...\n");
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle);
return 0;
}

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
from scapy.all import *
def spoof_reply(pkt):
# Only respond to ICMP Echo Requests
if ICMP in pkt and pkt[ICMP].type == 8:
print(f"Intercepted ICMP Echo Request from {pkt[IP].src} to {pkt[IP].dst}")
# Build spoofed ICMP Echo Reply
ip = IP(src=pkt[IP].dst, dst=pkt[IP].src)
icmp = ICMP(type=0, id=pkt[ICMP].id, seq=pkt[ICMP].seq)
# Add payload if present
payload = pkt[Raw].load if Raw in pkt else b""
new_pkt = ip/icmp/payload
print(f"Sending spoofed reply from {pkt[IP].dst} to {pkt[IP].src}...")
send(new_pkt, verbose=0)
print("Sniff-and-Spoof active on br-c031fbf1a197...")
# Filter: icmp echo-request
sniff(iface='br-c031fbf1a197', filter='icmp and icmp[icmptype]=8', prn=spoof_reply)

Binary file not shown.

View File

@@ -0,0 +1,39 @@
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
printf("Got a packet\n");
}
int main() {
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "icmp";
bpf_u_int32 net;
// Step 1: Open pcap session on the interface
handle = pcap_open_live("br-c031fbf1a197", BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device: %s\n", errbuf);
return 2;
}
// Step 2: Compile filter_exp into BPF code
if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 2;
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 2;
}
// Step 3: Capture packets
printf("Sniffing ICMP packets using C and libpcap...\n");
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle);
return 0;
}

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python3
from scapy.all import *
def print_pkt(pkt):
pkt.show()
print("Sniffing ICMP packets from spoofed IP 1.2.3.4...")
pkt = sniff(iface='br-c031fbf1a197', filter='icmp and src host 1.2.3.4', prn=print_pkt, count=1)

Binary file not shown.

View File

@@ -0,0 +1,81 @@
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
/* Checksum calculation function */
unsigned short in_cksum (unsigned short *buf, int length) {
unsigned short *w = buf;
int nleft = length;
int sum = 0;
unsigned short temp=0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&temp) = *(u_char *)w ;
sum += temp;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
void send_raw_ip_packet(struct iphdr* ip) {
struct sockaddr_in dest_info;
int enable = 1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock < 0) {
perror("Socket creation failed");
return;
}
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable));
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = ip->daddr;
if (sendto(sock, ip, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_info, sizeof(dest_info)) < 0) {
perror("Sendto failed");
} else {
printf("Spoofed ICMP packet sent.\n");
}
close(sock);
}
int main() {
char buffer[1500];
memset(buffer, 0, 1500);
struct iphdr *ip = (struct iphdr *) buffer;
struct icmphdr *icmp = (struct icmphdr *) (buffer + sizeof(struct iphdr));
// Construct ICMP Header
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->un.echo.id = htons(1234);
icmp->un.echo.sequence = htons(1);
icmp->checksum = 0;
icmp->checksum = in_cksum((unsigned short *)icmp, sizeof(struct icmphdr));
// Construct IP Header
ip->version = 4;
ip->ihl = 5;
ip->ttl = 64;
ip->saddr = inet_addr("1.2.3.4");
ip->daddr = inet_addr("10.9.0.5");
ip->protocol = IPPROTO_ICMP;
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr));
send_raw_ip_packet(ip);
return 0;
}

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
from scapy.all import *
print("Spoofing ICMP echo request from 1.2.3.4 to 10.9.0.5...")
a = IP()
a.src = '1.2.3.4'
a.dst = '10.9.0.5'
b = ICMP()
p = a/b
send(p)

Binary file not shown.

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python3
from scapy.all import *
target = "8.8.8.8"
print(f"Traceroute to {target}...")
for i in range(1, 31):
pkt = IP(dst=target, ttl=i) / ICMP()
reply = sr1(pkt, verbose=0, timeout=1)
if reply is None:
print(f"{i}: * * *")
elif reply.type == 3: # Destination unreachable
print(f"{i}: {reply.src} (Unreachable)")
break
else:
print(f"{i}: {reply.src}")
if reply.src == target:
print("Reached target!")
break