Format String 2
Descrição do Desafio:
Autor: SkrubLawd Plataforma: PicoCTF Categoria: Binary Exploitation Dificuldade: Médio Data: 2024 Descrição:
This program is not impressed by cheap parlor tricks like reading arbitrary data off the stack. To impress this program you must change data on the stack!
Passo a Passo da Solução
1. Análise do arquivo fornecido
Este desafio nos fornece tanto o executável como o arquivo fonte. Então, o primeiro passo é analisar o arquivo fonte e ver o que ele tem de interessante para nós.
É um código simples, ele tem uma variável global inicializada com 0x21737573
, e imprimirá a flag caso o seu valor seja 0x67616c66
. Se repararmos, logo após inserirmos o input, o programa imprime ele, porém ele imprime passando o buf
diretamente como parâmetro, sem informar as strings de formato, abrindo brecha para um possível ataque de format string.
2. Exploit
Para realizarmos um ataque de format string, nesse caso, nós precisamos saber o endereço da variável sus
e o offset no qual o nosso input começa a aparecer na pilha.
Nota: Como a variável sus
é de escopo global, ela dificilmente estará armazenada na pilha.
Para sabermos o endereço da sus
, precisamos saber inicialmente se o executável contém alguma proteção como o PIE
.
E como podemos ver ele não contém, então para pegarmos o endereço da sus
nós podemos usar a instrução elf.sym['sus']
do pwntools (note que elf
deve ser declarada anteriormente como ELF('./vuln')
) ou simplesmente digitar p &sus
no terminal do pwndbg. Com o endereço em mãos, vamos atrás do offset, cuja maneira mais fácil de se obter é executar o programa e digitar a sequência %x-
no input, após isso ir contando quantos resultados apareceram antes dos bytes dos caracteres do input.
No caso, eles começam a aparecer no 14º offset.
3. Solução
A solução é um tanto simples, devemos escrever 0x67616c66
caracteres e com o formato %n
salvar no endereço obtido. Porém essa quantidade de caracteres tende a encerrar a execução do programa antes dele imprimir tudo, então dividiremos em duas partes: a parte superior (6c66
) e a parte inferior (6761
).
Nota: A parte superior e inferior estão invertidas em relação ao valor 0x67616c66
por causa do formato little-endian
.
Com essas duas partes, iniciamos o payload escrevendo o menor valor, e depois escrevemos o maior valor menos o que já foi imprimido. Após isso, completamos o payload com qualquer caractere para a pilha não ficar desalinhada (deve ser sempre múltiplo de 8 se for 64bits). Por fim inserimos os endereços, sendo o endereço original a parte superior e o endereço somado com mais 2 (deslocado 2 bytes) a parte inferior.
Importante: Os endereços devem estar no final do payload pois eles contém bytes nulos, que fazem o printf()
parar de imprimir.
Flag
picoCTF{f0rm47_57r?_f0rm47_m3m_ccb55fce}
Autor da WriteUp
Atualizado