Code execution as root via AT commands on the Quectel EG25-G modem

As I mentioned towards the end of my previous blog post, where I detailed running my blog on the PinePhone's GSM/WWAN/GPS modem, I suspected that the daemon responsible for parsing AT commands on the modem's side is susceptible to OS command injection, as it uses a lot of system() calls. My hunch turned out to be true.

Communication with the PinePhone

Among other channels, the PinePhone communicates with the Quectel modem by sending AT commands to the modem over a serial line - /dev/ttyUSB2 on the PinePhone's side and /dev/ttyHSL0 on the modem's side.

The modem, which runs a full Linux install separate from the PinePhone's main OS, receives these commands, parses them, and executes them according to program logic. After this, the modem either returns OK or ERROR over the serial line back to the PinePhone. The daemon primarily responsible for this is atfwd_daemon.

Analyzing atfwd_daemon

Getting the daemon is easy. It's possible to set up adb access and extract it using adb. It's also possible to simply extract it from the firmware's update packages, as it's not encrypted in any way.

Loading atfwd_daemon in Ghidra reveals that the executable uses system() in 233 different places across the file. That's… quite a lot.

While using system() with user input is never a good idea, most of the calls cannot be exploited due to being hardcoded or the fact that user input is converted to an integer using sprintf():

/blog/img/1BSw4BQZ.png

However, there are a few places where user input is sprintf()-d as %s and no checks or sanitization is performed on user input.

One of these places is in a routine called quectel_handle_fumo_cfg_command():

/blog/img/EnUTEnhj.png

Here we can see that param1[1] is being formatted as ipth_dme -dmacc %s &, which is then passed to system(). What's interesting to note here is that ipth_dme does not exist on the system at all, so this program would never run.

Traversing the program execution flow, we can see that the switch case in the previous screenshot is triggered when some part of user input begins with "dmacc". This is checked in a routine called quectel_parse_fumo_cfg_cmd_params():

/blog/img/DSuRVPK1.png

The rest of the input remains relatively untouched.

Going further up the program flow, we can see that the command in question which parses this input is +QFUMOCFG:

/blog/img/wCONynTY.png

Code execution

From this, we can deduce that arbitrary command execution is possible. We can, for example, use backticks to execute our commands in a subshell. As an example, to reboot the modem:

AT+QFUMOCFG="dmacc","`reboot`"

Due to the fact that the daemon runs as root, the code is also being executed as the root user on the modem.

As an example, in this Asciinema recording, I cat /etc/passwd and run id, and return the data to the PinePhone's OS over a serial line:

asciicast

It's very possible that this vulnerability affects other Quectel products as well, as firmware is commonly reused, but I do not possess other hardware to test it on.


Timeline

  • 03/04/2021 - Attempted to contact vendor
  • 13/04/2021 - Vendor confirmed vulnerability
  • 23/04/2021 - Vendor issued $2,000 bounty
  • 24/04/2021 - Assigned ID CVE-2021-31698
  • 08/09/2021 - Write-up published

Author | nns

Ethical Hacking and Cybersecurity professional with a special interest for hardware hacking, IoT and Linux/GNU.