D-Link: A Firmware Security Analysis – Part 4

by Nov 6, 2019

Firmware Exploitation

This is the part you’ve been waiting for right? We’ve downloaded a firmware, scoured through it for hours, found a vulnerability, emulated it, and now it’s time to write an exploit. Our goal is simple: to gain control of the camera and make it do our bidding. Or at least run a command we supply.

In part three we overwrote the return address by sending an arbitrarily long string by holding down the A key for a while. This approach, while easy, is not very scientific and probably won’t result in reliable control over where we jump. Whip out your calculator, it’s time to do some math.


Understanding the Stack and Return Addresses

Before we get into overwriting the return address, it’s useful to know where the return address is. But first, let’s go over how the stack is laid out when the vulnerable function is called.

The stack will start at some high address, typically I’ve seen 0x7FFFFFFF, and grow down in memory. Reads and writes go up in memory. Now, with that knowledge let’s look at the prologue in the vulnerable function for a practical example.



The first operation, addiu (add immediate unsigned), will move our stack pointer down in memory which is how it allocates space for the function’s local variables. The sw (store word) operations below that are saving registers on the stack so they can be restored after this function is complete. The remaining space on the stack, which is not being used by the registers, is for local variables.

The top of the function’s disassembly in IDA the offsets of variables on the stack and based off the prologue we can rename the variables to be more easily tracked.

Renaming variables on the stack

Renaming variables on the stack

And what the stack actually looks like.

Stack setup

Stack setup


That Little Bit of Math

Now we are ready to figure out how many ’A’s to write to our buffer to test our ability to control execution. Looking back at the strcpy in IDA, we need to find out which stack value is used as the destination buffer.

string copy viewed in IDA

string copy viewed in IDA

Register $a0 is populated with var_30 which is the last piece of the puzzle. We now know where the destination buffer is on the stack, and we also know where the return address is stored.

Simple math time.

Our buffer, var_30, is at offset –30 and the return address, $ra, is at offset –8 so to calculate the distance between the two is:

0x30 minus 0x8 which equals 0x28

If we write 0x28 bytes (40 decimal) into the buffer, it will take us right up to the return address. The next four bytes we write will overwrite the return address.

A wget command combined with IDA debugging will prove if this value is correct. I went back to user mode emulation for this so I didn’t need to set the referrer or provide a password.

wget$(python3 -c "print('A' * 0x28, end=''); print('BBBB')")
Return address filled with BBBB

Return address filled with BBBB

Notice how $ra is now filled with 42424242 (BBBB) instead of 4141414 (AAAA) that we saw in part three? We have successfully overwritten the return address. We can now control execution. The next step is to identify a useful address to which we can jump to instead of 0x42424242.


Return to LibC Attack

The best option for executing a command on this camera: a return to libc attack. Basically, we want to call system with a string–provided by us–that will perform a useful operation. Calling /sbin/reboot to restart the router is a great example. To accomplish this we need a few pieces of information.

  • Address of libc
  • Address of system
  • Address of a gadget to place a string in $a0
  • Offset on the stack to write our command

Finding the Address of LibC

Finding the address of libc is an easy task. Here’s why: the kernel for this firmware is 2.6.21. Library load randomization–a security feature–wasn’t a thing until kernel 2.6.36. Since we have an older kernel, the load address of the library will be consistent every time the server is run.

We need to cat the process mapping for this process. The library for libc is libuClibc–0.9.28.so. Notice how it appears in the mappings three times. We need to chose the executable version so our code executes we when we jump to it.

The executable version is the one with the x permission, or r-xp. Based on the image below, our load address for libc is 0x2AB86000.

Process Mapping

Process Mapping

Finding the Address of System

Finding the address of system requires loading the libc library in IDA and viewing the offset of the system function. Once found, adding that offset to the load address of libc will provide the address of system when the process is running. From the image below and the address for libc, the loaded address of system should be

0x2AB86000 + 0x45080 = 0x2ABCB080
System Call

System Call

Finding the Address of a ROP Gadget

Next we need a gadget, known as a ROP gadget, that will move a stack address into $a0, move an $s register to $t9, and call $t9. This will allow us to call system with a command we provide in the overflow. Sadly, there are no single gadgets that move a stack pointer to $a0 and call an $s register, so this exploit required two gadgets. The two gadgets from libc I chose are:

    | Gadget Name | Gadget Offset | Gadget Summary                 |
    | ROP1        | 0x0004B0F8    | move    $t9, $s0               |
    |             |               | jalr    $t9 ; sub_4A890        |
    |             |               | addiu   $s2, $sp, 0x1E8+var_F8 |
    | ROP2        | 0x00018F40    | move    $t9, $s1               |
    |             |               | jalr    $t9 ; sub_18EB0        |
    |             |               | move    $a0, $s2               |

Because both gadgets came from libc the math to find their loaded address is the same.

  • ROP1 – 0x2AB86000 + 0x4B0F8 = 0x2ABD10F8
  • ROP2 – 0x2AB86000 + 0x18F40 = 0x2AB9EF40

ROP1 will move a stack value from an offset into an $s register. This operation happens in the third line of the gadget addiu $s2, $sp, 0x1E8+var_F8. The string will come from offset 0x1E8 - 0xF8 = 0xF0 and be stored in register $s2.

ROP2 will move a function pointer from an $s register to $t9, move an $s register to $a0, and call $t9. In our case, we will put the address of system in $s1, so it will be moved to $t9. We will place our string in $s2 in the last gadget so that it will be moved to $a0 which will become the the parameter when calling system.

In the end, we need our stack to look like this:

ROP Stack

ROP Stack

Time to Test the Firmware Exploit

The addresses are sent in reverse because of the little endian architecture in the firmware. The new URL looks like this:


If everything works out, this should call system with the parameter /sbin/reboot. If the system reboots, then we know that we can run any commands we want on this camera.

ROP to system

ROP to system



It worked! And I’m in.

Unfortunately, this exploitable vulnerability is present in a variety of D-Link cameras. Sadly, many remain unpatched at the time of this writing.

If you want full source code for this exploit you can grab it on my github here. And if you’re curious, the CVE number for this vulnerability is CVE–2019–10999.

I sincerely hope you enjoyed this how-to on firmware security analysis and exploitation. For more information, you can watch my presentation here on YouTube.


About the Author

Evan Walls is a vulnerability analyst and developer at Tactical Network Solutions, focusing on embedded system exploitation. When he isn’t searching for new exploits, he teaches training courses developed by TNS, including IoT Firmware Exploitation. Prior to working at TNS he was a Windows developer for the Department of Defense. Now that he’s seen the glory and freedom that is Linux he vowed never to use another windows development machine again.

You can follow him on LinkedIn or GitHub.

Recent Posts
How to Compare Two Different Binary Files

How to Compare Two Different Binary Files

One of our favorite new capabilities in the Centrifuge Spring ‘20 release is Firmware Differencing. This is how to compare two binary files quickly and efficiently for Linux, QNX, and VxWorks. But that’s not all it compares!

How to Enforce IoT Security Standards and Compliance

How to Enforce IoT Security Standards and Compliance

With all of these certification standards and compliance regulations, conducting product cyber-security assessments quickly becomes very complicated and expensive. Here’s how to save time and money.