Transformar Saída do objdump Em Shellcode

Introdução

Quem já precisou transformar a saída do objdump em uma shellcode, sabe do trabalho que dá. É muito chato pegar byte por byte para montar a shellcode, e em alguns casos, acontece de esquecermos alguns bytes, fazendo a shellcode não funcionar.

Hoje irei apresentar um método usando somente comandos padrões do linux para transformar a saída do objdump em uma shellcode.

Shellcode?

Shellcode é um pequeno pedaço de código usado para explorar uma aplicação vulnerável e na maioria das vezes seu objetivo é rodar uma shell (daí o nome shellcode). Como as shellcodes realizam funções específicas, é muito fácil encontrá-las prontas na Internet. Sites como [2], mantém muitas shellcodes em seus bancos de dados.

Em geral, um programa em C ou em assembly é escrito e em seguida compilado, e à partir dos códigos de máquina desse programa, a shellcode é gerada. Esse processo, feito de forma manual é muito cansativo, principalmente quando a shellcode é grande, e também muito passível de erros.

objdump

objdump, como o nome já sugere, mostra (“dump“) informações de arquivos binários. Essas informações são as mais diversas possíveis, e para esse post a mais importante é a capacidade de mostrar o código de máquina do binário assim como as instruções em assembly correspondentes.

Obtendo a shellcode através do objdump

Esse post é baseado totalmente na ideia apresenta por gunslinger_ em [1]. O que irei fazer aqui é um passo a passo de como a sequência de comandos funciona.

Bem, como exemplo irei usar o seguinte programa em assembly para obter uma shellcode:

.text

.globl _start
_start:

push $0xb
pop %eax
cltd
push %edx
push $0x68732f2f
push $0x6e69622f
movl %esp, %ebx
xorl %ecx, %ecx
int $0x80

O que esse programa faz é executar a syscall execve para executar uma shell (/bin/sh). Vamos compilá-lo:

$ as -gstabs -o prog.o prog.s
$ ld -o prog prog.o

O executável prog foi criado. Agora vamos ver a saída do objdump em cima desse arquivo:

$ objdump -d prog
prog:     file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
 8048054:	6a 0b                	push   $0xb
 8048056:	58                   	pop    %eax
 8048057:	99                   	cltd
 8048058:	52                   	push   %edx
 8048059:	68 2f 2f 73 68       	push   $0x68732f2f
 804805e:	68 2f 62 69 6e       	push   $0x6e69622f
 8048063:	89 e3                	mov    %esp,%ebx
 8048065:	31 c9                	xor    %ecx,%ecx
 8048067:	cd 80                	int    $0x80

A sequência de bytes “6a 0b 58 … cd 80” é a nossa shellcode. Vamos usar o grep para selecionar somente as linhas da shellcode:

$ objdump -d prog | grep '[0-9a-f]:' | grep -v file
 8048054:	6a 0b                	push   $0xb
 8048056:	58                   	pop    %eax
 8048057:	99                   	cltd
 8048058:	52                   	push   %edx
 8048059:	68 2f 2f 73 68       	push   $0x68732f2f
 804805e:	68 2f 62 69 6e       	push   $0x6e69622f
 8048063:	89 e3                	mov    %esp,%ebx
 8048065:	31 c9                	xor    %ecx,%ecx
 8048067:	cd 80                	int    $0x80

Agora vamos deletar os endereços que estão antes do ‘:’:

$ objdump -d prog | grep '[0-9a-f]:' | grep -v file | cut -f2 -d ':'
	6a 0b                	push   $0xb
	58                   	pop    %eax
	99                   	cltd
	52                   	push   %edx
	68 2f 2f 73 68       	push   $0x68732f2f
	68 2f 62 69 6e       	push   $0x6e69622f
	89 e3                	mov    %esp,%ebx
	31 c9                	xor    %ecx,%ecx
	cd 80                	int    $0x80

