文章11
标签16
分类0
小米路由器4C刷机改AP

小米路由器4C刷机改AP

学长毕业清宿舍,于是捡了两台小米路由器4c回家拿来做AP,但是原系统实在是烂到离谱,而且功能太少,所以刷个openwrt玩玩

准备

主要教程:@BBSD丿草丶帽写的教程

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

image-20220129153048721

右键复制刷机包下载链接,控制面板里关闭防火墙(不关的话路由器一般访问不了),然后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进制):

image-20220129155111192

然后按照教程,擦除需要写入的系统分区位置(可以看前面的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.commandboot 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):

asus-internals-default

由于是做AP用,我想让路由器wan口接上级路由,lan口和wifi接的机子都从上级路由dhcp获取ip,为了省去每次都找不到openwrtluci面板的ip,最后只能翻上级路由器的设备列表的麻烦,给子路由自己设定静态ip,然后在上级路由器绑定一下ip和mac。

在接口处将wan和wan6删除,在设备处给br-lan的网桥端口连上eth0.1和eth0.2,也就是lan和wan放在一起(也可以说wan已经变成了lan):

image-20220129172653837

然后在接口处为LAN设定静态ip(别和上级路由重了就行,冲突了就只能先断开wan口连接才能进管理面板了),网关设为上级路由ip。然后在DHCP设置里选择忽略此接口:

image-20220129173113283

为了让路由器自己也能上网,在接口处设置LAN的DNS为上级路由:

image-20220129173304246

最后在无线设置里将模式改为AP,wifi的ssid、密码、加密方式设置和其他ap/主路由器一致,网络选择lan即可:

image-20220129173620190

测试了一下,还是非常香的:

image-20220129174226427

恢复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点重启)

本文作者:.torrent
本文链接:https://blog.hitachimako.top/2022/flash-mirouter4c/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可