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 (
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 :) )
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 (
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 Openwrt)
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:sohoadmin)
Cheers,
RE-Solver
Hello, the article are very interesting, have you a book or more readings to study the topic?
RispondiEliminaI wish the same with a ubiquiti router.
Thanks a lot
Jaime