Ir para conteúdo
  • Cadastre-se
  • Resolvendo um desafio de CTF com bash e força bruta

       (0 análises)

    gzn

    Caro leitor, você gosta de desafios? Neste artigo vou contar como resolvi um desafio de engenharia reversa do Shellterlabs, mas sem usar um disassembly!

    Para quem não é acostumado com o termo, de acordo com o grupo CTF-BR!, um CTF (Capture The Flag) nada mais é do que uma competição que envolve diversas áreas mas principalmente as áreas ligadas à segurança da informação. No Papo Binário também há um vídeo sobre o assunto.

    O desafio em questão é o Shellter Hacking Express Acidentalmente. Em sua descrição, há a seguinte frase: Acidentalmente codificamos a chave.

    Isso não diz muita coisa mas ao baixar o binário, percebemos que há dois arquivos:

    tar tf ~/Downloads/e74a74b5-86cf-4cb3-a5bb-18a36ef067cf.tgz
    RevEng400/
    RevEng400/encoder
    RevEng400/key.enc
    

    Usando o comando file, verifiquei de que tipo são os arquivos extraídos:

    cd RevEng400/
    $ file *
    encoder: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.2.5, stripped
    key.enc: data

    Ao ver que o encoder é um binário ELF, fui direto analisar seu código num disassembler usando objdump e gdb, mas percebi que o binário não continha os símbolos, o que torna sua análise um pouco mais difícil.

    Sendo iniciante em engenharia reversa e depois de horas analisando a função de cifragem, confesso que fiquei sem saber para onde ir (já viu algum apressadinho tentando aprender a tocar guitarra? Pois é, já quer ir lá tocar aquele solo, e na velocidade Steve Vai, aí não dá né? rs) e desisti, mas não por muito tempo (ei crianças, nunca desistam dos seus sonhos viu! rs), e procurei o nosso querido prof. @Fernando Mercês lá no servidor do Discord, que me deu umas dicas. Segue trecho da conversa:

    > @fernandom @gzn sei que vc ta treinando ER, mas nem precisa disassemblar esse binario pra esse desafio nego
    > se vc olhar bem, vai ver que a saída do encoder tem o tamanho da string de entrada + o byte 0x03 no final
    > olhando a chave encodada (key.enc), é razoável admitir que ela tenha 16 caracteres então
    > você só precisa encontrar qual deles é o 0xef .. um loop com bash mata
    > supondo que seja o 'A'... então 'A' -> 0xef, aí você vai precisar da letra que gera o 0xf9 e assim sucessivamente, até chegar em 16

    Já ouviu a expressão "pensar fora da caixa"? Pois é! Por que eu fui direto disassemblar? Esse é um dos problemas quando nós estamos começando: às vezes a gente acha que o método mais difícil deve ser o único ou o melhor para se resolver problemas, mas nem sempre é assim. Daí pensei: se o Mercês falou que não é muito difícil, vamos ao menos tentar não é?

    Bem, a primeira coisa que fiz foi ver uma maneira de imprimir o conteúdo do binário em hexadecimal. Para isso criei um pequeno script que usa o hexdump para me dar uma saída somente com os bytes em hexadecimal do parâmetro que receber. Chamei o script de hexdump.sh e depois dei permissão de execução nele (chmod +x). Seu conteúdo é o seguinte:

    #!/bin/sh
    hexdump -v -e '/1 "%02X "' $1

    Então comecei os testes:

    for letra in 0 A a; do echo -n "$letra "; ./encoder $letra | ./hexdump.sh; echo; done
    0 7F 01
    A F7 02
    a F7 03

    Parece que nem sempre o final é 0x03... Bem, fui verificar o conteúdo do arquivo key.enc e encontrei isso:

    ./hexdump.sh < key.enc
    EF F9 42 09 A3 1A 43 F7 8C 8B BB 22 2A C2 A3 14 03 

    Pela lógica, já que essa é a chave codificada, se eu passar a chave correta original em texto como parâmetro para o binário encode ele terá que gerar a sequência acima. Seguindo a dica do Mercês, usei o próprio bash para tentar quebrar o desafio, primeiro mostrando o conteúdo em hexadecimal da chave codificada, depois iterando pelos caracteres possíveis e filtrando pelo primeiro byte dela:

    hexdump.sh < key.enc; echo
    
    for ((i=32;i<=126;i++)); do
    > l=$(printf "\x$(printf "%x" $i)")
    > echo -n "$l "
    > ./encoder "$l" | ./hexdump.sh
    > echo
    done | grep 'EF'

    Este código basicamente faz:

    • Mostra os bytes em hexadecimal da chave a cada vez que executarmos esse comando (só pra saber qual byte é o próximo).
    • Itera por todos caracteres imprimíveis da tabela ASCII (faixa de 32 à 126 em decimal).
      • Imprime o caractere na tela sem a quebra de linha.
      • Passa essa letra para como argumento para o binário encode e imprime a saída dele em hexadecimal.
    • Por fim, usa o grep para encontrar uma combinação que tenha o próximo byte da chave.

    Partindo para um exemplo prático, fui tentar encontrar a primeira letra dessa chave, sabendo que sua versão codificada deve resultar no byte 0xEF:

    ./hexdump.sh < key.enc; echo
    EF F9 42 09 A3 1A 43 F7 8C 8B BB 22 2A C2 A3 14 03 
    
    for ((i=32;i<=126;i++)); do
    > caractere=$(printf "\x$(printf "%x" $i)")
    > echo -n "$caractere ";./encoder "$l" | ./hexdump
    > echo
    done | grep 'EF'
    " EF 01 
    B EF 02 
    b EF 03

    Conforme pode ver acima, encontrei três caracteres diferentes que, quando encodados pelo encoder, geram o byte 0xEF: ", B, e b. Escolhi seguir com o B, prefixando-o na chave para dar sequência ao script e ver se encontramos o caractere que resulta no próximo byte da chave codificada (0xF9):

    ./hexdump.sh < key.enc; echo
    EF F9 42 09 A3 1A 43 F7 8C 8B BB 22 2A C2 A3 14 03 
    
    for ((i=32;i<=126;i++)); do
    > caractere=$(printf "\x$(printf "%x" $i)")
    > echo -n "B${caractere} "
    > ./encoder "B$l" | ./hexdump
    > echo
    done | grep 'EF F9'
    % EF F9 01 
    E EF F9 02 
    e EF F9 03 
    
    

    Mais uma vez encontrei três opções. Foi só continuar este processo até encontrar a chave que gera os exatos 16 bytes do arquivo key.enc.

    Aproveitei e automatizei um brute forcer com Python:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    import subprocess
    
    def encode(arg):
        result = subprocess.run(['./encoder', arg], stdout=subprocess.PIPE)
        return result.stdout
    
    def loadKey():
        key_enc = []
        with open('./key.enc', 'rb') as file:
            while True:
                byte = file.read(1)
                if byte:
                    # a ordem dos bytes aqui não importa (porque trata-se apenas de 1 byte), mas é necessário especificar
                    key_enc.append(int.from_bytes(byte, byteorder='little'))
                else:
                    break
        return key_enc
    
    def permutations(key='', key_enc=loadKey(), key_i=0):
        if key_i == len(key_enc) - 1:
            print(key)
            return
        for char in (chr(i) for i in range(32, 127)):
            result = encode(key + char)
            if result[key_i] == key_enc[key_i] and key_i < len(key_enc):
                permutations(key=key + char, key_enc=key_enc, key_i=key_i + 1)
    
    def main():
        permutations()
    
    if __name__ == "__main__":
        main()

    Saída codificada em base64 (pra não estragar a brincadeira de quem vai tentar resolver o desafio por conta própria):

    QmV3aXRjaGluZyBTZXh0LwpCZXdpdGNoaW5nIFNleHRPCkJld2l0Y2hpbmcgU2V4dG8K

    Segue vídeo do canal com a explicação do algortimo de encoding desse desafio: 

     

    Editado por Fernando Mercês



    Feedback do Usuário

    Crie uma conta ou faça o login para deixar um comentário

    Você precisa ser membro para fazer uma análise

    Criar uma conta

    Crie uma nova conta em nossa comunidade. É fácil!

    Crie uma nova conta

    Entrar

    Já tem uma conta? Faça o login.

    Entrar Agora

    Não há análises para mostrar


×