Exploit XJTU_WLAN(二)

前言

俺在前文中剖析了XJTU_WLAN这个WiFi的一些漏洞。本文接着前文,进一步优化,实现对所有流量的转发,而非单端口的代理。本文旨在实战,俺也不再浪费笔墨来讲述TAP/TUN驱动的原理。

炊具一览

Rasbery Pi一枚(自购无线网卡)
ShadowVPN一枚
202.117.0.0/18或者115.154.0.0/16的主机一枚

菜谱分析

解决方案如下图所示,Raspberry Pi上面有两张物理网卡,分别是eth0(Pi自带)和wlan0(自购)。
需要上网的设备为左边的My Mac,其中蓝色的块为数据包,首先数据包由My Mac发送到tun0上,tun0再将该数据包打上紫色的UDP53头,通过wlan0将数据送出。wlan0连接到XJTU_WLAN热点,该热点对UDP53的流量不阻拦,正确的将该数据包送向俺们安放在外面的主机,俺们的主机拆包,并将该数据送向Internet,反向流量同理。如此,便完成了一记完美的Bypass。

开始做菜XJTU_WLAN

Raspberry Pi 过水

首先Rasberry Pi上配置Shadowvpn一枚,编译后进行配置,如果默认情况下,编辑/etc/shadownvpn/client.conf文件,特别注意一下server=x.x.x.x和port=53配置。其他按需求配置即可。

配置/etc/shadownvpn/client_up.sh文件,请特别注意注释掉所有iptables与route相关的配置,否则会造成严重后果。因为shadownvpn会在进程启动后直接调用该脚本进行iptables与route的配置,由于特殊的网络环境限制,俺们需要手动的修改iptables与route配置。

 202.117.16.x过油

直接默认的方式安装shadowvpn就好了,自行配置/etc/shadowvpn/server.conf的server与port字段,使其和raspberry pi和client.conf字段一致。

实际上,注意到,shadownvpn的作者在iptables的nat配置时,显得非常暴力。更准确的写法应该是source字段写为客户端的ip地址就可以了。但是实际上,由于配置服务端时并不知晓客户端的IP地址,所以作者在这里采取了折衷的做法。
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  anywhere             anywhere             /* eth0 (shadowvpn) */

Pi与Host一锅闷 

经过上述的过程,想必大家对shadowvpn这个小程序已经有所了解,其实shadownvpn利用了kernel 2.2.x的tun/tap特性,俺这边文章还是以实战为主,便不再对该特性做深入的解释。

配置俺的Mac电脑的IP地址与网关,注意到网关为Pi的IP地址。其他按照喜好添加。

登陆202.117.x.x的主机,开启shadownvpn -c /etc/shadownvpn/server.conf,注意更改iptables的规则udp 53接受,俺之前就因为这个原因,导致装逼失败。

配置好pi的eth0的IP地址,利用SSH登陆,首先iwconfig wlan0 "XJTU_WLAN",使得wlan0接口连接XJTU_WLAN热点,然后dhcpclient wlan0,获取IP地址等信息。如果此时DNS已经能够顺利解析,其实你已经成功了一大半。继续,直接执行shadownvpn -c /etc/shadowvpn/client.conf,开启shadownvpn客户端进程,现在注意到ifconfig之中出现了tun0接口,并且ip为10.7.0.2,接着ping 10.7.0.1,试试看是否已经成功搭上梯子。

接着,配置路由规则与NAT。首先ip route del default删去默认的转发规则,加上ip route add default via 10.7.0.1,接着加上通向目的主机的规则,ip route add 202.117.x.x via 49.208.0.1。OK,搞完这两步,应该你的Pi能够正常上网了,试试百度能否打得开(哎,百度在俺心中沦为了,逃。。。)

最后一步,开启NAT,使得接入小Pi的主机也能正常浏览网页,iptables -t nat -A POSTROUTING -s 192.168.x.x -j MASQURADE -o tun0,打完收工。

试试你的小电脑能够乖乖的上网了?

总结

好了,俺记得有同僚问我,难道你交网络中心的人吃素的?俺的回答:当然不是。好吧,为了封堵这个东西,去换掉所有天花板上的AP,你说划算不划算?

当然,俺如写流水账般洋洋洒洒才手写了这么几行,实际上,在本文背后的技术细节还是蛮多的,譬如TUN/TAP,Netfilter模块等,都不是看了本文就能了解的,特别是netfilter的链/表的运行机制与iptables的配置,其实俺还是弄了好久才搞懂咧!

谢谢观看!

由MAC地址谈到网卡驱动

近日,有个需求是,通过u-boot进行PXE启动,采用的硬件为Xilinx的Zedboard.

再这里,碰到个严重的问题,mac地址冲突。对于绝大多数的嵌入式系统而言,其mac地址冲突是个经常发生的问题。如果mac地址冲突,则会造成2层交换机的工作不正常。表现在外面的现象则是,两台冲突机器同时ping包,同时,只有一台机器能够正常通信。

这种不正常现象是做高层开发的难以遇见的情况。本文就不再分析mac地址冲突对网络造成的影响,因为不同的硬件交换机对mac地址冲突所采取的处理方式不同。

OK,返回正题,让俺们深入mac地址,谈谈内核及uboot中的内核驱动。

首先,提一个问题,mac地址是否可以修改?绝大多数以教科书为范本的“学霸”的答案是“不可以”,不少喜欢折腾的同学的答案是“可以”。但是俺的答案是“不一定可以”。

为什么不可以,则要从ifconfig这个程序说起。大家可以轻松地找到如何修改MAC地址。

sudo ifconfig eth0 hw ether 08:00:00:00:00:01

