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.