Shellcode for Beguinners

Introdução

O Shellcode é um grupo de instruções que podem ser executadas enquanto outro programa está rodando. Hoje em dia existem vários exemplos mostrando como um shellcode pode ser executado enquanto uma aplicação é executada assim como utiliza-los para exploitar vulnerabilidades. Para conseguir tirar vantagem de uma falha de segurança é indispensável injetar um shellcode, pois devemos tomar controle da aplicação vulnerável.

O Objetivo do artigo não é explicar todas as possibilidades de injetar shellcodes mas sim, analisar e entender sua essencia.

Registers

Antes de analisar o código assembly e os binários, é necessiário um overview dos registers da CPU a fim de entender sua importancia na linguagem assembly. A arquitetura que iremos utilizar para teste é a Intel-x86. Todos os registers da plataforma Intel suportam 32 bits e podem ser divididos em sub-sessões de 16 e 8 bits, simplesmente para fazer um uso heuristico da memória.

32 bits 16 bits 8 bits (alto) 8 bits (baixo)
EAX AX AH AL
EBX BX BH BL
ECX CX CH CL
EDX DX DH DL

EAX, AX, AH, AL : Estes registers são chamados acumuladores e podem ser usados para operações de Input ou Output ou para executar Interrupt Calls. Veremos como é indispensável o uso deles quando precisamos efetuar operações com System Calls.

EBX, BX, BH, BL Estes registers são os registers básicos e são utilizados como ponteiros básicos para acessar a memória. Usamos estes registers para passar system call arguments.

ECX, CX, CH, CL Estes registers são os chamados “contadores”

EDX, DX, DH, DL Estes registers são registers de dados e eles podem ser utilizados para operações aritiméticas, interrupt calls e também para I/O.

Pequena introdução a Linguagem Assembly

A linguagem assembly que iremos ver um pouco é chamada “Inline Assembly” e adota a sintaxe da AT&T. O nome dos registers é precedida pelo simbolo “%”, sendo assim se quisermos utilizar o register eax devemos usa-lo como “%eax”. Se vamos nos referir à constantes numéricas, este valor deve ser precedido pelo simbolo “$”. Seguindo estes esquemas, vamos ver as principais instruções utilizadas em Assembly:

MOV – Esta instrução nos permite mover um valor para um register.
mov $0x4, %al – move 0x4 para al
mov %eax, %ebx – move o que existe em eax para ebx

PUSH – Coloca um valor na Stack.

POP – Pega um valor vindo da Stack e guarda em um register ou em uma variávele.

INT – interrupt call.
int $0x80 – Isto da o controle do Kernel.

Codification phase

O algoritmo que iremos implementar em Assembly e o código binário (versão hexadecimal) é o print no vídeo da string “WWW.ROSIELLO.ORG”.

A solução do problema em C é o pedaço de código a seguir:

int main()
{
write(0, “WWW.ROSIELLO.ORG”, 16);
exit(0);
}

Para efetuar o write() e exit() nos temos que executar suas System Calls. Isto é possível de ser encontrado na library Linux “unistd.h” que é onde ficam guardadas todas as system calls que podemos utilizar

In order to realize the write() and the exit() we have to exe- cute their system calls. It is possible to find in Linux the library “unistd.h” where are stored all the system calls that one can use.
Agora vamos tentar implementar esta instrução em Assembly.

xor %eax, %eax <- isso limpa o register %eax
xor %ebx, %ebx
xor %edx, %edx
push %eax <- Insere NULL na stack fechando a string, porém, nenhum caracter perdido vai aparecer.
push $0x47524f2e #push GRO. into the stack
push $0x4f4c4c45 #push OLLE into the stack
push $0x49534f52 #push ISOR into the stack
push $0x2e575757 #push .WWW into the stack

Os 4 push acima inserem na Stack a string “www.rosiello.org” na codificação hexadecimal.

mov %esp, %ecx # it moves %esp into %ecx

