OpenWrt 校园网共享
背景
入学新学校,校园网 49元/月 100M ,只能够同时登录两台设备,于是着手破解。
破解后当普通 Wi-Fi 连接即可,无设备数量限制,同时还可以做到多账号网速叠加。

思路
- 逆向校园网 Web 登录算法
- 将登录算法移植到 OpenWrt 中
- 使用 Macvlan 和 MultiWAN 插件实现多账号登录
登录算法逆向
连接校园网,随便输入一个网址就可以跳转到登录界面(这是通过 DNS劫持 实现的)。
通过观察代码发现,这是锐捷网络的 Web 认证,便在网络上找到了相同认证系统的登录算法实现,所以这一步非常的轻松,基本没有自己进行逆向分析。

OpenWrt
硬路由的选择
因为要使用 OpenWRT 系统,所以路由器需要选择能够刷机的,且自带 Wi-Fi 的硬路由。
这里选择的是 H3C NX30 Pro,二手售价约100元,配置如下:
| 型号 | H3C NX30 Pro |
|---|---|
| CPU | MT7981B |
| 闪存 | 128M |
| 内存 | DDR3 256M |
刷机教程
网络上有很多刷机的教程,这里不多赘述。
使用的 uboot 仓库如下:
https://github.com/hanwckf/bl-mt798x
固件的选择
固件选择了自编译 ImmortalWrt 23.05.6,且使用最精简固件的思路,仅保留了 mwan、turboacc、fileassistant、upnp 插件。
编译产物的 config.buildinfo 如下
CONFIG_TARGET_mediatek=y
CONFIG_TARGET_mediatek_filogic=y
CONFIG_TARGET_mediatek_filogic_DEVICE_h3c_magic-nx30-pro-nmbm=y
CONFIG_LIBCURL_COOKIES=y
CONFIG_LIBCURL_CRYPTO_AUTH=y
CONFIG_LIBCURL_FILE=y
CONFIG_LIBCURL_FTP=y
CONFIG_LIBCURL_HTTP=y
CONFIG_LIBCURL_NGHTTP2=y
CONFIG_LIBCURL_OPENSSL=y
CONFIG_LIBCURL_PROXY=y
CONFIG_LIBCURL_TFTP=y
CONFIG_LIBCURL_THREADED_RESOLVER=y
CONFIG_LIBCURL_TLS_SRP=y
CONFIG_LIBCURL_UNIX_SOCKETS=y
CONFIG_PACKAGE_bash=y
CONFIG_PACKAGE_curl=y
CONFIG_PACKAGE_htop=y
CONFIG_PACKAGE_ip-tiny=y
CONFIG_PACKAGE_ip6tables-nft=y
CONFIG_PACKAGE_ipset=y
CONFIG_PACKAGE_iptables-mod-conntrack-extra=y
CONFIG_PACKAGE_iptables-mod-ipopt=y
CONFIG_PACKAGE_iptables-nft=y
CONFIG_PACKAGE_iputils-arping=y
CONFIG_PACKAGE_jq=y
CONFIG_PACKAGE_kmod-ip6tables=y
CONFIG_PACKAGE_kmod-ipt-conntrack=y
CONFIG_PACKAGE_kmod-ipt-conntrack-extra=y
CONFIG_PACKAGE_kmod-ipt-core=y
CONFIG_PACKAGE_kmod-ipt-ipopt=y
CONFIG_PACKAGE_kmod-ipt-ipset=y
CONFIG_PACKAGE_kmod-nf-conncount=y
CONFIG_PACKAGE_kmod-nf-ipt=y
CONFIG_PACKAGE_kmod-nf-ipt6=y
CONFIG_PACKAGE_kmod-nft-compat=y
CONFIG_PACKAGE_kmod-nft-fullcone=y
CONFIG_PACKAGE_kmod-tcp-bbr=y
CONFIG_PACKAGE_libblkid=y
CONFIG_PACKAGE_libcap=y
CONFIG_PACKAGE_libcap-ng=y
CONFIG_PACKAGE_libcurl=y
CONFIG_PACKAGE_libipset=y
CONFIG_PACKAGE_libiptext=y
CONFIG_PACKAGE_libiptext-nft=y
CONFIG_PACKAGE_libiptext6=y
CONFIG_PACKAGE_libmount=y
CONFIG_PACKAGE_libncurses=y
CONFIG_PACKAGE_libnghttp2=y
CONFIG_PACKAGE_libreadline=y
CONFIG_PACKAGE_librt=y
CONFIG_PACKAGE_libsmartcols=y
CONFIG_PACKAGE_libstdcpp=y
CONFIG_PACKAGE_libuuid=y
CONFIG_PACKAGE_libuv=y
CONFIG_PACKAGE_libwebsockets-full=y
CONFIG_PACKAGE_libxtables=y
CONFIG_PACKAGE_lsblk=y
CONFIG_PACKAGE_luci-app-fileassistant=y
CONFIG_PACKAGE_luci-app-mwan3=y
CONFIG_PACKAGE_luci-app-turboacc=y
CONFIG_PACKAGE_luci-app-upnp=y
CONFIG_PACKAGE_luci-i18n-mwan3-zh-cn=y
CONFIG_PACKAGE_luci-i18n-turboacc-zh-cn=y
CONFIG_PACKAGE_luci-i18n-upnp-zh-cn=y
CONFIG_PACKAGE_miniupnpd-nftables=y
CONFIG_PACKAGE_mwan3=y
CONFIG_PACKAGE_openssh-client=y
CONFIG_PACKAGE_openssh-keygen=y
CONFIG_PACKAGE_pdnsd-alt=y
CONFIG_PACKAGE_smartmontools=y
CONFIG_PACKAGE_smartmontools-drivedb=y
CONFIG_PACKAGE_terminfo=y
CONFIG_PACKAGE_xtables-nft=y
CONFIG_PACKAGE_zlib=y通过上面的 buildinfo 编译出来的固件仅仅 15.4MB。
登录算法移植
这里使用 OpenWrt 的 init.d 与 procd 守护校园网登录状态,如果检测到离线则自动尝试登录。
代码如下:
#!/bin/sh /etc/rc.common
# campus portal login as an init.d service for OpenWrt (with procd monitor)
START=95
STOP=10
USE_PROCD=1
# 配置区
USER_ID="" # 用户名
PASSWORD="" # 密码
SERVICE="DianXin" # Service 字段,没有可以留空
INTERFACE="eth1" # 网络端口,用于指定 Macvlan 多账号登录
PING_URL="http://baidu.com"
PORTAL_HOST="10.30.252.71" # 记得修改这个为自己学校的登录网址 IP
INDEX_URL="http://$PORTAL_HOST/eportal/index.jsp"
LOGIN_API="http://$PORTAL_HOST/eportal/InterFace.do?method=login"
CURL_UA="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36"
CURL_OPTS="-fsS --connect-timeout 5 --max-time 10 --interface $INTERFACE"
# 监控间隔(秒)
MONITOR_INTERVAL=60
# 断线判定失败后重试次数/间隔
RETRY_MAX=3
RETRY_SLEEP=2
EXTRA_COMMANDS="login sstatus"
EXTRA_HELP=" login Run a one-shot portal login now
sstatus Print current connectivity/portal status"
url_encode() {
local s="${1}" i c out=""
for i in $(seq 1 ${#s}); do
c="$(printf '%s' "$s" | cut -c $i)"
case "$c" in
[a-zA-Z0-9.~_-]) out="${out}${c}" ;;
*) out="${out}$(printf '%%%02X' "'$c")" ;;
esac
done
printf '%s' "$out"
}
is_online() {
local resp
resp="$(curl $CURL_OPTS "$PING_URL" -A "$CURL_UA" || true)"
echo "$resp" | grep -q "http://www\.baidu\.com/" && return 0 || return 1
}
extract_query_string() {
local resp="$1"
echo "$resp" \
| grep -o "top\.self\.location\.href='http://$PORTAL_HOST/eportal/index\.jsp?[^']\+'" \
| sed "s#.*index.jsp?##; s/'\$//"
}
do_login_once() {
echo "[campus_portal_eth1] Probing connectivity..."
if is_online; then
echo "[campus_portal_eth1] Already online."
return 0
fi
echo "[campus_portal_eth1] Fetching portal redirect page..."
local resp qs enc1 enc2 post_data login_json
resp="$(curl $CURL_OPTS "$PING_URL" -A "$CURL_UA" || true)"
qs="$(extract_query_string "$resp")"
if [ -z "$qs" ]; then
echo "[campus_portal_eth1] ERROR: failed to get queryString."
return 1
fi
echo "[campus_portal_eth1] Got queryString (len=${#qs})."
enc1="$(url_encode "$qs")"
enc2="$(url_encode "$enc1")"
post_data="userId=$(url_encode "$USER_ID")&password=$(url_encode "$PASSWORD")&service=$(url_encode "$SERVICE")&queryString=$enc2&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false"
echo "[campus_portal_eth1] Logging in..."
login_json="$(curl $CURL_OPTS -X POST "$LOGIN_API" \
-H "User-Agent: $CURL_UA" \
-H "Referer: $INDEX_URL" \
-H "Accept: */*" \
-H "Accept-Encoding: gzip, deflate" \
-H "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8" \
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
--data "$post_data" || true)"
echo "$login_json" | grep -q '"result":"success"' && {
echo "[campus_portal_eth1] Login success."
return 0
}
echo "[campus_portal_eth1] Login failed."
echo "$login_json" | sed -n 's/.*"message":"\([^"]*\)".*/[campus_portal_eth1] reason: \1/p'
return 2
}
# procd 管理的常驻监控
_monitor_loop() {
# 注意:这里不要用 'EOF',让变量在这里就展开
cat <<EOF
MONITOR_INTERVAL=${MONITOR_INTERVAL}
RETRY_MAX=${RETRY_MAX}
RETRY_SLEEP=${RETRY_SLEEP}
PING_URL="${PING_URL}"
CURL_UA="${CURL_UA}"
is_online() {
resp="\$(curl -fsS --connect-timeout 5 --max-time 10 --interface $INTERFACE "\$PING_URL" -A "$CURL_UA" || true)"
echo "\$resp" | grep -q "http://www\\.baidu\\.com/"
}
while true; do
if is_online; then
sleep "\$MONITOR_INTERVAL"
continue
fi
echo "[campus_portal_eth1] offline detected, trying to login..."
tries=1
rc=1
while [ "\$tries" -le "\$RETRY_MAX" ]; do
# 请注意,一定要保证这里的文件名和你的文件名一致。
/etc/init.d/campus_portal_eth1 login && { rc=0; break; }
sleep "\$RETRY_SLEEP"
tries=\$((tries+1))
done
if [ "\$rc" -ne 0 ]; then
echo "[campus_portal_eth1] auto-login failed after \$RETRY_MAX tries."
fi
sleep "\$MONITOR_INTERVAL"
done
EOF
}
start_service() {
procd_open_instance
# 用 /bin/sh -c 启一个 loop,当作守护进程
procd_set_param command /bin/sh -c "$(_monitor_loop)"
# 异常退出自动重启(首次延迟 5s,之后指数回退 60s 封顶)
procd_set_param respawn 5 1 60
procd_set_param stdout 1
procd_set_param stderr 1
# 可按需添加:当网络配置变更时重启该实例
procd_close_instance
}
stop_service() {
# procd 会杀掉守护进程
return 0
}
reload_service() { stop; start; }
service_triggers() {
# 当 network / wireless 配置被 commit 时触发
procd_add_reload_trigger "network" "wireless"
}
# rc.common 默认 boot() 会调用 start()
# 我们保留一次性手动命令
login() { do_login_once; }
sstatus() { if is_online; then echo "[campus_portal_eth1] Online"; else echo "[campus_portal_eth1] Offline"; fi; }
修改完配置后将代码写到 /etc/init.d/campus_portal_eth1(注意,文件名要在代码里面一并修改),启动后可以在系统日志中看到日志信息。
# 开机启动
/etc/init.d/campus_portal_eth1 enable
# 启动
/etc/init.d/campus_portal_eth1 start多账号
配置详细教程如下:基于openwrt的MWAN3实现多运营商负载均衡的一种方法
配置完成端口后将上面的 init.d 代码全局替换 eth1 为你创建的接口后,复制一份到 /etc/init.d/ 中即可,每个账号一个服务这样可以保证对每个校园网账号的登录控制。
Macvlan
在 网络→接口→设备 中可以添加 Macvlan,选择正确的 基础设备 后点击保存即可。
添加 Macvlan 完成后在 网络→接口 中添加对应的 DHCP客户端,同时设置防火墙区域为 wan 即可。


MultiWAN 负载均衡
mwan 的配置教程这里就不过多赘述了,我使用个两个接口(也就是两个账号)配置如上图。mwan 是通过 iptables + fwmark 路由规则来实现的负载均衡,所以在单线程情况下,无法速度叠加。





