[ Inicio ] [ Hacking ] [ CTFs ] [ Rant ]
.:: Brenn0 Weblog ::.

Título : EZPZ [WPICTF2018] - Write-up
Autor : brennords
Data : 21/04/2018
            

O WPICTF rolou no final de semana passado, do dia 13/04 ao dia 15/04. Não tive tempo suficiente para tentar resolver todos os challs, mas, nos poucos minutos que passei, consegui solucionar o mistério de dois binários. Um deles foi o ezpz.

Nenhuma descrição foi oferecida além do link para o programa e o endereço no qual o mesmo rodava.

nc ezpz.wpictf.xyz 31337 redundant servers on 31338 and 31339 made by awg file from https://drive.google.com/open?id=1RpTW-5_-pv6ShZEDBTAoIB_AiPkb8db9

Ao rodar o negócio, na primeira linha se vê algo sobre “debugging” e aparentemente três endereços de memória. Na segunda, se requisita que seja inserida a senha correta para pegarmos a flag, mas ao inserir qualquer valor, temos um erro de segmentation fault.

1ezpz

Agora temos que debuggar esse bagulho e descobrir o que está havendo. Começo usando o ltrace e observando as chamadas de função, dá para ver que, após a senha ser inserida, é chamada “getenv”, que é uma função responsável por pegar valores das variáveis de ambiente. É bem provável que a razão desse segmentation fault seja o fato de que no meu linux não há variável de ambiente chamada “EZPZPASSWORD”.

2ezpz

Então criei e setei um valor para a variável EZPZPASSWORD e rodei o ezpz mais uma vez.

3ezpz

Olha só, ele até tentou imprimir a flag. O problema é que no meu sistema também não existe uma variável chamada EZPZFLAG. Mas criando e setando uma pode-se ver o resultado:

4ezpz

Já foi possível entender, por cima, o funcionamento do programa. Com certeza essas variáveis estão setadas no servidor que o chall está rodando. Mas saber disso não ajuda tanto. E por ser um desafio de XPL e não REV, provavelmente haveria uma falha a ser explorada e eu ainda não havia percebido onde ela estava.

Quando rodei o programa com o ltrace, saquei que se usava fgets para receber a senha inserida por nós. Então tive a impressão, errada, de que seria inútil buscar um overflow nesse input porque fgets costuma ser segura. Mas ainda é possível usar fgets e estourar o buffer de destino.

Pode ser culpa de um erro de cálculo ou uma falta de atenção, mas pode acontecer. Não é porque fgets recebe como argumento o tamanho do buffer de destino que o tamanho esteja correto. Um erro totalmente humano.

Percebi que era o caso do desafio quando inseri como senha uma longa string de 200”A’s” usando, dentro do gdb, comando: run $(python -c ‘print “A”*200’)

5ezpz

Sobreescrevi totalmente o endereço de retorno. Quando a instrução ret é executada, vai buscar na stack esse endereço, que é onde todos meus A’s estão morando.

Agora, com todo esse poder em mãos, o que fazer?

Após umas tentativas preguiçosas de calcular exatamente quantos bytes eram necessários para chegar exatamente no endereço de retorno (diminui de 200 para 100 A’s, depois para 150… E assim chegar a exatos 136), dei uma olhada com quais esquemas de segurança o binário contava.

6ezpz

Nx não nos deixa executar instruções inseridas na stack, PIE faz com que, falando de forma bruta, cada vez que o programa for rodado, o mesmo seja carregando em posições diferentes da memória. E o partial RELRO protege alguns setores do binário de serem sobreescritos, tipo a Global Offset Table (eu acho).

Certo, isso diminui as opções. Mandei um info functions porque já havia feito um monte de coisas e faltava o básico: análisar o código.

7ezpz

Meu gdb tá com esse problema de encontrar, mesmo que não exista, em alguns binários em particular, o arquivo do source, ainda não consertei essa porra mas é isso aí.

Além de main, há duas funções interessantes: correct_pw e wrong_pw.

Os nomes já são sugestivos, mas fazendo o disassembly para confirmar, dá para ver que correct_pw é a função que vai buscar o valor na variável de ambiente EZPZFLAG e imprimi-lo.

8ezpz

O objetivo agora ficou claro: sobreescrever o endereço de retorno com o endereço de correct_pw.

Seria um clássico desafio de stack overflow igual a vários que já fiz: estourar o buffer e sobreescrever o endereço de retorno com a função que imprime a flag, a não ser pelo pequeno detalhe de que, toda vez que o programa é executado, os endereços são diferentes graças a proteção do PIE.

Mas o binário facilita a vida: essa primeira linha de “debugging” e os três endereços exibidos são, respectivamente, os endereços de correct_pw, wrong_pw e main. Dá para se comprovar isso observando e setando breakpoints em main+21,28 e 35, que é quando são enviados os valores para RCX, RDX e RSI antes de chamar o printf.

9ezpz

Era óbvio o que tinha que se fazer: conectar ao servidor que ezpz rodava, pegar a primeira linha com os endereços, extrair o primeiro endereço, mandar 136 bytes + esse endereço e assim executar correct_pw e ver a flag ser impressa.

Na correria, peguei o código de um exploit antigo e adaptei para o ezpz. Não é bonito mas funciona. Fica óbvio que ainda preciso aprender a usar a pwntools e, após o meu exploit, postarei outro (que não me pertence) com um código bem menor graças as facilidades da pwntools.

E aqui uma versão bem mais bonita: https://github.com/soolidsnake/Write-ups/blob/master/WPICTF/ezpz/exploit.py