一、写在前面
啊,好吧。我又回归了。这几个月没动静是因为不知道写什么好(笑
事情的起因是,我的朋友因为流量快要用完了,问我有没有电脑开热点给手机上网的办法。
我和朋友是一起买的校园网账号,一个账号只能两个设备连接。
最开始的时候,只是简单的在电脑运行一个代理服务,让手机连上电脑的代理服务即可。
直到后面,朋友买了一个单网口小主机,研究之路才正式开始。。。
二、账号认证
好吧,既然是小主机,那么配置肯定不高。
赛扬的CPU,4G的内存,只有Linux愿意接受它了。
我选择的是Fedora 42,不得不说,Fedora除了Firewalld和SELinux哪都好,不愧是Linux之父钦定的系统,哈哈哈。
什么?你问我为什么不是OpenWRT之类的东西?其实我们当时的方案只有代理服务,选择一个服务器发行版再好不过了。
Fedora的服务器版本是没有桌面的,没有桌面我们怎么打开浏览器进行网页认证呢?
我开了抓包软件,在我的电脑上走了一遍认证流程。发现前端传递给后端的参数里有一个叫pass的参数。
这个pass参数每次认证时,前半段会变化,但是后半段不变。而且看上去也不像某种类型的hash,我目前的判断是,这个后半段就是用户密码。密码改了后半段就会变化,但是前半段我就不知道是什么了。我甚至打开了前端的js,但是看不懂,而且还被混淆了!
不过值得注意的是,每次认证的历史pass参数都是一直有效的。即使后面重复认证产生了新的pass参数,这就给了我一个机会,我写了几个curl发包命令,尝试重新发包认证,居然成功了。
嗯,好吧,我还是不要往密码方向死磕了,能认证就行(笑
三、代理服务
这个方案很简单,就是服务器运行一个代理服务。
在服务端配置运行后,客户端打开软件,启动TUN模式就行。
嗯,这里我就不多说了,懂点网络的应该知道我的意思,搭建教程稍微搜索一下就行。
四、nftables
目前我们使用的方案,nftables真是一个好东西啊!
起因是,我在一个群里聊天时,正好讨论到校园网这个话题。
一位群友说,他是改MAC地址解决的。
我躺在床上,想了一下。改MAC地址是什么鬼?所有设备改成同一个MAC地址?!
后来我才知道,一些路由器有“MAC地址克隆”这个功能,就是让路由器克隆一个已经认证的电脑的MAC,让上游认为路由器还是那台电脑。
呃,这个方案我没怎么听说过,我还是问了Gemini才知道的。也不知道是不是真的有这种功能的路由器,如果不对还请见谅。
我们继续,看到这种方案之后,我突然有了一个灵感。能不能给我们的小主机一个静态IP,然后我们的IP和主机在同一个网段,小主机为网关。然后我自己写NAT规则。
起床之后,我马上打开虚拟机,开始按照我的想法来。
首先在桥接模式下,通过ip addr给小主机一个静态IP(192.168.233.1),即使这个网卡目前是DHCP获取上游地址。
因为如果我们手动设置上游的静态IP就不能上网。
添加之后,另一台Windows 10设置一个静态IP,和小主机同一个网段。
发现可以Ping通,然后让GPT写了规则,让我们的233网段作为NAT地址,然后通过小主机获取到的上游IP出去。
搭建简单的NAT之后,我测试了一下。当然,不能上网。
因为是简单的NAT,和普通的路由器没有区别。我把tcpdump的结果发给了GPT,GPT看了几个抓包结果,说可能是上游有限制,而且看上去是校园网或者什么公共网络的环境。
我就和GPT说对的,我确实在校园网,我要绕过这个设备限制。
GPT居然没拒绝我,说校园网一般会检测TCP指纹、UA、TTL什么的,更有甚至还有DPI检测。
我当时就想,都说DPI非常烧钱,一个大专不可能开这种东西吧。于是我有看了看关于TCP指纹和UA的解决方案,我还看了眼GPT的聊天记录,它说可以试一下把TTL设置为64的规则加进去规避一下基于TTL的检测。
好吧,死马当活马医吧。
我加了规则,刷新了一下网页,成功了。。。
我还是太高看我的学校了,原来这么简单,只有一个TTL检测,无语了。
我把nftables的规则保存搬到了小主机上,在物理机上也成功上网,只需要手动配置静态IP就行。

这个就是我们现在的架构,首先客户端和路由器在同一个静态网段,网关为路由器。
因为在同一个光猫下,客户端到路由器的数据不需要路由,我们利用光猫转发了一下。然后路由器把客户端的数据伪装成是它的数据包(修改源地址、改TTL),上游以为只有路由器在上网。
至于代理,嗯,Gemini说是代理服务端把数据包重建了,也能达到目的。不过电脑一直挂着TUN确实麻烦。
还有MAC方案,嗯,这几天我可能会看看。因为有个工具叫macchanger,我看看能不能达到目的。
差点忘了,我这里贴一下我的nftables规则。
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "eno1" ip saddr 192.168.233.0-192.168.234.255 masquerade
oifname "brvm" ip daddr 192.168.234.11 masquerade
oifname "eno1" ip saddr 192.168.234.0/24 masquerade
}
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
tcp dport 2001 dnat to 192.168.234.11:21118
tcp dport 2001 dnat to 192.168.234.11:21118
}
}
table ip mangle {
chain postrouting {
type filter hook postrouting priority mangle; policy accept;
oifname "eno1" ip saddr 192.168.233.0-192.168.234.255 ip ttl set 64
}
}
table inet filter {
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "brvm" oifname "eno1" accept
iifname "eno1" oifname "brvm" ct state established,related accept
iifname "brvm" oifname "eno1" ip daddr 192.168.233.0/24 accept
iifname "eno1" oifname "brvm" ip saddr 192.168.233.0/24 ct state established,related accept
iifname "eno1" oifname "eno1" ip saddr 192.168.233.0/24 ip daddr != 192.168.233.0/24 accept
iifname "eno1" oifname "brvm" ip saddr 192.168.233.0/24 accept
iifname "eno1" oifname "eno1" ip daddr 8.8.8.8 udp dport 53 accept
iifname "brvm" oifname "eno1" ip daddr 8.8.8.8 udp dport 53 accept
iifname "brvm" oifname "eno1" ip saddr 192.168.234.0/24 accept
iifname "eno1" oifname "brvm" ct state established,related accept
iifname "eno1" oifname "brvm" ip saddr 10.32.0.0/17 accept
}
}
table ip libvirt_network {
chain forward {
type filter hook forward priority filter; policy accept;
counter packets 24424457 bytes 17722682718 jump guest_cross
counter packets 24424457 bytes 17722682718 jump guest_input
counter packets 24424457 bytes 17722682718 jump guest_output
}
chain guest_output {
ip saddr 192.168.122.0/24 iif "virbr0" counter packets 117 bytes 7020 accept
iif "virbr0" counter packets 0 bytes 0 reject
}
chain guest_input {
oif "virbr0" ip daddr 192.168.122.0/24 ct state established,related counter packets 0 bytes 0 accept
oif "virbr0" counter packets 0 bytes 0 reject
}
chain guest_cross {
iif "virbr0" oif "virbr0" counter packets 0 bytes 0 accept
}
chain guest_nat {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 192.168.122.0/24 ip daddr 224.0.0.0/24 counter packets 3 bytes 120 return
ip saddr 192.168.122.0/24 ip daddr 255.255.255.255 counter packets 0 bytes 0 return
meta l4proto tcp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 10 bytes 600 masquerade to :1024-65535
meta l4proto udp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 0 bytes 0 masquerade to :1024-65535
ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 0 bytes 0 masquerade
}
}
table ip6 libvirt_network {
chain forward {
type filter hook forward priority filter; policy accept;
counter packets 0 bytes 0 jump guest_cross
counter packets 0 bytes 0 jump guest_input
counter packets 0 bytes 0 jump guest_output
}
chain guest_output {
}
chain guest_input {
}
chain guest_cross {
}
chain guest_nat {
type nat hook postrouting priority srcnat; policy accept;
}
}里面有两个端口映射规则是我穿透到虚拟机的Rustdesk的,没错,我们甚至还在路由器装了虚拟机用来学习通刷课(笑
五、最后
好吧,这就是一篇简单的nftables配置。
虽然只是简单的TTL绕过,不过参考性还是有的,至少可以当个小故事看?
希望大家都可以摆脱设备限制,随心所欲地网上冲浪(笑
草,好几个月没写文章都不知道怎么结尾了。