Ataques, Programação

Escrevendo um payload e analisando um do Metasploit

Tempo de leitura: 5 minutos

Um payload (shellcode) para Linux é criado usando as syscalls deste sistema. Eu queria criar um payload simples que apenas executasse o comando “ls -l”. Só isso. Como fazer isso? Entrei no site syscalls.kernelgrok.com e vi o que era necessário para executar o “sys_execve”. Os passos são:

  1. Salvar “0x0B” em EAX
  2. Salvar em EBX um endereço de memória que contenha uma string com o executável que eu desejo executar
  3. Salvar em ECX o endereço de um ponteiro de ponteiro (matriz multidimensional) que contenha os argumentos que serão passados para o executável. A documentação do Linux diz que este valor pode ser zero, mas se tiver argumentos a serem passados, ao final do array multidimensional é necessário ter um “ponteiro nulo”
  4. Salvar em EDX o endereço de um ponteiro de ponteiro (matriz multidimensional) que contenha possíveis variáveis de ambiente que o executável deverá usar. A documentação do Linux diz que este valor pode ser zero, mas se tiver argumentos a serem passados, ao final do array multidimensional é necessário ter um “ponteiro nulo”

Criando o payload em C primeiro

Para simplificar um pouco mais, criei um código em C que chame a função “execve” que executa a syscall que quero chamar. Em um compilador C online qualquer (digitei “C compiler online” sem as aspas no Google e entrei no primeiro resultado) testei o seguinte código seguindo a documentação da função execve ( http://man7.org/linux/man-pages/man2/execve.2.html ):

#include <stdio.h>

int main(){
    
    char sh[] = "/bin/sh";
    char *args[] = {"/bin/sh", "-c", "ls -l"};
    execve(sh, args,0);

    return 0;
}

Este código deu um “warning” falando da declaração implícita da função. Esse “warning” me diz que esqueci de colocar a biblioteca que deveria ser usada. A documentação me diz que a declaração dessa função está em unistd.h. Ok. Adicionei essa biblioteca no meu código, ficando assim:

#include <stdio.h>
#include <unistd.h>

int main(){
    
    char sh[] = "/bin/sh";
    char *args[] = {"/bin/sh", "-c", "ls -l"};
    execve(sh, args,0);

    return 0;
}

Agora parece correto, mas ainda não estava retornando a lista de arquivos e pastas do diretório que estou… Por que? Li novamente a documentação e vi que “ao final do array multidimensional é necessário ter um ponteiro nulo”. Talvez seja isso. Adicionei um NULL ao final da matriz multidimensional de argumentos. Meu código ficou assim:

#include <stdio.h>
#include <unistd.h>

int main(){
    
    char sh[] = "/bin/sh";
    char *args[] = {"/bin/sh", "-c", "ls -l", NULL};
    execve(sh, args,0);

    return 0;
}

Aháááá! Agora deu certo! Pela descrição dos registradores para chamar a syscall, sei que EAX precisa ter “0x0b” (11 em hexadecimal), em EBX estará o endereço do primeiro argumento da função execve, em ECX estará o endereço da matriz multidimensional que enviei para o segundo argumento e em EDX terá zero.

Pronto. Tudo está pronto para criar o código em Assembly. Já entendi perfeitamente todo o necessário. Vamos nessa!

Passando o payload para Assembly

Para criar um payload/shellcode confiável, eu preciso empurrar as strings para a stack, pegar os endereços de cada string para enviar para os registradores, colocar 0x0B em EAX e criar uma interrupção 0x80 para executar a syscall. Tudo isso gerando o menor código de máquina possível. Ao fim de algum tempo tentando diminuir o código, consegui chegar a isso:

section .text
global main
global _start

    main:
    _start:
        mov eax, 0x0b   ;Aqui copio 11 para EAX
        push 0x0000006c ;Empurro para a stack o valor 
                        ;hexadecimal de "l" na tabela 
                        ;ASCII. A stack funciona de 
                        ;trás para frente
        push 0x2d20736c ;Empurro para a stack o valor 
                        ;hexadecimal de "ls -" na tabela 
                        ;ASCII. A stack funciona de trás 
                        ;para frente
        mov edi, esp    ;Em ESP tem o endereço de onde 
                        ;começa a string "ls -l" (ou 
                        ;seja, ESP tem um ponteiro). 
                        ;Copio o valor de ESP para EDI.
		        ;Então em EDI tem um ponteiro 
                        ;para a string "ls -l"
        push 0x0000632d ;Empurro para a stack o valor 
                        ;hexadecimal de "-c" na tabela 
                        ;ASCII. A stack funciona de 
                        ;trás para frente
        mov edx, esp    ;Em ESP tem o endereço de onde 
                        ;começa a string "-c" (ou 
                        ;seja, ESP tem um ponteiro). 
                        ;Copio o valor de ESP para EDX.
		        ;Então em EDX tem um ponteiro 
                        ;para o argumento "-c"
        push 0x0068732f ;Empurro para a stack o valor 
                        ;hexadecimal de "/sh" na tabela 
                        ;ASCII. A stack funciona de trás 
                        ;para frente
        push 0x6e69622f ;Empurro para a stack o valor 
                        ;hexadecimal de "/bin" na 
                        ;tabela ASCII. A stack funciona de 
                        ;trás para frente
        mov ebx, esp    ;Em ESP tem o endereço de onde 
                        ;começa a string "/bin/sh" (ou 
                        ;seja, ESP tem um ponteiro). Copio 
                        ;o valor de ESP para EBX.
                        ;Então em EBX já tem o valor da 
                        ;string do meu executável. Aqui 
                        ;tá pronto
        push 0x00000000 ;Agora vou começar a preparar 
                        ;a stack para conter todos os 
                        ;meus argumentos. Aqui empurro 
                        ;zero para a stack porque
                        ;"ao final do array multidimensional 
                        ;é necessário ter um ponteiro nulo"
        push edi        ;Em EDI tem o ponteiro para 
                        ;"ls -l". Empurro ele para 
                        ;a stack
        push edx        ;Em EDX tem o ponteiro para "-c". 
                        ;Empurro ele para a stack
        push ebx        ;Em EBX tem o ponteiro para "/bin/sh". 
                        ;Empurro ele para a stack
        mov ecx, esp    ;Em ESP tem um ponteiro para o 
                        ;array multidimensional. Copio esse 
                        ;ponteiro para ECX
        mov edx, 0      ;Copio zero para EDX porque não 
                        ;usarei variáveis de ambiente
        int 0x80        ;Gero a interrupção 0x80

Pronto! Salvei esse código como teste.asm. Para gerar o “código objeto” no Linux, usei o nasm com os seguintes comandos:

nasm -f elf32 teste.asm

Para linkar, usei:

ld -s -o teste teste.o

Executei ./teste no terminal e lá estava a listagem de arquivos e pastas do meu diretório atual! Deu certo =D

Meu código gerou o seguinte código de máquina:

“\xB8\x0B\x00\x00\x00\x6A\x6C\x68\x6C\x73\x20\x2D\x89\xE7\x68\x2D\x63\x00\x00\x89\xE2\x68\x2F\x73\x68\x00\x68\x2F\x62\x69\x6E\x89\xE3\x6A\x00\x57\x52\x53\x89\xE1\xBA\x00\x00\x00\x00\xCD\x80”

Consegui um shellcode de 47 bytes! Legal! Bem reduzido! Qual o tamanho que o metasploit gerou? 41 bytes… 6 bytes a menos… Como? Vamos analisar!

Analisando o payload do Metasploit

O payload do Metasploit linux/x86/exec que execute “ls -l” é este:

\x6A\x0B\x58\x99\x52\x66\x68\x2D\x63\x89\xE7\x68\x2F\x73\x68\x00\x68\x2F\x62\x69\x6E\x89\xE3\x52\xE8\x06\x00\x00\x00\x6C\x73\x20\x2D\x6C\x00\x57\x53\x89\xE1\xCD\x80.

Digitei “hex to assembly” no Google e entrei no primeiro site que apareceu ( https://defuse.ca/online-x86-assembler.htm ). Digitei este código hexadecimal e me retornou este código:

0:  6a 0b                   push   0xb
2:  58                      pop    eax
3:  99                      cdq
4:  52                      push   edx
5:  66 68 2d 63             pushw  0x632d
9:  89 e7                   mov    edi,esp
b:  68 2f 73 68 00          push   0x68732f
10: 68 2f 62 69 6e          push   0x6e69622f
15: 89 e3                   mov    ebx,esp
17: 52                      push   edx
18: e8 06 00 00 00          call   0x23
1d: 6c                      ins    BYTE PTR es:[edi],dx
1e: 73 20                   jae    0x40
20: 2d 6c 00 57 53          sub    eax,0x5357006c
25: 89 e1                   mov    ecx,esp
27: cd 80                   int    0x80

A primeira diferença que surtiu resultado é o “push 0x0b; pop eax”. O meu “mov eax, 0x0b” gerou 5 bytes (b8 0b 00 00 00), já o “push 0x0b; pop eax” gerou apenas 3 (6a 0b 58).

A segunda diferença que mais visível foi usar o “cdq”. Esta instrução significa “Convert Double to Quad”. Para isso, ela pega o bit de sinal de EAX e preenche EDX com ele. O bit de sinal de EAX é zero, então EDX será zerado com uma instrução de 1 byte apenas (CDQ vale 99), enquanto meu “mov edx, 0” ocupa 5 bytes (ba 00 00 00 00).

Com essas duas pequenas mudanças, o payload do Metasploit conseguiu diminuir 6 bytes em relação ao meu! =D

Bônus

Na última parte do payload, que é preparar a stack para conter os endereços onde estão as strings que serão passadas como argumentos para o executável, o Metasploit tem algo interessante:

  1. Ele empurra zero para a stack “push edx”
  2. Chama a instrução que está no endereço offset 0x23. Isso ficará no meio da linha com 20 na frente. Portanto o código irá para “57 53 89 e1 cd 80”. Portato a instrução “sub eax,0x5357006c” muda, já que o código literalmente pula dois bytes da instrução. Os bytes 57 e 53 equivalem respectivamente a “push edi” e “push ebx”. Uma instrução de “call” empurra para a stack o endereço do próximo byte porque quando a função for finalizada, o processador precisa saber para onde voltar. Portanto “call 0x23” empurra para a stack o endereço onde começa a instrução “ins    BYTE PTR es:[edi],dx”. Mas espera, este call pula os bytes “6c 73 20 2d 6c 00” e coloca na stack o endereço de “retorno” para 6c. Analisando esses bytes na tabela ASCII, vejo que “6c 73 20 2d 6c 00” equivale a “ls -l”. Ou seja, Ele não empurrou “ls -l” na stack como eu fiz porque não precisava! Ele já estava no código! Assim o autor deste payload só precisou empurrar o endereço onde estava esta string e a instrução “call” resolveu isso com perfeição. Muito legal! =D

O que achou dessa análise? Me conta!

Abraços e até a próxima

Leave a Comment

O seu endereço de e-mail não será publicado.