Agora o endereço da String está no register %esp (lembre-se que que o esp é incrementado ou decrementado apenas com pop/push) e nós colocamos ele no register %ecx, assim a CPU conseguirá encontrar a posição mais apropriada da string na stack (write(o,string, ..)).

mov $0x10,%dl #size 16 bytes

Exatamente como na linguagem C nós indicamos que o tamanho da string é 16 bytes (write(0, string, 16)).

mov $0x4,%al #syscall for write()

Colocamos no register eax o núermo da rotina write().

int $0x80 # execute the syscall

Agora o kernel irá ter o controle da aplicação e irá executar a rotina write().

exit(0):

xor %eax, %eax
xor %ebx, %ebx
eax e ebx estão limpos :)

mov $0x1, %al # syscall para exit()

Vamos inserir o valor do exit em al.
int $0x80 # Passamos então o controle para o kernel.

Compile and Execute

O último passo a fazer é a codificação em código binário. Para fazer isso usaremos o Gnu Debugger (gdb).

h4ck@matrix:~shellcode$ gdb teste01

(gdb) disas main
Dump of assembler code for function main:
0x80482f4 : push %ebp
0x80482f5 : mov %esp,%ebp
0x80482f7 : sub $0x8,%esp
0x80482fa : and $0xfffffff0,%esp
0x80482fd : mov $0x0,%eax
0x8048302 : sub %eax,%esp
0x8048304 : xor %eax,%eax
0x8048306 : xor %ebx,%ebx
0x8048308 : xor %edx,%edx
0x804830a : push %eax
0x804830b : push $0x47524f2e
0x8048310 : push $0x4f4c4c45
0x8048315 : push $0x49534f52
0x804831a : push $0x2e575757
0x804831f : mov %esp,%ecx
0x8048321 : mov $0x10,%dl
0x8048323 : mov $0x4,%al
0x8048325 : int $0x80
0x8048327 : xor %eax,%eax
0x8048329 : xor %ebx,%ebx
0x804832b : mov $0x1,%al
0x804832d : int $0x80
End of assembler dump.

Para ganhar o opcode você deve proceder da seguinte forma:

(gdb) x/bx main+16
0x8048304 : 0x31 <- OPCODE
(gdb)
0x8048305 : 0xc0 <- OPCODE
(gdb)
0x8048306 : 0x31 <- OPCODE
….
Agora é indispensável colocar qualquer como o padrão “x31xc0x31..”.
“x31xc0x31xdbx31xd2x50x68x2ex4f”
“x52x47x68x45x4cx4cx4fx68x52x4f”
“x53x49x68x57x57x57x2ex89xe1xb2”
“x10xb0x04xcdx80x31xc0x31xdbxb0”
“x01xcdx80”

Para compilar e executar o shellcode você pode organiza-lo no programa em C da seguinte forma:

h4ck@matrix:~shellcode$ cat shellcode.c

#include

char shellcode[]=
“x31xc0x31xdbx31xd2x50x68x2ex4f”
“x52x47x68x45x4cx4cx4fx68x52x4f”
“x53x49x68x57x57x57x2ex89xe1xb2”
“x10xb0x04xcdx80x31xc0x31xdbxb0”
“x01xcdx80”;
main()
{
void (*routine) ();
(long) routine = &shellcode;
printf(“Size: %d bytesn”, sizeof(shellcode));
routine();
}

h4ck@matrix:~shellcode$ gcc shellcode.c -o shellcode
h4ck@matrix:~shellcode$ ./shellcode
Size: 44 bytes.
WWW.ROSIELLO.ORG

Conclusão

Fazer um shellcode não é dificil, mas você vai precisar de paciência e prática para ficar bom nisto. O shellcode é muito importante para exploitar diversas falhas de segurança uma vez que sem ele você simplesmente vai ocasionar um Buffer Overflow é gerar um Core Dump no sistema :)

Referências: http://www.rosiello.org :)

[]s

Advertisements

3 responses to “Shellcode for Beguinners

  1. Pingback: ShellCode « Security Down

  2. Pingback: ShellCode | Brenn0 WeBlog

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s