学长毕业清宿舍,于是捡了两台小米路由器4c回家拿来做AP,但是原系统实在是烂到离谱,而且功能太少,所以刷个openwrt玩玩
准备
breed,恩山@hackpascal的通刷包,恩山帖子,博客,下载服务器。选择breed-mt7688-reset38.bin(MT7628AN/KN 全通用,波特率 57600,复位键 GPIO#38)
openwrt,官网目前已有release:Table of Hardware
R3GV3 patches,来自教程:R3GV3 patches
SHELL
首先利用漏洞开启路由器的telnet和ftp,OpenWRTInvasion高版本不能使用,0.0.1版本只能利用netcat开个shell,但是没有ftp,比较麻烦。只能用原文提到的R3GV3 patches(python脚本)。
原文提到R3GV3 patches不需要wan口连接,其实还是需要的,不然脚本开头tracert就过不了。或者确保没有问题后也可以直接删掉检测代码,手动输192.168.31.1的ip进去。
开启telnet和ftp的脚本如下。需要准备好原包中的/data/main.tar.gz,脚本会以更新包的方式将这个包塞进路由器,其实也可以手动执行
import sys
import re
import time
import random
import hashlib
import requests
import socket
import subprocess
line4 = subprocess.check_output(["cmd","/c","chcp","437","&","tracert","-d","-h","1","1.1.1.1"]).decode().split("\r\n")[4].strip().split(" ")
for data in line4:
if len(data.split(".")) == 4:
router_ip_address = data
break
try:
r0 = requests.get("http://{router_ip_address}/cgi-bin/luci/web".format(router_ip_address=router_ip_address))
except:
print ("Xiaomi router not found...")
sys.exit(1)
try:
mac = re.findall(r'deviceId = \'(.*?)\'', r0.text)[0]
except:
print ("Xiaomi router not found...")
sys.exit(1)
key = re.findall(r'key: \'(.*)\',', r0.text)[0]
nonce = "0_" + mac + "_" + str(int(time.time())) + "_" + str(random.randint(1000, 10000))
account_str = hashlib.sha1((input("Enter router password: ") + key).encode('utf-8')).hexdigest()
password = hashlib.sha1((nonce + account_str).encode('utf-8')).hexdigest()
data = "username=admin&password={password}&logtype=2&nonce={nonce}".format(password=password,nonce=nonce)
r1 = requests.post("http://{router_ip_address}/cgi-bin/luci/api/xqsystem/login".format(router_ip_address=router_ip_address),
data = data,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"})
stok = re.findall(r'"token":"(.*?)"',r1.text)[0]
print("start uploading config file...")
resp = requests.post("http://{router_ip_address}/cgi-bin/luci/;stok={stok}/api/misystem/c_upload".format(router_ip_address=router_ip_address,stok=stok), files={"image":open("data/main.tar.gz",'rb')})
print(resp)
print("run telnet+ftpd...")
print("http://{router_ip_address}/cgi-bin/luci/;stok={stok}/api/xqnetdetect/netspeed".format(router_ip_address=router_ip_address,stok=stok))
resp = requests.get("http://{router_ip_address}/cgi-bin/luci/;stok={stok}/api/xqnetdetect/netspeed".format(router_ip_address=router_ip_address,stok=stok))
print(resp)
print("Done")
这里有个小坑,这种方式开启的ftp如果直接通过windows文件管理器访问,复制出来的文件全部都是28字节,而如果是通过xftp软件访问,则连文件名都显示不全,而且复制出来的文件也全部都是28字节。只能通过python包ftplib来访问。
备份
需要注意的是,备份一定要一份份来,备份一个,下载一个,然后删除一个,不然空间会被占满,导致路由器卡死。如果卡死了,只能重启然后重新开启shell和ftp。
丢一个备份脚本,改自原文的R3GV3 patches:
import sys
import telnetlib
import subprocess
import ftplib
line4 = subprocess.check_output(["cmd","/c","chcp","437","&","tracert","-d","-h","1","1.1.1.1"]).decode().split("\r\n")[4].strip().split(" ")
for data in line4:
if len(data.split(".")) == 4:
router_ip_address = data
break
try:
tn = telnetlib.Telnet(router_ip_address)
except:
print ("telnet server not found")
sys.exit(1)
tn.read_until(b"login:")
tn.write(b"root\n")
tn.read_until(b"root@XiaoQiang:~#")
ftp=ftplib.FTP(router_ip_address)
print ("Creating bk_all.bin")
tn.write(b"dd if=/dev/mtd0 of=/tmp/bk_all.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Downloading bk_all.bin")
file=open('data/bk_all.bin', 'wb')
ftp.retrbinary(f'RETR /tmp/bk_all.bin', file.write)
file.close()
print ("Done")
print ("Removing tmp")
tn.write(b"rm /tmp/bk_all.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Creating bk_bootloader.bin")
tn.write(b"dd if=/dev/mtd1 of=/tmp/bk_bootloader.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Downloading bk_bootloader.bin")
file=open('data/bk_bootloader.bin', 'wb')
ftp.retrbinary(f'RETR /tmp/bk_bootloader.bin', file.write)
file.close()
print ("Done")
print ("Removing tmp")
tn.write(b"rm /tmp/bk_bootloader.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Creating bk_factory.bin")
tn.write(b"dd if=/dev/mtd3 of=/tmp/bk_factory.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Downloading bk_factory")
file=open('data/bk_factory.bin', 'wb')
ftp.retrbinary(f'RETR /tmp/bk_factory.bin', file.write)
file.close()
print ("Done")
print ("Removing tmp")
tn.write(b"rm /tmp/bk_factory.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Creating bk_os1.bin")
tn.write(b"dd if=/dev/mtd7 of=/tmp/bk_os1.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
print ("Downloading bk_os1")
file=open('data/bk_os1.bin', 'wb')
ftp.retrbinary(f'RETR /tmp/bk_os1.bin', file.write)
file.close()
print ("Done")
print ("Removing tmp")
tn.write(b"rm /tmp/bk_os1.bin\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Done")
ftp.quit()
tn.write(b"exit\n")
备份文件是按照原厂闪存布局来的,可以在openwrt上这款路由器的相关页面找到。备份的是mtd0(ALL)、mtd1(Bootloader)、mtd3(Factory)、mtd7(OS1),其中比较重要的是mtd3(Factory),也就是常说的eeprom,后续需要刷入这个保留原始mac和出厂时针对硬件差异写入的调制信息。
闪存分区
Stock /proc/mtd
dev | size | erasesize | name |
---|---|---|---|
mtd0 | 01000000 | 00010000 | “ALL” |
mtd1 | 00020000 | 00010000 | “Bootloader” |
mtd2 | 00010000 | 00010000 | “Config” |
mtd3 | 00010000 | 00010000 | “Factory” |
mtd4 | 00010000 | 00010000 | “crash” |
mtd5 | 00010000 | 00010000 | “cfg_bak” |
mtd6 | 00100000 | 00010000 | “overlay” |
mtd7 | 00c60000 | 00010000 | “OS1” |
mtd8 | 00af0000 | 00010000 | “rootfs” |
mtd9 | 00200000 | 00010000 | “disk” |
OpenWRT snapshot /proc/mtd
dev | size | erasesize | name |
---|---|---|---|
mtd0 | 00020000 | 00010000 | “bootloader” |
mtd1 | 00010000 | 00010000 | “config” |
mtd2 | 00010000 | 00010000 | “factory” |
mtd3 | 00010000 | 00010000 | “crash” |
mtd4 | 00010000 | 00010000 | “cfg_bak” |
mtd5 | 00100000 | 00010000 | “overlay” |
mtd6 | 00ea0000 | 00010000 | “firmware” |
mtd7 | 002052ab | 00010000 | “kernel” |
mtd8 | 00c9ad55 | 00010000 | “rootfs” |
mtd9 | 00a10000 | 00010000 | “rootfs_data” |
刷breed
由于常规ftp软件用不了,所以只能改一下R3GV3 patches的脚本来传。需要先将breed-mt7688-reset38.bin放在脚本目录/data/
文件夹下:
import sys
import telnetlib
import subprocess
import ftplib
line4 = subprocess.check_output(["cmd","/c","chcp","437","&","tracert","-d","-h","1","1.1.1.1"]).decode().split("\r\n")[4].strip().split(" ")
for data in line4:
if len(data.split(".")) == 4:
router_ip_address = data
break
try:
ftp=ftplib.FTP(router_ip_address)
except:
print ("ftp server not found")
sys.exit(1)
try:
file=open('data/breed-mt7688-reset38.bin', 'rb')
except:
print ("breed-mt7688-reset38.bin not found")
sys.exit(1)
print ("Uploading breed-mt7688-reset38.bin ...")
ftp.storbinary(f'STOR /tmp/breed-mt7688-reset38.bin', file)
file.close()
ftp.quit()
print ("Upload done")
tn = telnetlib.Telnet(router_ip_address)
tn.read_until(b"login:")
tn.write(b"root\n")
tn.read_until(b"root@XiaoQiang:~#")
print ("Writing Bootloader...")
tn.write(b"mtd -e Bootloader write /tmp/breed-mt7688-reset38.bin Bootloader\n")
tn.read_until(b"root@XiaoQiang:~#")
tn.write(b"exit\n")
print ("Done")
刷完拔电,牙签插住reset然后插电,进入breed
刷openwrt
192.168.1.1进入breed,,先在图形界面选通用0x60000,然后刷eeprom保留mac和出厂调制信息,也就是之前备份的bk_factory.bin。如果这里没首先刷入eeprom,后面还得折腾(见#恢复eeprom)
刷完后,为了将刷机包导进路由器,先在本机开个http服务器,就在刷机包所在的目录:
py -m http.server
然后ipconfig找到本机ip,然后浏览器打开[本机ip]:8000,8000是默认端口,本机ip一般是192.168.1.2
右键复制刷机包下载链接,控制面板里关闭防火墙(不关的话路由器一般访问不了),然后telnet 192.168.1.1进入breed:
wget [文件http链接]
这里是下载到内存的,所以下完需要看提示才知道下载到的内存地址(比如0x8000000)
根据教程的说法:
前面提到了,openwrt实际上是针对原厂固件进行了适配的。通过阅读openwrt仓库里xiaomi-router-4c的dts文件(openwrt/target/linux/ramips/dts/mt7628an_xiaomi_mi_router-4c.dts)可以发现,firmware分区是从0x160000开始,大小为0xea0000的扇区。即kernel位于0x160000起始。但前面说到了,我们刷入的breed是通用breed,并没有针对此处进行适配,如果使用图形界面刷机,只能刷入0x60000等几个有限的位置。这就导致了虽然bootloader能在0x60000运行kernel,但由于kernel内隐含了dtb文件,dtb文件定义了文件系统所处的位置,如果将openwrt.bin刷入到0x60000位置,但由于整个文件向前移动了,导致文件系统也向前移动,所以dtb无法找到挂载文件系统的位置(magic:D0 0D FE ED),所以整个系统启动过程会失败,导致不断重启。
为了解决这个问题,我们需要手动将openwrt.bin刷入到0x160000起始的闪存位置上,先通过计算器算出该文件真实大小对应的16进制数字。
也就是说,系统是放在0x160000开始的位置的(其实不需要看dts,根据前面的闪存分区信息,简单的将firmware以前的分区大小加起来就是0x160000),而相比修改设备树指定文件系统位置,还是直接改刷入位置来的更快(毕竟跑一次编译得几个小时)
先手动计算刷机包的大小(16进制):
然后按照教程,擦除需要写入的系统分区位置(可以看前面的openwrt的闪存分区表):
flash erase 0x160000 0xea0000
再刷入openwrt
flash write 0x160000 [系统包下载到的16进制内存地址] [计算得到的系统包16进制大小]
根据教程:
刷写完毕后,由于breed的autoboot命令默认从0x60000加载kernel,所以我们需要额外添加参数指定其从0x160000开始加载kernel内核。执行
boot flash 0x160000
从0x160000加载内核。至此已经可以正常启动openwrt固件,进入openwrt系统了。但是,这种方法要求每次启动都要先telnet进breed后台手动启动,显然不现实。通过翻阅恩山论坛breed教程贴,发现可以使用环境变量解决
先在breed内开启环境变量,默认内部即可。然后填写环境变量autoboot.command
为boot flash 0x160000
重启进入openwrt
改AP
192.168.1.1进入openwrt,先把repo改成清华镜像装上中文语言包:
手工替换
登录到路由器,并编辑
/etc/opkg/distfeeds.conf
文件,将其中的downloads.openwrt.org
替换为mirrors.tuna.tsinghua.edu.cn/openwrt
即可。自动替换
执行如下命令自动替换
sed -i 's_downloads.openwrt.org_mirrors.tuna.tsinghua.edu.cn/openwrt_' /etc/opkg/distfeeds.conf
↑其实也可以在网页端改
然后把密码啥的都给整了,然后把ssh密码登录关了,添加自己的公钥
来到设备界面,br-lan是网桥,eth0是cpu交换机,eth0.2和0.1都是eth0虚拟出来的,也就是vlan1和vlan0的交换机,分别接wan和lan。也就是说eth0.2可以看作是接机子原本的wan口,eth0.1可以看作是接机子原本的lan口。这一点从默认的接口配置或者在交换机页面也可以看得出来。
放一张openwrt wiki上的图(见https://openwrt.org/docs/guide-user/network/vlan/switch):
由于是做AP用,我想让路由器wan口接上级路由,lan口和wifi接的机子都从上级路由dhcp获取ip,为了省去每次都找不到openwrtluci面板的ip,最后只能翻上级路由器的设备列表的麻烦,给子路由自己设定静态ip,然后在上级路由器绑定一下ip和mac。
在接口处将wan和wan6删除,在设备处给br-lan的网桥端口连上eth0.1和eth0.2,也就是lan和wan放在一起(也可以说wan已经变成了lan):
然后在接口处为LAN设定静态ip(别和上级路由重了就行,冲突了就只能先断开wan口连接才能进管理面板了),网关设为上级路由ip。然后在DHCP设置里选择忽略此接口:
为了让路由器自己也能上网,在接口处设置LAN的DNS为上级路由:
最后在无线设置里将模式改为AP,wifi的ssid、密码、加密方式设置和其他ap/主路由器一致,网络选择lan即可:
测试了一下,还是非常香的:
恢复eeprom
发现每次重启mac都会改变,导致zerotier等重启就出问题,想了想应该是eeprom没有在刷openwrt之前刷入。
不知道为什么,明明新旧分区定义的Factory/factory分区都是在同样的位置,刷包前提前刷入eeprom可以识别,但是openwrt刷入后再通过breed刷入eeprom就无法被识别了…
不管怎么样,手动将备份的eeprom刷到openwrt定义的factory分区就好了。
和刷openwrt一样,切到breed,先开http服务器,然后用wget下载。一般来说备份出来的eeprom都是64kb大小,wget一般下载到0x8000000(ram里)。计算eeprom大小为65536byte也就是16进制0x10000
然后按照教程,擦除需要写入的factory分区位置(可以看前面的openwrt的闪存分区表,0x30000 = 0x20000+0x10000):
flash erase 0x30000 0x10000
再刷入之前备份的eeprom
flash write 0x30000 [eeprom下载到的16进制内存地址] [计算得到的eeprom16进制大小]
重启后即可。mac回来了,信号变好了。
杂项
穿透:安装zerotier或tailscale&tailscaled并按照文档配置。
WOL:安装luci-app-wol,然后在服务处可以找到网络唤醒。为了防止arp条目被清除,可以在计划任务处添加arp绑定*/5 * * * * ip neigh add "目标ip" lladdr "目标mac" nud permanent dev br-lan || ip neigh chg "目标ip" lladdr "目标mac" nud permanent dev br-lan
(每5分钟加一次arp表)
自动重启:在计划任务处添加0 5 * * * sleep 70 && touch /etc/banner && reboot
(每天凌晨5点重启)