O que sobrou foram os códigos de máquina e as instruções em assembly correspondentes. Para extrair somente o código de máquina, podemos considerar somente os 6 primeiros campos da linha tendo o espaço como separador.

$ objdump -d prog | grep '[0-9a-f]:' | grep -v file | cut -f2 -d ':' | cut -f1-6 -d' '
	6a 0b
	58
	99
	52
	68 2f 2f 73 68
	68 2f 62 69 6e
	89 e3
	31 c9
	cd 80

Por fim só falta formatarmos essa saída. Primeiro retiramos os espaços desnecessários:

$ objdump -d prog | grep '[0-9a-f]:' | grep -v file | cut -f2 -d ':' | cut -f1-6 -d' ' | tr -s ' ' | tr '\t' ' '
 6a 0b
 58
 99
 52
 68 2f 2f 73 68
 68 2f 62 69 6e
 89 e3
 31 c9
 cd 80

e usamos o sed para adicionar o \x no início de cada byte e as aspas no início e no fim da string. O paste unirá todas as linhas em uma só, usando o delimitador nulo:

$ objdump -d prog | grep '[0-9a-f]:' | grep -v file | cut -f2 -d ':' | cut -f1-6 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//' | sed 's/ /\\x/g' | paste -d '' -s | sed 's/^/"/' | sed 's/$/"/'
"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"

Obviamente esses comandos podem ser melhorados para evitar a chamada de processos desnecessários (ex: um sed atrás do outro), mas preferi usar a forma original, como visto em [1].

Obtendo a shellcode e o assembly como comentário

Sempre é bom incluir as instruções em assembly junto a shellcode para facilitar a leitura da mesma. Pensando nisso, modifiquei os comandos apresentados anteriormente para gerar uma shellcode incluindo as instruções assembly como comentários em C.

$ objdump -d prog | grep '[0-9a-f]:' | grep -v file | cut -f2 -d ':' | tr '\t' ' ' | sed ':a;s/ \([0-9a-f][0-9a-f]\)/\\x\1/;ta;s,^\([^ ]*\) *\(.*\),"\1" /* \2 */,' | column -t

"\x6a\x0b"              /*  push  $0xb         */
"\x58"                  /*  pop   %eax         */
"\x99"                  /*  cltd  */
"\x52"                  /*  push  %edx         */
"\x68\x2f\x2f\x73\x68"  /*  push  $0x68732f2f  */
"\x68\x2f\x62\x69\x6e"  /*  push  $0x6e69622f  */
"\x89\xe3"              /*  mov   %esp,%ebx    */
"\x31\xc9"              /*  xor   %ecx,%ecx    */
"\xcd\x80"              /*  int   $0x80        */

Os comandos até o sed são os mesmos que os anteriores. O sed substituirá cada ocorrência de “<espaco>XX”, sendo XX dígitos hexas, por “\xXX”. Esse processo de substituição é feito usando um loop já que o uso do ‘g’ poderia pegar dígitos hexas nas instruções em assembly, o que não estaria correto. Quando a condição do loop falhar, a segunda substituição será realizada. Basicamente o que ela faz é pegar tudo antes do espaço (a shellcode em si) e adicionar aspas duplas, e o restante da string,  incluir entre os indicadores de comentários em C, /* */. Por fim, o comando column embeleza a saída, criando uma tabela com espaçamentos iguais em todas as linhas.

Conclusão

Esse post foi para mostrar mais uma vez a eficiência das ferramentas padrões do linux e como o tratamento de texto merece também um pouco de atenção por parte dos usuários, por facilitar algumas tarefas entediantes, como obter uma shellcode à partir da saída do objdump.

Referências

[1] Get all shellcode on binary file from objdump, by gunslinger_ (Acessado em: Março/2012)http://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump

[2] exploit-db (Acessado em: Março/2012)
http://www.exploit-db.com/shellcode/

Deixe um comentário