那么问题来了,这个指令到底深入到内核去,它干了啥?深入到内核,则必然联系到驱动这个层面,当然在Unix/Linux世界里面,通常是module来替代。

一个不干活的驱动

首先,俺们来看一个简单的rt8139的驱动
其实准确地说,下面根本就不是驱动代码。

如上的代码可以通过cc -I/usr/src/linux-2.4/include/ -Wall -c rtl8139.c的指令编译后,通过insmod进入内核,便能够通过ifconfig -a看到该设备。当然,代码太古老,以至于现在难以编译通过。不过通过如上的代码可以看到网络驱动的大概。

关注46行,
register_netdev
将 rtl8139 注册为网络设备,如果成功,则网络设备就注册成功了,这时,利用ifconfig就能看到rtl8139这个设备了。

关注40行,就能找到这个设备。40行init里面赋,值为rtl8139_init函数,该函数接受一个net_device作为参数,

关注41行,注意到驱动编写者需要做的,直接利用内核传入的net_device勾住初始化代码。将net_device结构内的open,stop,hard_start_xmit函数hook在rtl8139_open,rtl8139_release,rtl8139_xmit上面。

其中open/rtl8139_open函数会在用户调用ifconfig rtl8139 up时被调用。stop/rtl8139_release会在用户调用ifconfig rtl8139 down时调用。而hard_start_xmit/rtl8139_xmit会在协议栈向驱动注入数据时,请求发送时使用,例如socket接口中的write函数。

当然,上面的代码实际上并非rtl8139的驱动的代码,因为实际上,所有的hook的函数,实际上除了打印一些信息以外什么也没干。

看完以上,诸位似乎已经大体地了解到什么是一个网卡驱动程序了。不过现实的网卡驱动程序可没有这么简单。首先,聪明的读者似乎已经注意到,俺的网卡驱动里面似乎并没有涉及到数据的接收过程,其次,俺的网卡驱动里面也没有任何的MAC地址的配置信息。

解剖Realtek 8139驱动

接下来,俺利用Kernel 4.3的Realtek 8139的驱动程序8139too.c来讲解MAC地址与驱动的关系。代码由于过长,就不再贴出来,可以自行点击上方的超链接下载。(注:所有的代码行均缘于8139too.c文件,本文限于篇幅,只粘贴重要代码及其函数)

烦人的MAC地址

关注2238行开始的函数rtl8139_set_mac_address,该函数接受两个参数,第一个参数是net_device结构体,用以描述该网络设备,第二个参数是个空指针,用于传送用户空间传来的MAC地址值。
可以看到验证完以太网地址后,直接利用
memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
将用户的值设置进入net_device对象的dev_addr内部,这一步就直接告诉内核,用户想要改变网卡设备的地址。

在配置完内核维护的网络设备的地址后,立即看到一把spinlock加锁,然后利用各种宏开始写寄存器。之后进行锁的释放。

实际上,写寄存器的主要目的在于使物理的MAC芯片能够过滤非本机的数据包,而不在于发送数据包的校验。

因此,在解析完此段代码后,俺想大部分读者已经能理解,为什么俺在文章开始的时候提到了MAC地址不一定可以被修改的原因。简单地说,如果驱动程序直接就return了,实际上是达不到修改MAC地址的功能的。

那么肯定会有读者会问,既然MAC地址能够被写入,那俺们教科书上所谓的与网卡绑定的MAC地址又是在哪里呢?

这个问题的答案在rtl8139_init_one函数中1004-1007行。循环内部不断地写入dev->dev_addr这个变量,而这个变量正是内核用以保存MAC地址的数据结构。

解锁收包逻辑

接下来,俺们来了解下数据接收的功能。即网卡驱动怎么拿数据,再怎么将数据递交个上层。这一块由于linux内核的NAPI使得问题分析变得复杂。

让俺们定位到1333行,rtl8139_open中,调用了request_irq,注册中断处理函数。
retval = request_irq(irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);
将中断的函数的第二个参数为rtl8139_interrupt,即中断处理函数。

继续追踪rtl8139_interrupt函数,2197-2202行。

这几行核心代码用于收取数据包。其中8139的驱动中,直接调用__napi_schedule函数进行处理。而这个__napi_schedule函数所做的,就是将将本数据包的处理交给NAPI系统。等待NAPI系统进行统一调度。具体的细节可直接看考Linux Foudation的关于NAPI的这篇文章

总之上,等待调度时间到了,  NAPI会调用某个hook的poll函数,8139通过1013行的这段代码进行hook。
netif_napi_add(dev, &tp->napi, rtl8139_poll, 64);
那么真正的处理函数便交给了rtl8139_poll函数。

该函数中核心在于rtl8139_rx函数的调用,通过调用这个函数,直接进入最后的skb打包并递交上层的流程。

rtl8139_rx函数大部分工作是从从环形队列中取出数据包,并打包上传。
定位到rtl8139_rx的2038-2067行。就能找到最重要的两个调用:
skb->protocol = eth_type_trans (skb, dev);
netif_receive_skb (skb);
分别解析数据包,并调用上层IP或ARP逻辑。
至此,驱动所有工作就完成了。接下来正式进入TCP/IP协议栈进行处理。这里就不在本文的涉及范围。

总结:
俺在分析网卡驱动逻辑的时候,被NAPI系统给折腾得不轻,目前的Linux已经不像是书本的设计了,本来interrupt函数中可以直接进行skb打包,但是NAPI的介入,使得调用逻辑不再连贯。而绝大多数的针对于网卡驱动的分析是建立在旧驱动的基础上。因此,俺写了本篇文章,希望大家避免走弯路。

最后,贴出我所参考的4.3内核中的源代码文件8139too.c,留以备份。
谢谢观看。