In this article I’ll explain how to craft shellcode that you can deliver as a second stage to a victim eCos device. I’m specifically covering the Broadcom variant of eCos here.
I’ll cover two techniques for building these:
manual function mapping
using the GCC linker
I know relying on the GCC linker is the best method but I chose to document both so that everyone can understand why.
Then the fixed address where the socket file descriptor value is saved. This address is shared by both the shellcode and the ROP chain so that the shellcode can re-use the opened socket from which the ROP chain read the shellcode received from the attack server.
A command offset is also defined, but we will get back to it later.
The code then defines function prototypes in the manner of a C header file. We have function from standard unix libraries and cygnus libraries, followed by custom function from Broadcom Foundation Classes (BcmConsole):
Once addresses and prototypes are defined, the code maps those:
We have to do that because we cannot execute dynamic linking or execute syscalls :)
Variables Initialization
It initializes the variables it will need:
It redirects standard I/O to our opened socket connection. This is the functional equivalent of calling dup2 on Linux.
Command Loop
Then the code enters a loop to receive and execute commands. This code imitates what serial, telnet, or SSH console handler of the Broadcom platform executes when a session is open.
Note: If you like reversing, the commands are received and executed by a dedicated Thread (BcmEcRemoteTerminalConsoleThread -> BcmEcTerminalConsoleThread -> BcmTerminalConsoleThread).
Command Offset
One thing I noticed during my experiences with this platform is that the command offset is not alway the same. For example, it is 0x107d on Sagemcom devices but 0x1080 on Netgear and ASKEY.
If you’re looking for BcmConsoleExecuteCurrentCommand, cross-reference the string ‘is not a valid command table’. The function that references that string is BcmConsoleExecuteCurrentCommand. You can then cross-reference calls to that function and identify the proper command offset that you need to use for your target.
Compiling Shellcode
To compile C code for our target you’ll need to obtain the right toolchain. You can either download it from the Internet if you’re brave enough. Or build it yourself such as described in eCos Firmware Analysis with Ghidra (remember, we needed the toolchain to build eCos shared libraries).
Once your toolchain is set, you can compile it to an object file with gcc and then exporting a raw binary file from it:
Relative Jump Issues
If we use this shellcode as-is, we’ll encounter one problem: the device crash after executing the first command we send.
This is the exact crash I got:
Let’s decompile our shellcode with radare2 to understand what’s happening !
The problematic instruction is at 0x000000e8, which is the jump responsible for our while(1) loop:
Given that the processor has no knowledge of the fact that our ‘function’ starts at offset 0x864df1e8 it will think it needs to jump to 0x80004000 (base address) + 0xa8 = 0x800040a8. And that’s why the device crash with a PC address of 0x800000a8.
To fix this problem, we need to make this shellcode location aware and make it perform direct jumps rather than relative jumps.
To do so, we need to get the jump destination address. This is the shellcode start address + the relative offset where the loop starts (0x864df1e8 + 0xa8 = 0x864df290).
So we should have something along these lines
I wrote the script below to fix this issue. For now only the fixed address can be provided but I plan on adding support to replace arbitrary relative jumps in any shellcode for Broadcom eCos.
Method 2: GCC Linker
If you looked at @stdwcode to turn a Cable Modem into a cheap SDR, you probably know what the build process will look like. Interestingly, I had built similar capabilities but turns out their code is cleaner so I borrowed from them. Go check out their repo !
Instead of manually typing function addresses in the source code, redefining the function prototypes, and setting functions pointers, we can simply rely on GCC -T option that allows us to provide our own linker script.
If you want to follow along, all the content below has been made public on our Github.
Makefile
We can combine that with a Makefile to make things even easier:
Setup the right application binary interface options:
march=mips32
mabi=eabi
msoft-float
Remove compiler optimizations:
mno-abilcalls - Do not generate code that is suitable for SVR4-style dynamic objects.
fno-builtin - Tells the compiler not to use generic handling and optimization of standard C and C++ library functions and operators.
Do not use standard libraries when linking:
nostdlib - Do not use the C library or system libraries tightly coupled with it when linking.
nodefaultlibs - Do not use the standard system libraries when linking.
nostartfiles - Do not use the standard system startup files when linking.
Linker Script
The linker script will contain the addresses of functions you plan on using and the SECTIONS definition set the start address where your shellcode should be written to in memory:
Shellcode
This time let’s setup a bindshell:
The code is way cleaner than the initial method, right ?
Calling
The shellcode has been designed to accept parameters from the command line when called using the call CLI command:
Conclusion
You now have all the tools to write your own reverse shell shellcode and deliver it to your ROP chain handler. But you can do much more as long as you understand the eCos architecture !
I created a repository with a bunch of shellcodes (reverse shell, bind shell, reverse shell in a thread, bind shell in a thread, sample threading app) and build instruction on Github. The repository contains a piece of Python code to write shellcode in memory over serial so that you can call and debug your code from the CLI.
As always, if you have any question feel free to contact me via Twitter or email.