Find Hidden Network Backdoor in WiFi Firmware

In this tutorial, you will learn how to use static analysis to check for backdoors in the OpenWRT firmware binary.

Hello World! Imagine you work on a security team in the forensics division. You have been given WiFi network's firmware to determine whether a suspicious binary is dangerous or not. We appreciate the network administrator's assistance in pointing out the target file. Today I'll go through how to examine WiFi firmware and detect dangerous programmes in it.

In case you want to practice this lab on the AttackDefense platform, here is the lab link –

There is only one file with the name firmware.bin in the current user's home directory (/root). Binwalk can discover available files that are stacked on top of each other and automatically extract (through the -e flag) them based on known types and deflators.

binwalk -e firmware.bin
Binwalk command to extract firmware.bin

The firmware.bin binary, as you can see, contains two types of files: First, you'll notice some LZMA compressed data. Let's ignore it for the time being because the next file in the binary is SquashFS, which is our actual firmware filesystem containing files as well as the malicious binary.

Extract compressed firmware binary

Binwalk will extract the binary into the _firmware.bin.extracted directory. You will find another directory named squash-root, which will be the firmware's root file system.

To facilitate setup, the OpenWRT team included a /etc/rc.local file in the firmware that will execute as a bash script when system init is completed; by default, the file is empty with a comment in it.

cd _firmware.bin.extracted
cat squashfs-root/etc/rc.local
Read startup script in the squashfs

You can see in file that it contains reference to script or binary with the absolute name /usr/bin/start_essentials and then exist 0 is executed. By the name alone, it appears to be a legitimate file, but let's dive deeper to learn more about it.

Check startup scripts for suspicious binary

Using the file command, you can determine the file type based on its magic number or content.

file squashfs-root/usr/bin/start_essentials
cat squashfs-root/usr/bin/start_essentials
Read start_essentials file in the firmware file system

So the file type is bash script, and as you know, ASCII encoded files are used (human-readable). The $PIDFILE variable, which is deduced from the $INPUT and $PID variables from the script, is run last in the start service function. It is obvious that the file is referring to another file located at /opt/5676.

Script file is executing /opt/5676 file

The file /opt/5676 is again a bash script and execute another script / binary with absolute path /usr/bin/webhelper.

Get contents of the suspicious script in /opt directory

When the file type of squashfs-root/usr/bin/webhelper is examined, it is discovered that it is ELF executable and dynamically linked.

To learn more about the binary, run a static analysis using the strings command and list all the human-readable data from the binary that is longer than or equal to 10 characters.

file squashfs-root/usr/bin/webhelper
strings -10 squashfs-root/usr/bin/webhelper | less
Get strings of webhelper binary

From the output of the strings command, it is evident that the file is reading the contents of /etc/shadow file and sending attacker machine hosted at domain name using POST request.

Output of strings commands showing HTTP request an shadow file name

If this isn't enough to classify it as a malicious binary, let's take it a step further and read all the dynamic symbols from the import table while filtering only FUNC (function) with the readelf command.

readelf --dyn-syms squashfs-root/usr/bin/webhelper  | grep FUNC
Read dynamic function imports for webhelper script

As you can see there are a lot of networking and file operation related functions are imported and referenced in the malicious binary:

  • fopen(), fread() and fclose()  functions could be used to open the /etc/shadow file, read its contents into a variable and then close the file
  • gethostbyname() is used to translate host into IP addresss to establish connection using connect() function.
LIst of imported functions for webhelper binary