Análise de Desafios de Engenharia Reversa em Competições

Desafio 1: Análise de Binário Python

Este foi o problema com menor pontuação, caracterizado por um arquivo anexo grande e múltiplas referências a "Py" nas ferramentas de inspeção do navegador (F12). A conclusão inicial foi que se tratava de um problema de engenharia reversa de Python.

Usando o pyinstxtractor para extrair o executável:

Normalmente, procuraríamos pelo arquivo pyc correspondente ao nome do anexo para descompilar, mas não foi encontrado. O suspeito foi o arquivo "src".

Descompilando com pycdc:


# Código Gerado com Decompyle++
# Arquivo: src.pyc (Python 3.10)

import hashlib

def obter_entrada_usuario():
    entrada_usuario = input('Digite: ').strip()
    if len(entrada_usuario) != 9:
        print('Sua entrada contém erros!')
        continue
    if not entrada_usuario.isdigit():
        print('Sua entrada contém erros!')
        continue
    return entrada_usuario

def verificar_correspondencia_md5(entrada_usuario, hash_alvo):
    md5_entrada = hashlib.md5(entrada_usuario.encode()).hexdigest()
    if md5_entrada == hash_alvo:
        return True

def principal():
    HASH_ALVO = 'b4bb721a74f07177a6dbc3e113c327e3'
    entrada_usuario = obter_entrada_usuario()
    corresponde = verificar_correspondencia_md5(entrada_usuario, HASH_ALVO)
    if corresponde:
        flag = "flag{md5(entrada_usuario + 'SDnisc')}"
        print('Verificação bem-sucedida!')
        print(f'''Flag: {flag}''')
        return None
    None('Falha na verificação!')

if __name__ == '__main__':
    principal()
    return None

O código mostra que a entrada primeiro remove caracteres de nova linha, espaços e tabulações. Em seguida, verifica se a entrada tem exatamente 9 caracteres e se todos são dígitos. Finalmente, calcula o hash MD5 e compara com o hash fornecido.

A solução é clara: precisamos realizar uma força bruta em hashes MD5 de 9 dígitos. Embora pudéssemos escrever um script em Python para isso, a abordagem mais eficiente é usar o hashcat com máscara.


hashcat -a 3 b4bb721a74f07177a6dbc3e113c327e3 ?d?d?d?d?d?d?d?d?d -m 0

Após encontrar o valor correspondente, basta concatenar "SDisc" e calcular o hash MD5 para obter a flag.

Desafio 2: Criptografia Complexa

Este problema era mais copmlexo, mas viável durante a competição (embora tenha sido o ponto em que fiquei travado).

A análise inicial focou na função de criptografia principal, localizada no endereço 569D. Antes disso, é importante entender como os dados são carregados.

Todos os dados começam 76 bytes após o início do arquivo "src", enquanto "srca" ocupa os 160 bytes após o byte 116.

A função recebe dois parâmetros: "enc" (flag criptografada) e "flag" (flag escrita). A verificação de tamanho é feita primeiro, mas o ponto crucial é que o valor da flag escrita é atribuído à flag criptografada. Na verdade, os primeiros 76 bytes de "enc" estão vazios, então essa atribuição não causa problemas.

O processo criptografa cada byte 40 vezes. Na primeira criptografia, o valor é passado para o endereço 4*28+4=116, uma posição familiar.

O ponto que me causou confusão foi o uso de operações bit a bit. Na verdade, o processo equivale a um deslocamento à esquerda de n2 bits, com os bits deslocados movidos para o final. Para a engenhraia reversa, precisamos inverter essa operação (basicamente, inverter a chave de entrada).

Aqui está o script manual que desenvolvi, considerando mais claro que as soluções geradas por IA:


# Script para o terceiro desafio da competição de reversão
# Equivalente ao segundo problema mais simples (são três no total)

chave = [0x3, 0x5, 0x1, 0x6, 0x2, 0x4, 0x5, 0x6, 0x3, 0x1, 0x2, 0x5, 0x4, 0x6, 0x1, 0x3, 0x2, 0x5, 0x4, 0x6, 0x1, 0x3, 0x2, 0x5, 0x4, 0x6, 0x1, 0x3, 0x5, 0x2, 0x4, 0x6, 0x1, 0x3, 0x2, 0x5, 0x4, 0x6, 0x1, 0x3]
cripto = [0xB4, 0xB6, 0x9F, 0xA1, 0xC5, 0xAD, 0x7A, 0x68, 0x77, 0xAD, 0x7B, 0x70, 0x1D, 0x68, 0x70, 0x7B, 0x76, 0x70, 0xA0, 0x7C, 0x1D, 0xAE, 0x7B, 0x77, 0xB4, 0x7C, 0xAE, 0xB4, 0x68, 0xA0, 0x68, 0xF9, 0x76, 0xB3, 0x70, 0x77, 0x9F, 0xBA]

def aplicar_cripto(valor, chave_atual):
    match chave_atual:
        case 1:
            valor = (valor ^ 5) & 0xFF
        case 2:
            valor = (valor - 1) & 0xFF
        case 3:
            valor = (valor + 1) & 0xFF
        case 4:
            valor = caso4(valor)
        case 5:
            valor = caso5(valor, 1)
        case _:
            valor = caso6(valor, 1)
    return valor

def caso4(valor1):
    valor2 = (caso6((valor1 + 1) & 0xFF, 2) + 2) & 0xFF
    return (caso5(valor2, 2) - 3) & 0xFF

def caso5(valor1, n2):
    n2 &= 7
    valor1 = valor1 >> (n2 % 8) | valor1 << (8 - n2 % 8)
    return valor1 & 0xFF

def caso6(valor1, n2):
    n2 &= 7
    valor1 = valor1 << (n2 % 8) | valor1 >> (8 - n2 % 8)
    return valor1 & 0xFF

print(len(cripto))
for i in range(len(cripto)):
    for j in chave[::-1]:
        cripto[i] = aplicar_cripto(cripto[i], j)
    print(chr(cripto[i]), end="")

Para lidar com problemas de estouro: minha recomendação é adicionar & 0xFF após todas as operações de adição para evitar estouro de bits.

Tags: engenharia reversa Python hashcat criptografia Segurança

Publicado em 6-8 20:49 por Thomas