实验一 ICMP重定向
ICMP重定向是路由器向主机提供实时的路由信息。
- 当一个主机收到ICMP重定向信息的时候,它就会根据这个信息来更新自己的路由表
- 由于没有必要的合法性检查,如果一个黑客想要被攻击的主机修改路由表,黑客就会发送ICMP重定向信息给被攻击的主机
- 思路:攻击者冒充网关,发送重定向包,要求受害者将自己的网关修改为攻击者制定的
gw
地址,如果修改之后的网关可实现中间人攻击和DOS,如果是一个随机IP,那么可能导致DOS攻击.
1 正常重定向
一般情况下:
- 主机将送往远程网络的数据先发送给路由器。
- 路由器尽自己最大的努力转发数据
- 如果已知的路由器并不是去往目的地的最优选择,希望让用户选择其它路由器来转发数据
- 路由器会发送ICMP重定向包告诉对方,不要将数据源发送给自己,而应该发送给其他的路由器
正常情况下的ICMP重定向:
- 情况一: 当路由器从某个接口收到了数据包之后,需要将数据包从同一个接口发往目的地,也就是路由器收到数据包的接口正是去往目的地的出口时,会像源发送ICMP重定向,通知对方,直接将数据包发送到下一跳。
- 情况二: 当数据包的源IP和自己转发时的下一跳IP是同一个网段时,会向源发送ICMP重定向,通知对方,直接将数据包发送到下一跳。
2 libcap使用
分为5步:
设置嗅探硬件设备(在Linux中一般是eth0) , 初始化pcap
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, char *argv[]){
char *dev; //device to sniff on
char errbuf[PCAP_ERRBUF_SIZE]; // Error string
//第一步
dev = pcap_lookupdev(errbuf);
if(dev == NULL){
// if dev doesn't exist,print errlog,which in errbuf
fprintf(stderr,"Couldn't find the default devices: %s\n",errbuf);
return (2);
}
printf("Device: %s\n", dev);
return(0);
}打开设备进行嗅探
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,char *ebuf)
参数 含义 char *device
所使用的设备,网卡 int snaplen
pcap要捕获的最大字节数 int promisc
设定为true,接口带入混杂模式(?) int to_ms
读取超时,以毫秒为单位,0表示没有time out char *ebuf
出错信息 代码:
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
int main(int argc, char *argv[]){
char *dev; //device to sniff on
char errbuf[PCAP_ERRBUF_SIZE]; // Error string
pcap_t *handle; // Session handle
// 第一步
dev = pcap_lookupdev(errbuf);
if(dev == NULL){
// if dev doesn't exist,print errlog,which in errbuf
fprintf(stderr,"Couldn't find the default devices: %s\n",errbuf);
return (2);
}
printf("Devices:%s\n",dev);
//第二步
/**
* pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,char *ebuf)
*
* @param device interface
* @param snaplen MaxLength pcap will sniff
* @param promisc true:interface into promiscuous mode
* @param to_ms read time out in milliseconds
* @param ebuf errbuf
* @return handle
*/
handle = handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return(2);
}
}过滤流量,我们写的嗅探器只对特定的流量感兴趣,需要使用
pcap_compile()
和pcap_setfilter()
为什么不用
if else
过滤?pcap过滤器效率高,它使用BPF过滤器
通过让BPF驱动程序直接执行可以减少很多步骤。过滤表达式用char
数组保存,规则同tcpdump
。编译使用函数
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
参数 含义 pcap_t *p
Session handle struct bpf_program *fp
过滤器的编译版本的位置 char *str
过滤表达式 int optimize
表达式是否应该优化 bpf_u_int32 netmask
过滤器适用的网络的网络掩码 编译完表达式之后应用它使用函数
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
参数 含义 pcap_t *p
Session handle struct bpf_program *fp
过滤器的编译版本的位置 代码:
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
int main(int argc, char *argv[]){
char *dev; //device to sniff on
char errbuf[PCAP_ERRBUF_SIZE]; // Error string
pcap_t *handle; // Session handle
struct bpf_program fp; // compiled expression
char filter_exp[] = "port 23"; // filter expression
bpf_u_int32 mask; // netmask
bpf_u_int32 net; // IP
// 第一步
dev = pcap_lookupdev(errbuf);
if(dev == NULL){
// if dev doesn't exist,print errlog,which in errbuf
fprintf(stderr,"Couldn't find the default devices: %s\n",errbuf);
return (2);
}
printf("Devices:%s\n",dev);
/*
* int pcap_lookupnet(dev, &net, &mask, errbuf)
*
* @param dev interface
* @param net IP
* @param mask netmask
* @param errbuf error log
* @return IP & netmask
*/
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
fprintf(stderr, "Can't get netmask for device %s\n", dev);
net = 0;
mask = 0;
}
//第二步
/**
* pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,char *ebuf)
*
* @param device interface
* @param snaplen MaxLength pcap will sniff
* @param promisc true:interface into promiscuous mode
* @param to_ms read time out in milliseconds
* @param ebuf errbuf
* @return handle
*/
handle = handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return(2);
}
//第三步
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);
}
}补充
pcap_lookupnet(dev, &net, &mask, errbuf)
参数 含义 char *dev
设备 bpf_u_int32 *net
IP地址 bpf_u_int32 *mask
子网掩码 char *ebuf
出错信息 使用
pcap_loop()
循环嗅探int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
参数 含义 pcap_t *p
handle int cnt
应该嗅探多少个数据包(负值表示一直抓下去,直到出错) pcap_handler callback
回调函数,如何处理抓到的包 u_char *user
一般设定为NULL 代码:
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
int main(int argc, char *argv[]){
char *dev; //device to sniff on
char errbuf[PCAP_ERRBUF_SIZE]; // Error string
pcap_t *handle; // Session handle
struct bpf_program fp; // compiled expression
char filter_exp[] = "port 23"; // filter expression
bpf_u_int32 mask; // netmask
bpf_u_int32 net; // IP
// 第一步
dev = pcap_lookupdev(errbuf);
if(dev == NULL){
// if dev doesn't exist,print errlog,which in errbuf
fprintf(stderr,"Couldn't find the default devices: %s\n",errbuf);
return (2);
}
printf("Devices:%s\n",dev);
/*
* int pcap_lookupnet(dev, &net, &mask, errbuf)
*
* @param dev interface
* @param net IP
* @param mask netmask
* @param errbuf error log
* @return IP & netmask
*/
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
fprintf(stderr, "Can't get netmask for device %s\n", dev);
net = 0;
mask = 0;
}
//第二步
/**
* pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,char *ebuf)
*
* @param device interface
* @param snaplen MaxLength pcap will sniff
* @param promisc true:interface into promiscuous mode
* @param to_ms read time out in milliseconds
* @param ebuf errbuf
* @return handle
*/
handle = handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return(2);
}
//第三步
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);
}
//第四步
pcap_loop(handle, -1, getPacket, NULL);
return 0;
}回调函数原型:
1
2
3
4
5
6/**
* @param u_char *args 对应于pacploop()的最后一个参数
* @param const struct pcap_pkthdr *header pcap的的头部信息,它包含有关何时嗅探数据包的信息,它的大小等
* @param const u_char *packet 另外一个u_char指针,它指向包含由pcap_loop()嗅探的整个数据包的一大块数据的第一个字节。
*/
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
3 IP报文和ICMP报文
3.1 IP 头部信息
1 | /* IP header */ |
- 版本号:4个字节 ipv4&ipv6
- 首部长度:首部长度说明首部有多少32位字(4字节),最大是15*4 = 60
- 区分服务:占8bit,最初被定义为服务类型字段,实际上并未使用,
- 显示拥塞通告:一般未使用
- 全长:16位,IP报文的总长度
- 标识符:16位,这个字段主要被用来唯一地标识一个报文的所有分片,因为分片不一定按序到达,所以在重组时需要知道分片所属的报文。每产生一个数据报,计数器加1,并赋值给此字段
- 标志:
- 这个3位字段用于控制和识别分片,它们是:
- 位0:保留,必须为0;
- 位1:禁止分片(Don’t Fragment,DF),当DF=0时才允许分片;
- 位2:更多分片(More Fragment,MF),MF=1代表后面还有分片,MF=0 代表已经是最后一个分片。
- 如果DF标志被设置为1,但路由要求必须分片报文,此报文会被丢弃。这个标志可被用于发往没有能力组装分片的主机。
- 这个3位字段用于控制和识别分片,它们是:
- 分片偏移 :这个13位字段指明了每个分片相对于原始报文开头的偏移量,以8字节作单位。
- 存活时间:这个8位字段避免报文在互联网中永远存在(例如陷入路由环路)。存活时间以秒为单位,但小于一秒的时间均向上取整到一秒。在现实中,这实际上成了一个跳数计数器:报文经过的每个路由器都将此字段减1,当此字段等于0时,报文不再向下一跳传送并被丢弃,最大值是255。常规地,一份ICMP报文被发回报文发送端说明其发送的报文已被丢弃。
- 协议,占8个字节,这个字段定义了该报文数据区使用的协议。
- 首部校验和:16位,只对首部查错,不包括数据部分。
- 源地址:ipv4地址
- 目的地址
- 选项:附加的首部字段可能跟在目的地址之后,但这并不被经常使用,从1到40个字节不等。
3.2 ICMP头部信息
1 | // icmp 重定向报文头 |
ICMP报头从IP保温的第160位开始,IP首部20字节之后。
- Type:ICMP的类型,标志生成的错误报文
- Code:进一步划分ICMP的类型,该字段用来查找产生错误的原因.;例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。
- Checksum:校验码部分,这个字段包含有从ICMP报头和数据部分计算得来的,用于检查错误的数据,其中此校验码字段的值视为0。
- ID:这个字段包含了ID值,在Echo Reply类型的消息中要返回这个字段。
- Sequence:这个字段包含一个序号,同样要在Echo Reply类型的消息中要返回这个字段。
重定向报文:
4 Socket编程
4.1 Socket类型
- *流套接字(Stream Socket) - * 在网络环境中保证交付,通过套接字发送三个项目ABC,他们会以相同的顺序ABC到达,TCP传输
- *数据包套接字(Datagram Socket) - * 无法保证交付,无连接,使用UDP
- *原始套接字(Raw Socket) - * 使用原始套接字,用户可以访问底层通信协议
- *顺序数据包套接字(Sequenced Packet Socket) - * 仅仅作为网络系统NS套接字抽象的一部分提供
Socket使用一个四元组<源IP,源port, 目的IP, 目的port>来描述一个网络连接,使用Socket的时候也需要数据结构描述。
1 | /** |
使用两个数据结构来表示地址原因是socket设计之初是准备支持多个地址协议的,不同的地址协议由不同的地址构造。如IPV4的sockaddr_in
,IPV6的sockaddr_in6
,AF_UNIX 的sockaddr_un
sockaddr
是对这些地址的上一层的抽象,在将使用的值复制给sockaddr_in
之后,通过强制类型转换就可以转换为sockaddr
.
4.2 Socket通信流程
4.2.1 TCP通信
客户端:建立Socket;建立连接,发送连接请求;发送/接收数据;关闭连接
服务器端:建立Socket;绑定本地地址。接受请求,接收/发送数据;关闭连接
4.2.2 UDP通信
省去了监听过程,不用建立连接
4.2.3 C/S代码实例
客户端代码:
1 |
|
服务器端代码:
1 |
|
4.2.4 raw socket
一个Ping程序:
1 |
|
5 实验代码
实验代码:
1 |
|