In my previous post I gave you an intro about AX3600 and how awesome it is. This post will focus on how to gain persistent ssh on the device.
First off, I recommend following the OpenWRT effort on porting AX3600: Adding OpenWrt support for Xiaomi AX3600 For Developers. Until then, follow this guide.
All the scripts, firmwares, configuration files, etc’ mentioned in this post are available on GitHub: odedlaz/ax3600-files. Please feel free to open issues or better yet - pull requests!!
All you need to know is that early versions of the router had a command injection vulnerability that is now closed. In order to gain ssh access, you first need to downgrade your router’s firmware then run the commands listed in OpenWRT’s page. But what is the command injection? taking closer look at the source…
$ ubireader_extract_images -o xioamifw -w miwifi_r3600_firmware_5da25_1.0.17.bin
The command injection URL looks like this:
How does it work? let’s find
$ grep! -R "set_config_iotdev"
Ok, it looks like there’s a file called
misystem.lua that contains all the controller apis. Also, the
set_config_iotdev parameter is tied to the
setConfigIotDev. easy enough… let’s look at it:
Very straight forward and classic! look at the line starting with
forkExec: there’s no input validation, thus parameters supplied to the api are used on the router itself and passed directly to exec. The injection basically add a
\n to the end of the last parameter, following by the command we really want to run, which is
nvram set ssh_en=1.
By the way, on later firmwares the
misystem.lua is compiled and encrypted. While trying to figure out how to decrypt it I came across a great deck called Exploit (Almost) all Xiaomi Routers Using Logical Bugs which you might like.
Enough! we just want to gain SSH! Let’s downgrade. It’s pretty easy, all you need to do is:
- access the setting page:
- press “Manual upgrade” (I’ve got Google Translate turned on)
- upload the firmware (miwifi_r3600_firmware_5da25_1.0.17.bin). I backed it up since I’m a bit paranoid. You can find it under the firmwares directory in my repo or the official link.
Afterwards, you can follow the guide supplied by OpenWRT or just import the Postman collection I created. Don’t forget to update the
stok variable in the collection. You can grab it by logging in to the router web interface and getting the value of “stok=” from the URL.
A guy called didiaoing wrote a nice tutorial which explains how to modify the routers bdata partition in order to persistenly turn on ssh. The problem? it’s manual, error prone and in chinese.
Enough talkin’! let’s get flashin’!
Clone my repo and create a bdata directory:
$ git clone https://github.com/odedlaz/ax3600-files.git
You can ssh to your router and see the mtd partitions for yourself, they’re all there for the taking:
$ cat /proc/mtd
We’re interested in the
bdata partition. go ahead and dump it:
then copy it to your machine. If you’re using linux, then the following command should work:
$ scp [email protected]:/tmp/bdata_mtd9.img /path/to/ax3600-files/bdata
If you open it up in a hex editor, you’ll notice a few weird bytes in the beginning then a few ASCII encoded strings, a looooot of zeroes and some other bytes in the end. the first four bytes are the CRC32 checksum for the bdata partition and the strings indicate the devices configuration. you probably recognize some of them, since you’ve updated them when turning on ssh on the device!
You can also look at the header by running the
header.py provided script. just cd to the
scripts directory and issue the following command:
$ ./header.py extract ../bdata/bdata_mtd9.img
We want to update these values to enable ssh on boot. Since dropbear is not turned on by default, you need to enable telnet as well. Actually, if we’re going to mess with the router, why not turn on the router’s serial port so you could flash the entire thing if you break it? Darell Tan’s got a nice post on customizing the firmware through serial. great read!
Moreover, in the next post I’ll write about the weird stuff I found on the router. Some of these stuff only run if the router’s Country Code is set to
CN, so why not change it to
US? this is not random! when you change the country code to something other than CN, the router behaves a bit differently. for instance, it performs online checks against
microsoft instead of
taobao. both are configured in
/etc/config/system and accessed by running
uci -q get system.netdt.world_domain and
uci -q get system.netdt.cn_domain respectively. Don’t believe me? take a look at the
check_gateway function in
Once you update the headers, you also need to update the checksum. Don’t worry,
header.py does it all for you.
First let’s do a test run to make sure we don’t break anything:
$ ./header.py modify --test ../bdata/bdata_mtd9.img ../bdata/bdata_mtd9.img.modified
If you got any response other than the above, please open an issue! if everything went fine, you can run:
$ ./header.py modify --country US ../bdata/bdata_mtd9.img ../bdata/bdata_mtd9.img.modified
You’ll notice that the checksum is now updated and
uart_en were all changed. Moreover, a new
boot_wait config has been added.
Unfortunately, the bdata partition is read only. You need to make it writable prior to flashing the bdata partition or you’ll get a readonly error:
$ mtd write /tmp/bdata_mtd9.img bdata
How? flash a new crash partition. Doing so opens the bdata partition for writing. I was (and still am) quite afraid to do that, since I have no clue what that crash partition contains. A random guy named
barnamacko uploaded a crash partition that works on the OpenWRT forum. Flashing an unknown binary from an unknown source on your home router? why not! on a more serious note, I wasn’t keen to do that. I’m not sure what that binary contains, but I do know that it doesn’t modify the filesystem (I checked).
I recommend you physically connect to the router before performing any of these commands. Some people lost Wi-Fi connectivity when they flashed the crash partition (don’t worry, after removing it Wi-Fi works again!)
Go ahead and run the following on your computer:
$ scp ../crash/crash_unlock.img [email protected]:/tmp
Then login to the router and flash it:
$ mtd write /tmp/crash_unlock.img crash
post reboot, upload the modified bdata partition you created:
$ scp ../bdata/bdata_mtd9.img.modified [email protected]:/tmp
and flash it:
$ mtd write /tmp/bdata_mtd9.img.modified bdata
After reboot, login to your router again and remove the crash partition:
$ mtd erase crash
Then perform a factory reset on the device by surfing to:
http://192.168.31.1/cgi-bin/luci/;stok=<STOK>/web/setting/upgrade. This step is important because otherwise you might bump into upgrade verification issues.
After the reset, you can upgrade to the latest firmware! If you do so, you’ll lose ssh connectivity. Don’t worry, you can connect to the device via telnet:
$ telnet 192.168.31.1 23
Oh snap! what’s the password? well, after you patched the router and upgraded the firmware, the ssh password you set beforehand got reset. dear didiaoing created a website that generates the password for you! all you need to do is fill in the router’s SN number and get the default password. There’s nothing bad about sending your routers SN to a random website which performs all the calculation on the backend, right? I had to do some digging in order to find the algorithm that’s being usedd to generate those :)
thankfully, I found another chinese guy that goes by the alias zhoujiazhao, that wrote the algorithm in php. who does that these days?! PHP? really? me to the rescue, I ported it to python. Just run
calc_passwd and you’re good to go:
$ ./calc_passwd.py ../bdata/bdata_mtd9.img.modified
By the way, I haven’t encountered password issues, only read about them online. Once I got ssh acsess and reset the password, I flashed the international firmware and was able to connect with telnet.
Once you’ve got shell access, you can turn on ssh by running postman steps (4) to (7), or just reset the password to
$ sed -i 's/channel=.*/channel=\"debug\"/g' /etc/init.d/dropbear
Moreover, if you do decide to flash the international firmware, you probably want to change the interface language to English:
$ uci set luci.main.lang='en'
Don’t forget to turn off telnet after you verify ssh is working:
$ nvram set telnet_en=0
That’s it folks! I hope you won’t need to do any of this once the guys at OpenWRT finish porting it to AX3600 :)