深入理解ret2libc
0x00 前言
针对ret2libc内一类需要计算地址的题型。
这类题型一般开启了PIE,binary内部有一些output函数或者binary内部没有system('/bin/sh')
的地址,但给出了libc.so。
0x01 概念
ret2libc 劫持binary执行system('/bin/sh')
。
0x02 劫持过程
一般有一个溢出点,要进行两次劫持。
第一次劫持是为了泄露出某个函数地址,第二次劫持是为了控制binary返回到libc中执行system('/bin/sh')
。
第一次劫持需要构造两个条件:
- 控制binary,为第二次劫持布局。
- 寻找output函数和待泄露函数。
0x03 布局
在第一次劫持中,如何控制binary,为第二次劫持布局?
首先,为完成第一次劫持,需要在溢出偏移空间内填满垃圾数据,直到覆盖ebp位置。
为泄露出某个函数地址,需要output函数,也即eip位置应该为output函数的plt地址。
binary运行到此处时,会进入output函数的栈帧,在进入栈帧前,根据栈帧平衡原则,需要将“老”ebp压入栈帧,
然后压入函数参数,再调用函数,也即这个“老”ebp位置应为binary在最初运行时的一个函数地址。
一般是main
函数,也可为其他的。目的是在泄露完函数地址返回时,“老”ebp会被弹出来,作为下一跳eip执行,从而使得binary再次可以被劫持。
这个output函数的特征必须为可以输出地址数据的函数。
函数参数位置应该为待泄露函数的got地址数据。
1 | payload1 = '\x90'*offset+[output_addr]+[first_addr]+[link_addr] |
0x04 寻找
如何寻找output函数和待泄露函数?
对于output函数,特征是可以输入地址数据的函数,一般是puts
和write
。
需要注意一点是,output函数的参数。
puts
函数的参数,就一个。而write
函数,其为write(1,addr,4)
,具有3个参数,后面4是泄露的字节(32位为4,64位为8)。
对于待泄露函数,一般没什么规定,只要binary中存在的就好。不过为比赛好记,建议泄露__libc_start_main
。即__libc_start_main
的got地址数据。
0x05 计算
在泄露出__libc_start_main
的got地址数据后,如何计算出libc内的system
地址和/bin/sh
地址?
libc中每个函数地址与libc的基地址和实际binary中的真实函数地址,具有以下关系:
1 | libc_base = func_addr - func_libc_addr |
函数真实地址func_addr
已经泄露出来,func_libc_addr
也可通过查询打到。
ps:func_libc_addr
的确定
如给出libc.so,直接查询。如没有,可以通过func_addr
地址的最低12位来确定libc.so的版本进而查询函数地址。
由此,可以计算出system('/bin/sh')
的实际地址:
1 | libc_base = link_addr - link_libc_addr |
0x06 劫持
在准备好system('/bin/sh')
的实际地址后,第二次劫持直接劫持binary执行system('/bin/sh')
。
1 | payload2 = '\x90'*offset+[system_addr]+[0xdeadbeef]+[binsh_addr] |
0x07 模板
exp模板:
1 | from pwn import * |
0x08 例子
以XCTF-level3为例
checkup:
exploit:
1 | from pwn import * |