CTF-PWN-栈溢出-中级ROP-【BROP-1】

文章目录

  • BROP(Blind ROP)
  • 攻击条件
  • 攻击路线
  • 三种gadget
  • 函数参数构造
  • 利用PLT
    • 构造rdx的参数的gadget
    • 找输出函数的plt
    • 输出二进制文件内容
    • 利用plt表中存在跳转到got表中相应的地址

BROP(Blind ROP)

即没有得到源码或者可执行程序文件的情况的文件下,只有一个提供的功能端口进行攻击

BROP(Blind ROP) 于 2014 年由 斯坦福大学 的 Andrea Bittau 提出,其相关研究成果发表在 Oakland 2014,其论文题目是 Hacking Blind,下面是作者对应的 论文 和 PPT, 以及作者相应的介绍

  • 论文
  • PPT讲解-很值得看看的

攻击条件

  1. 存在栈溢出漏洞,并知道如何触发
  2. 服务器进程在崩溃后重启, 并且重写启动的进程的地址与先前的地址一样(这也就是说即使程序有 ASLR 保护,但是其只是在程序最初启动的时候有效果)。目前 nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的)

CTF-PWN-栈溢出-中级ROP-【BROP-1】_第1张图片

攻击路线

目前,大部分应用都会开启 ASLR、NX、Canary 保护。我们需要做的就是如何去绕过这些防护
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第2张图片

  1. 暴力枚举判断栈溢出长度
  2. 暴力尝试攻破canary,因为我们由之前的利用的条件二知道每次crash后重启的程序canary依然不变,所以可以按照字节爆破,每个字节8位,共有256种可能,我们可以一种一种字节可能去尝试,如果遇到没有报错的,说明此时该字节正确,然后再去尝试下一个。所以32位情况尝试1024次,64位尝试2048次
  3. 暴力枚举写返回地址找到能返回到主函数的地址
  4. 首先找到gadget,可以找libc_cs_init的gadget来利用,从而控制要调用函数的参数
  5. 首先利用得到的gadget构造参数,然后暴力枚举地址作为返回地址,此时存在地址对应的指令可能正好是输出函数,那么此时经过之前构造的参数,会输出对应的内容,如存在这些内容,说明找到了输出函数如write或者puts或者printf
  6. 此时再将所有地址的内容都输出,并写到一个自己建立的文件中,然后将该文件反汇编,可以看到该程序的汇编代码,此时找到对应输出函数的plt表的内容,可以找到 jmp 输出函数在got表中的地址 该指令,然后记录该输出函数在got表中的地址
  7. 然后将该输出函数在got表中的地址作为参数,返回地址为输出函数进行输出。可得到输出函数的地址
  8. 通过输出函数的地址找的对应的glibc版本,并计算出基地址,然后得到system的地址和/bin/sh的地址
  9. 此时再次输入,此时构造参数为/bin/sh的地址,返回地址为system的地址,从而getshell

三种gadget

PIE(Position-Independent Executables):位置无关的可执行文件,和Windows下的ASLR(Address Space Layout Randomization)机制类似,PIE enabled表示程序开启地址随机化选、意味着程序每次运行的时候地址都会变化。主要是为了解决二进制本身地址已知的问题,可用来防御return2elf和其他已知地址读写问题。

默认情况下,PIE未开启,x86加载的基地址为0x8048000,而x64加载的基址为0x400000。

开启PIE后,elf中相对偏移不变,但加载机制每次均变化。

probe就是用去暴力枚举的地址或者说探测的地址,
stop就是不会是程序crash的地址
trap就是会使程序crash的地址

从在栈上的返回地址开始布置不同的stop和trap可以识别执行的指令,因为执行stop意味程序不会崩溃,而trap意味着程序会崩溃,所以可以判断该指令的ret在哪个位置

  • probe,stop,traps,traps……
    通过不断改变probe去尝试,此时不会崩溃的是最后使得栈顶没有变化且最后会执行ret指令的指令序列
    ret
    xor eax,eax;ret
    push eax;pop ebx;ret
  • probe,trap,stop,trap, trap……
    此时通过不断改变probe的值,可以找到最后使栈顶元素少去一个,并且在ret之前栈顶不改变的情况下再ret的指令流
    pop rax;ret
    pop rdi;push rax;pop rsi;ret
  • probe,trap,trap,trap,trap,trap,trap,stop,trap,trap……
    此时可以找到含ret的指令,且在将进行ret之前,此时栈顶位置少去6个单位
    pop rax;pop rdi;pop rsi;pop rdx;pop rbx;pop r14;ret

但此时注意如果probe是_start函数的开始地址都不会crash
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第3张图片

函数参数构造

此时如果需要用到write需要如下的gadget
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第4张图片
对于参数可通过libc_csu_init
此时libc_csu_init中有一下子弹出 6 个寄存器的 gadgets,程序中并不经常出现。所以,如果我们发现了这样的 gadgets,那么,有很大的可能性,这个 gadgets 就是需要的gadgets。此时可通过错位构造得到想要的gadget
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第5张图片
找gadget对于不同指令需要的排列的probe stop trap也不同
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第6张图片
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第7张图片
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第8张图片

利用PLT

当probe为plt上某个函数的区间部分时,会执行这个给函数
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第9张图片

构造rdx的参数的gadget

由于此时需要的gadget得有pop rdx;其最后还需有ret或者其他能间接将值转移到rdx得指令。但几乎没有
但此时是能够用调用strcmp函数后rdx是字符串长度值这个特性来修改rdx (有待商榷)

我们知道strcmp(para1, para2),只有para1和para2都是有效地址时,strcmp()函数才能够正确执行。我们前面已经知道了两个可用的gadget,csu_gadget1 + 7 和csu_gadget1 + 9。因此我们可用将这两个地址分别作为para1和para2,然后依然尝试probe判断是否成功执行,如果成功执行,说明找到了strcmp()。当然啦,也有可能找到strncmp()和strcasecmp()函数,但是它们和strcmp()有一样的效果。

CTF-PWN-栈溢出-中级ROP-【BROP-1】_第10张图片

找输出函数的plt

依然是构造好参数,但此时可以构造rdi,rsi,rdx了,此时只需要尝试probe如果输出参数对应的内容即找到了输出函数的plt的位置

输出二进制文件内容

然后构造好参数,利用输出函数输出二进制文件所有地址的内容。此时相当于没有blind,有源文件了。此时知道了plt表中的所有函数,那么可以调用不同函数了如read函数

利用plt表中存在跳转到got表中相应的地址

此时得到二进制文件后,找到输出函数的对应plt位置,找到其中跳转指令中存在输出函数在got表的地址,然后再次输出,此时参数为输出函数在got表中的地址,此时可以得到输出函数的真实地址,最后得到libc基地址并计算出/bin/sh的地址和system的地址,如果没有此时可以利用read函数或write函数写入可写的内存,参数自己构造,所以知道地址,然后调用系统调用或system都行,只不过系统调用还需构造其他参数
CTF-PWN-栈溢出-中级ROP-【BROP-1】_第11张图片

你可能感兴趣的:(CTF-PWN-栈溢出,PWN)