ip_route_input_slow,另一个是ip_route_output_slow,其中前一个函数的限制在: if (ipv4_is_lbcast(daddr) || ipv4_is_zeronet(daddr) || ipv4_is_loopback(daddr)) goto martian_destination;而后一个函数限制在其调用的__mkroute_output中:
if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK)) return -EINVAL;我们到底应该怎么绕过去呢?如今我们知道,NAT逻辑已经将目标地址改成了127.0.0.1,而源地址保持不变,这种数据包在进入的时候会被ip_route_input_slow拦截并且由于源地址非loopback而目标为loopback而丢弃,丢弃的时候注意还没有查找路由呢?现在我们假设这个包已经过去了,那么返回包能不能顺利通过ip_route_output_slow呢?答案无疑也是否定的,由于标准路由是基于目标地址的,对于返回包的目标地址其实就是正向包的源地址,路由查找无疑是可以通过的,但是路由前由于还不知道目标设备,因此不能简单地丢弃,只有到了路由之后确定目标设备非loopback之后才能丢弃。
struct rtable *dummy_rth = (struct rtable*)kmalloc(sizeof(struct rtable), GFP_ATOMIC);注意,如果是准备在insmod的时候,也就是调用module的init时生成这个rtable的话,大可不必使用GFP_ATOMIC标志。
struct dst_entry dst = dummy_rth->u.dst; memset(&dummy_rth->u.dst, 0, sizeof(struct dst_entry)); dummy_rth->u.dst.ops = NULL; dummy_rth->u.dst.path = &dst; dummy_rth->u.dst.flags= DST_HOST; dummy_rth->u.dst.flags |= DST_NOXFRM; dummy_rth->u.dst.flags |= DST_NOPOLICY; dummy_rth->u.dst.dev = init_net.loopback_dev; dummy_rth->rt_iif = init_net.loopback_dev->ifindex; dummy_rth->rt_type = RTN_LOCAL; dummy_rth->u.dst.input = (int (*)(struct sk_buff*))0xffffffff812734eb; //ip_local_deliver的地址 atomic_set(&dummy_rth->u.dst.__refcnt, 1);接下来,我们需要在每一个被DNAT到loopback的数据包上应用上述的rtable,那么在哪里应用呢?很显然是在PREROUTING这个HOOK点的NAT之后了,也就是nf_nat_in钩子函数里面:
static unsigned int nf_nat_in(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { unsigned int ret; __be32 daddr = ip_hdr(skb)->daddr; ret = nf_nat_fn(hooknum, skb, in, out, okfn); if (ret != NF_DROP && ret != NF_STOLEN && daddr != ip_hdr(skb)->daddr) skb_dst_drop(skb); if (daddr != ip_hdr(skb)->daddr && ipv4_is_loopback(ip_hdr(skb)->daddr)) { atomic_inc(&dummy_rth->u.dst.__refcnt); //增加一个引用计数。 skb_dst_set(skb, &dummy_rth->u.dst); //将rtable的dst_entry附着到skb上。 } return ret; }以上就完成了正向包的路由前火星地址检查,注意我们并不打算将上述的dummy_rth插入到系统的路由cache哈希表中,大多数的字段也没有填充,这是因为我们从来不想把它用作什么路由cache,也从不想将其删除,也不想被常规的管理,我们只是在NAT模块还在的时候使用它将数据包路由到本地传输层或者以上而已。
if (daddr != ip_hdr(skb)->daddr && ipv4_is_loopback(ip_hdr(skb)->daddr)) { struct rtable *rev_dummy = dst_alloc(&ipv4_dst_ops); int err; unsigned hash; atomic_inc(&dummy_rth->u.dst.__refcnt); //增加一个引用计数。 skb_dst_set(skb, &dummy_rth->u.dst); //将rtable的dst_entry附着到skb上。 //以下构造返回包使用的rtable,注意是反向的,源地址就是目标地址,目标地址成了源地址 atomic_set(&rev_dummy->u.dst.__refcnt, 1); rev_dummy->u.dst.flags= DST_HOST; rev_dummy->u.dst.flags |= DST_NOXFRM; rev_dummy->u.dst.flags |= DST_NOPOLICY; rev_dummy->fl.fl4_dst = ip_hdr(skb)->saddr; rev_dummy->fl.fl4_tos = (u32)(RT_TOS(ip_hdr(skb)->tos) & (IPTOS_RT_MASK | RTO_ONLINK));; rev_dummy->fl.fl4_src = ip_hdr(skb)->daddr; ... rev_dummy->rt_dst = ip_hdr(skb)->saddr; rev_dummy->rt_src = ip_hdr(skb)->daddr; hash = rt_hash(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr, 0, rt_genid(dev_net(skb->dev))); err = rt_intern_hash(hash, rev_dummy, rev_dummy, NULL); }如此一来也就成了,具体的测试方式很简单,那就是写一个侦听127.0.0.1:abc的TCP程序,重新编译并加载nf_nat.ko以及iptable_nat.ko模块,然后设置以下的规则:
即可。
另外如果对Windows上做hack比较熟悉的话,还有一种更加高效的方式来处理上述第2点,那就是“二进制重置”,通过内核符号表找到__mkroute_output的地址,并且通过特征码找到:
if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))