This article demonstrates how “easy” may be build a potentially malicious firmware. 
This way should be valid for EVERY TP-Link firmware header version 1 (identified by the very first 4 bytes in the header, in little endian!)

A TP-Link RE200 is a widespread cheap Range Extender.
I love having root shell on my devices, so long story short:
I’ve started to decrypt RE200 config.bin (which I’ll publish soon), to find a vector to inject code and get a root shell.
Unfortunatelly, for this time, the firmware is (not really, but enough) well written so I couldn’t see any code injection possibilities from the browser and/or from the nvram config file and (almost) every stack smash chance are avoided by reimplemented TP-Link functions.

Ok, excluding a phisical attack, a custom firmware is the common route (among the same family devices) to get the shell.

Index:
0 – rcS script
1 – firmware header structure – httpd Analysis with Ghidra
2 – firmware customization and squashfs recompression
3 – firmware file reconstruction
  3.1 – Uncompress the image
  3.2 – Modify
  3.3 – Re-Compress the image
  3.4 – Put the things togheter
4 – Firmware mod new hash
5 – Upload and verify

0 – rcS script

Once unpacked the firmware by using binwalk ($ binwalk  -e firmware.bin ) the first thing to take a look at RunControlSingle user script (rcS).

Pretty clear,  TP-Link has commented the telnet row command, disabling the daemon.
In the end we’ll see the httpd start command.

1 – firmware header structure – httpd Analysis with Ghidra

Let’s start to examinate the firmware header in Ghidra. Our focus lands on upgradeFirmware(void *param_1,uint param_2)

I will try to be as brief as possible. Additional explainations requests in comments are welcome
1- In a similar way we seen in the config.bin [here], the file offset 0x4c contanins the md5 hash signature
2- int variable in 0x94 indicates if our firmware conains bootloader. If yes, the md5 signature uses a different salt.
3- md5_veryfy_digest. No explaination, this works in the same way as in the config.bin. Getting the md5  from the file in a variable, it replaces the same file address readen 0x4c with the 0x10 bytes with the right salt on point 2. It calculates the md5, and compare with the one originally readen.
4- Country code check @ 0x98 len=3chars: in case we’ll change the firmware from a different country, we need to restore the same country code the device is factured with.

 md5key_withoutbootloader = DC D7 3A A5 C3 95 98 FB DD F9 E7 F4 0E AE 47 38
 md5key_withbootloader =      8C EF 33 5B D5 C5 CE FA A7 9C 28 DA B2 E9 0F 42

2 – firmware customization and squashfs recompression
We have now enough informations about the firmware header which allows us to grant writing on the device flash.

On the official TP-Link firmware, we have 2 headers, the first at 0x0 and a second one at 0x20200 (in this case). The first section contains the uboot, after the second header kernel + rootfs.

The second header contains the signature made from 0x20200 to EOF with the md5key_withoutbootloader salt
The first md5 signature is made with  md5key_withbootloader from 0x0 to EOF, (thus also including the second image)

As seen in httpd, we have 2 allowed kind of images: the first, is the whole image uboot+kernel+rootfs and the second way is to write just the kernel+rootfs which leaves the uboot uneffected (read: if I fail I can still de-brick by using a serial TTL :slight_smile: )
That’s why I’ve splitted the firmware.

3 – Firmware file reconstruction
 3.1 Uncompress the image
First of all, from the firmware, we’re going to extract the squasfs file and uncompress by using unsquashfs:

Note: binwalk is an amazing swiss-knife, keep in mind the compression xz (LZMA2) and the inodes

By dd tool, squashfs has been extracted skipping, from the firmware file, the first 0x100000 (1048576 in decimal) bytes.
Next step was to uncompress the image with simply with # unsquashfs filename

 3.2 Modify
I’ve used nano to modify the rcS row number 33
from:
#telnetd -l /bin/login &
to
telnetd -l /bin/login &
Forgot to say: /etc/shadow file contains a different password then the one we use on the web.
Create your new /etc/shadow file!
The TP-Link credential hardcoded are root:shoadmin
(Thank you 6c2e6e2e )

3.3 Re-Compress the image
now recompress time with: mksquashfs squashfsdir/ fileout.squashfs -comp xz -no-duplicates
Those last two options are vital. The first indicates to mksquashfs to use LZMA2 algo(the same algo we know the stock firmware is using, showed by binwalk), the second one prevents mksquashfs to delete duplicates, guaranteeing the correct restoration of inodes and file number.

3.4 Put the things togheter 
Anyway the file size is reasonably different different. Anyway we can have a bigger (or smaller) image, anyway not bigger than 0x6C0000 calculated from my reduced (kernel+squashfs) stock firmware (EOF) 0x7C0000 – 0x100000 (initial squashfs magic number).
In the re200_imagecut.bin (obtained in paragraph 2, see image) we overwrite into the file starting from 0x100000 our new squashfs image by keeping the SAME original file size, if needed padding the file adding or removing 0xFF up to 0x7C0000-1

4 – Firmware mod new hash
We have now recreated the right file structure. The last step is to rewrite the new md5 at 0x4c-0x5c.
In my case, bootloaderless, my key will be md5key_withoutbootloader:

By keeping in mind what we read at the paragraph n.2, once replaced the md5 you’ll have the new md5 (see the picture at the bottom) wich we will use to replace the salt (in red) from 0x4c to 0x5c.

5 – Upload and verify

Let’s upload and try to login with Putty…

BOOM … it worked like a charm on the first try!!!

Modified firmware image here.
(no uboot, telnet enabled: default telnet credentials root:shoadmin)

Cheers,
RE-Solver

Article Link: https://resolverblog.blogspot.com/2020/03/tp-link-re200-aka-ac750-unpack-repack.html

* This article was originally published here
www.MakingSenseofSecurity.com