DEF CON Quals 2026 - mapllvm (WIP)
It takes the finest maple syrup to make a compiler like this
Challenge Overview
Category: Binary Exploitation | Points: 290 | Solves: 33
mapllvm 是一个用 Racket 编写的简单函数式语言编译器,将类 Scheme 源码编译为 x86-64 汇编,链接自定义 GC 后执行。服务端接受 <=2000 字符的源码,编译运行在 nsjail 中。flag 位于 /flag。
编译器架构
1 | 源码 (read) → validate → uniquify → closure-conversion → datatype-encoding |
类型系统
- 3-bit tagged pointers: INTEGER(000) / PTR(001) / OTHER(010)
- 堆对象: vector(tag=2), pair(tag=3), closure(tag=4)
- 整数为 61-bit signed,tag 后为
n << 3 - 算术运算无溢出检查(直接使用 x86
add/sub/imul)
内置操作
1 | * + - < > <= >= = != not cons car cdr |
已发现漏洞
1. panic! 可绕过
Validator 调用
validator 试图通过 guard 阻止 panic!:
1 | [`(,op ,es ...) |
(panic! 0) 会被 fallback pattern 捕获并验证通过,后续
pass 中 panic! 作为 source-lang-op
被正确处理。实战验证:(panic! 42) 编译运行,exit
code = 42。
危害:仅能调用 exit(n),无可控代码执行。
2. 算术溢出导致
make-vec OOB 写
整数范围为 [-2^60, 2^60-1]。两个最大值相加:
1 | (+ 1152921504606846975 1152921504606846975) |
(make-vec overflow_value) 中: 1.
gc_alloc(-2+2) = gc_alloc(0) → 最小分配 8 bytes 2. 写
VECTOR-OBJ-TAG 到 offset 0,写 length(-2) 到 offset 1 (OOB) 3.
vec-zero-init 从 ptr+16 开始以
len=-2 递归写 0 到负偏移
zero-init 是一个 tail-recursive 循环: 1
2
3(if (= len 0) UNIT
(begin (mem-write addr (- len 1) 0)
(call vec-zero-init addr (- len 1))))
以 len=-2 进入后永远无法达到
len=0,持续向低地址写 0: - offset -3: 写 block header mark
- offset -4: 写 block header allocated flag (设为 0 = free) - offset -5:
写 block header size (设为 0) - 继续向下直到 segfault
服务端验证:(make-vec (- 0 1)) → exit code -11
(SIGSEGV)
利用障碍
- zero-init 无限循环无法停止(堆地址空间与栈分离,无法改写栈上循环计数器)
- 写入值恒为 0,无法注入任意地址
- 堆不可执行(NX),即使 corrupt closure code label 也无法执行 shellcode
- 无 seccomp(nsjail 配置无线程限制外的 syscall 过滤),但缺少可利用的代码执行原语
- 内部操作 (
closure-env-set等) 无法从源码调用(uniquify pass 产生 match error)
待探索方向
- GC metadata 破坏后能否通过大量分配触发 GC → UAF/double-alloc
- nsjail mount namespace 逃逸(/flag 为 bind mount,/dev 可读写)
- Racket
read的 reader extension 注入(#reader等) - 编译器 pass 间的类型混淆(如 closure 与 vector 的 tag 互换)