前言
上一节通过一个简单的ret2syscall
题目认识了ROP。本节将通过三道ret2libc
题目继续学习ROP。
学习ret2libc
题目之前需要对plt
和got
有一定的了解。
GOT全称Global Offset Table
,即全局偏移量表。它在可执行文件中是一个单独的section,位于.data
section的前面。每个被目标模块引用的全局符号(函数或者变量)都对应于GOT中一个8字节的条目。编译器还为GOT中每个条目生成一个重定位记录。在加载时,动态链接器会重定位GOT中的每个条目,使得它包含正确的目标地址。
PLT全称Procedure Linkage Table
,即过程链接表。它在可执行文件中也是一个单独的section,位于.text
section的前面。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目实际上都是一小段可执行的代码。
通过下图说明。当第一次调用func函数时。会跳转到该函数对应的PLT处。该函数对应的PLT第一条指令执行它对应的.GOT.PLT里的指令。第一次调用时,该函数的.GOT.PLT里保存的是它对应的PLT里第二条指令的地址;继续执行PLT第二条、第三条指令,其中第三条指令作用是跳转到公共的PLT(.PLT[0])。 公共的PLT(.PLT[0])执行.GOT.PLT[2]指向的代码,也就是执行动态链接器的代码;动态链接器里的_dl_runtime_resolve_avx
函数修改被调函数对应的.GOT.PLT里保存的地址,使之指向链接后的动态链接库里该函数的实际地址;再次调用该函数对应的PLT第一条指令,跳转到它对应的.GOT.PLT里的指令(此时已经是该函数在动态链接库中的真正地址),从而实现该函数的调用。
ret2libc1
本题需要通过gets函数溢出,利用程序中的system
函数和/bin/sh
字符串获取权限。
通过strings
判断程序中是否存在system
函数和/bin/sh
字符串。
1 | root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop# strings ret2libc1 | grep system |
检查保护,发现开启了NX防护,这意味着我们不能在堆栈中执行shellcode。
1 | [*] '/root/pwn/rop/ret2libc1' |
程序反汇编如下
这里我们的具体方法是,通过gets函数传参数溢出。使得main
函数能够return到system
函数的地址,并将/bin/sh
字符串地址传入到system
函数中。
那么如何获取system
函数和/bin/sh
字符串在程序中的地址呢?
我们可以通过程序获取
1 | from pwn import * |
接着我们通过gdb动态调试计算需要溢出的数据大小。通过stack
命令查看eax到ebp的距离。
需要溢出的数据大小为0xffffd5b8 - 0xffffd54c
再加上四字节ebp一共112字节。
ROP链构造如下
之后我们可以构造exp
1 | from pwn import * |
执行程序获取shell
1 | root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop# python3 exp_ret2libc1.py |
ret2libc2
本题和ret2libc1
的区别是,程序中没有/bin/sh
,需要我们自己将/bin/sh
字符串写入到程序中进行控制。
通过反汇编查看我们应该将/bin/sh
写入到bss段的buf2
中。
所以我们的思路是首先ROP到gets函数,在gets函数中将/bin/sh
读取并放入buf2
。之后继续ROP到system@plt
即可。
ROP链构造如下
exp构造如下
1 | from pwn import * |
ret2libc3
本题程序中没有system
函数也没有/bin/sh
字符串,但是提供了so
文件。对于/bin/sh
字符串我们可以用前面的方法写入,但system函数是无法写入的。
然而在linux延迟绑定机制中,当程序调用库函数时,会将libc.so
文件中的函数地址写到程序的got表中,调用时会跳转到got表所写的地址。那么我们如果要调用system函数,就要知道他的got表中的地址,got表中的地址指的就是当系统将libc(动态链接库)加载到内存中时,库中的函数的地址。但libc被加载到的内存的位置是随机的,我们无从得知。
但是,同一版本的libc的两个库函数在libc中的相对位置是不变的,所以如果我们可以知道一个已经执行过的函数的got表地址,然后确定libc的版本,就可以加上和system函数的偏移,从而得到system函数的真实地址,即got表地址。
这里我们拥有一个puts函数,我们可以用puts函数,将一个已经执行过的函数的got表地址打印出来,然后再根据地址获取libc版本,确定偏移,得到真实地址。
首先strings看一下程序中的/bin/sh
字符串,没有找到/bin/sh
字符串,可以用sh
代替,效果是一样的。
1 | root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop/ret2libc3# strings ret2libc3 | grep /bin/sh |
因此ROP链构造如下
溢出数据大小通过反编译查看,得到大小为56+4=60
字节
接着我们需要知道system
函数在程序中的真实地址
1 | from pwn import * |
最终exp构造如下构造如下
1 | from pwn import * |
参考
https://www.bilibili.com/video/BV1854y1y7Ro