Broadcom eCos | Gaining Persistence with Firmware Implants
When I sent out my first vulnerability report for a memory corruption issue affecting a Broadcom based eCos device, the conclusion stated:
By chaining these vulnerabilities an attacker can gain unauthorized access to customers LAN (over the Internet or by being in reception range of the access point), fully compromise the router, and leave a persistent backdoor allowing direct remote access to the network.
At that point I was confident that backdooring would be possible but I did not have definitive proof yet. This article will explore how we can achieve that by building a backdoored firmware.
Firmware Repacking
The first steps to investigate whether we can run a backdoored firmware is to unpack, modify, repack, and try to run the re-packed firmware.
We have an extracted Broadcom eCos firmware file from the manufacturer ASKEY, provided to Orange Belgium ISP. The first step is to get rid of all the null bytes padding at the end of the file, otherwise ProgramStore repacking will fail.
From the output below, we see that content from 0x01965f50 to 0x06000000 is full of null bytes:
We can remove them easily with dd:
Now let’s replace a string in place with sed:
And repack the firmware into a ProgramStore file with Broadcom’s utility:
Now all we have to do is boot the device, go into the bootloader menu by pressing ‘p’ and run a firmware in RAM from a file loaded over TFTP:
The device boots normally, our changed string is visible, this means that neither the bootloader or the operating system enforce firmware authenticity checks or secure boot.
We can move on and try to insert an actual backdoor into the firmware.
Arbitrary Code Injection
I identified one interesting function that I had renamed ‘StartupServer’. This function performs some operations related to remote console access via telnet, but is not crucial to the device operation.
The function spans from offset 0x805f4434 to 0x805f4b28, and the idea will be to overwrite that section with our own custom payload.
The payload I designed will launch a thread named ‘payload’, exposing a bind shell on port 4444:
We need to edit the linker script and put the right offset, which corresponds to the start address of StartupServer function:
I wrote this quick and dirty Python script to overwrite a given section with custom shellcode:
Let’s inject our shellcode:
We repack it, serve it over TFTP and let it boot. As we can see from the boot logs below, our malicious code is executing successfully.
Note that if we wanted our code to run in a single window without being preempted by the scheduler, we could add calls to cyg_scheduler_lock and cyg_scheduler_unlock. This way our logs would no longer be spread around in the boot logs :)
The device is fully functional, and we have a bind shell:
Our malicious thread (payload) is visible:
This device is quite unique in that it runs on two cores, one dedicated to cable modem work (CM) and the other dedicated to network routing (RG). Each of them expose a specific console, and you need to run code from a specific context to execute within CM or RG. Interestingly, we have two functions named StartupServer:
offsets: 0x805f4434 - 0x805f4b28 (CM)
offsets: 0x805d4624 - 0x805d4d18 (probably RG)
So if you are targeting a Broadcom device that expose two consoles (these are rare), and that you really want to gain access to both, you’d have to also replace the second function. We can imagine the CM console exposed on port TCP/4444 and the RG console exposed on port TCP/5555.
Implant Writing Shellcode
I was initially planning on writing my own client to write the backdoored firmware to flash when I found out that broadcom devices implement multiple update commands:
CM/ip_hal/dload - download and save firmware to flash (IP controller)
CM/docsis_ctl/dload - download and save firmware to flash (CMTS controller)
CM/ip_hal/bootloader - download and save bootloader to flash (IP controller)
CM/docsis_ctl/bootloader - download and save bootloader to flash (CMTS controller)
The difference between ip_hal and docsis_ctl is the route that the TFTP request will take when fetching the file from a remote host, but I won’t cover DOCSIS networking internals here.
Here’s the command documentation:
A quick demo with our malicious firmware:
And here’s the documentation for the bootloader update command:
I did not test it yet with a backdoored bootloader image, but I’ll make sure to edit this post when I do.
Conclusion
Over the course of this article, we learned how to unpack, implant, and repack a Broadcom eCos firmware file. We then explored ways of running our malicious firmware file: loading over TFTP and running on RAM for debugging purposes, and writing to NAND flash for persistence.
We therefore proved our initial hypothesis that said “and leave a persistent backdoor allowing direct remote access to the network”.
As always, if you have any question feel free to contact me via Twitter or email.