Ret2libc详解 - ZhouYetao

Ret2libc详解

之前因为准备蓝帽杯初赛的原因,重新复习了一下stack的有关知识(但是**这次蓝帽杯初赛没有出pwn!没有pwn!没有pwn!),这边具体把ret2libc的知识具体讲一下。
在开始讲例题之前,先补充一下有关于ret2libc的攻击思路以及如何实现:
1.先讲一下什么是libc.so,libc.so中存放的是所有编译所需要用到的函数。libc中的两个函数的偏移和程序中两个相同函数的偏移是一样的,打个比方在实际函数中的puts()和libc中的puts()函数之间的距离是X,那么程序中的gets()函数和libc中的gets( )函数之间的距离也是X别。小看了这个功能,就是这个简单的功能,可以让我们泄露出函数的真实地址以供我们接下来的使用;
2.所谓的pwn题,归根结底就是最后不管以什么手段让程序运行system(“/bin/sh”)这个函数,所以这里就需要一个GOT-PLT地址覆写的技术,将程序中的一个函数的地址覆盖成system( )函数的地址,然后将函数中的参数覆盖成”/bin/sh”,这样就可以达到最终的目的——getshell。
3.这边做pwn的话,有一个工具非常的好,那就是pwntools这个库,他里面集结了所有的做pwn题可能会用到的工具,比如可以直接在脚本中实现搜索函数的地址,当然还有”/bin/sh”的地址,你想想,如果说pwn的地址的问题你都解决了,那你还有什么是不可以解决的;
4.最后一点就还是有关溢出字节大小判断的方法,有时候光靠看IDA中的字节大小还不一定是一定正确的,还得靠自己的调试,还有一些工具或者脚本的帮助,比如peda或者是pattern.py这个脚本来进行溢出字节的判断。
然后我这里先讲一下32位的ret2libc,也就是32位情况下的ret2libc使用,虽然现在的CTF32位的题目不是很多,但是还是应该要了解一下32位的知识。
这边通过一个例题来和大家讲解一下,我借用的是ctf-wiki中的一个ret2libc的例题3(在wiki中,他运用的是LibcSearcher库求解),这边我运用比较一般的,常用的ret2libc的解法。
先逆向一下这个程序:
32-1.png

还是先给出exp,然后根据着脚本来一步一步讲解吧:
32-exp.png

根据上面的思路来说的话,是不是觉得思路还是很清晰的,一步一步来看:
1.第一步是通过pwntools的内置工具直接泄露出puts( )的PLT和GOT的地址还有在libc中的偏移(这边要重点强调一下什么是偏移,偏移有点像物理中的相对位置的概念,也就是puts( )的偏移也就相对于libc的基地址;更简单的来说,偏移就是假设libc中函数的排列是从0开始排 列);
2.构造payload的那一步是关键,关键之一在于这一部构造rop链可以泄露函数真实地址,关键之二在于这一rop链的构造与你溢出的函数有关。比如这个函数中,你的溢出函数是puts( )函数,那么你构造的payload就必须与puts( )相匹配,首先看看puts( )函数的定义以及函数的参数问题——这边因为是32位的程序,所以payload1的返回地址,也就是main函数的地址要放在GOT表和PLT表之间,这一步的意思就是在运行一遍payload之后可以让程序的返回地址是main函数,也就是可以整个流程再运行一遍,可以理解成手动添加了一个while(1)循环,可以让溢出二次进行甚至有些题目可以多次leak,这一步可以leak出puts函数的真实地址;
3.接下来的binsh的真实地址的计算这一步在运用了工具之后还是很简单那的,然后libc.search(“/bin/sh”).next( )向左移,这个其实就一个距离相同的概念,也就是一开始说的那个距离相同的情况;
4.System( )的真实地址用的是这个方法,如果不理解,你可以把off拆开,用上面的式子代换,你会发现这个和binsh的计算的实质其实是一样的。
5.最后的一步就是执行了system(“/bin/sh”),中间的是一个虚假返回地址,因为程序这步运行完你其实已经拿到了shell权限,所以返回地址你可以随便写,只要是一个正常的32位地址就可以。
也有一些别的程序可能是用的write( )函数,那这个的payload1的构造就比puts( )相比要复杂一些,如果是write( )函数的话,payload就需要这样构造:
payload=”A”*XX+p32(write_plt) + p32(valu_add) + p32(1) +p32(write_got) + p32(4)

讲完了32位的ret2libc,接下来讲一下64位的ret2libc,大多数人觉得64位的要难理解,其实并不全是,至少我觉得在ret2libc上,64位的反而要比32位的ret2libc要好理解,而且rop链的构造也更符合我们的一般思维。
在讲64位之前,我们得稍微提一下64位的栈构造,与32不同的是当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9,也就是我们不能只像32位的程序一样,只对栈上的元素进行操作,所以我们在进行leak地址或是进行payload发送的时候,需要加一步,也就是pop一个寄存器。
这边我的例题就拿铁三比赛的一个pwn题来吧。
先来逆向一波这个程序:

64-1.png

发现这个程序很容易理解,也很容易就找到了问题函数,也就是gets( )函数,具体思路和之前的32位一样,但是这个需要再多加一步,就是需要寻找一下有没有pop的函数。这边我常用的是ROPgadget这个工具,具体的格式是
ROPgadget --binary tie_pwn3(你的具体的程序名) --only “pop|ret”
rop.png

然后这边可以用的一个是pop rdi;ret、pop r15;ret、pop rbp;ret这三个在这个程序中都是可以用的,具体的rop链的构造就是在靠近溢出字节的一侧加上pop的地址,
64-exp.png

这边的的payload构造就和32位的不一样了,这边需要将返回地址放在最后一位,然后再进行下一步的构造,然后这边是直接加上构造所需要的地址,直接累加,最后pop的地址,然后是溢出的字节;
Payload2的构造就更为简单了,直接只需要溢出字节+ p64(pop) + p64(binsh) +p64(sys)就可以了。
同样,如果是write( )函数的话,那就是:
payload1="A"*XX+p64(rdiret_add)+p64(puts_got)+p64(puts_plt)+p64(valu_add)

讲完了两种机制的ret2libc,这边有几点我得说说就是有关于返回地址的问题,不一定是一个main函数,比如有的题目会将漏洞函数放在一个单独的函数中,比如我可以调用一个自己定义的函数,比如下面的这个:
ps1.png

ps2.png

所以这边的valu的地址就是overflow( )函数的地址。

接下来再总结一些常用的函数(在e=ELF(“./程序名”) libc=ELF(“libc.so”)前提下):
e.plt[“puts”]:在程序中puts( )函数的plt表地址;
e.got[“gets”]:在程序中gets( )函数的got表地址;
libc.sym(bols)[“puts”]:在libc中puts( )函数的偏移——相当于是libc从0x0开始生长;
libc.search(“/bin/sh”).next( ):在libc中”/bin/sh”字符串的偏移;
next(libc.search(“/bin/sh”)):同上,不过相比我比较喜欢上面的写法,那样比较清楚
具体的如何判断溢出字节的大小这里我就不介绍了,看ret2libc的朋友这个肯定没有问题的哈。
就讲这么多,今天课多,码到了现在。

2 条评论

  1. Mac 环境下 PWN入门系列(二)_威客安全 非主流浏览器 非主流操作系统 中国 北京 北京

    [...]Ret2libc详解[...]

    1. ZhouYetao Google Chrome 78.0.3904.70 Windows 10 中国 浙江 杭州

Leave a Comment

@author:ZhouYetao
© 2020 Copyright.  | Power by Mijiu                                                                                               
本站已安全运行 940 天