#!/usr/bin/env python3
"""
Reto 02 - PIR-05 "Persistencia Silenciosa" (Reversing / Forense)
El binario descifra su configuracion con una clave derivada del hostname
(FNV-1a 64-bit). NO necesitamos el hostname: usamos texto conocido.

Flujo del binario (main):
  clave = 8 bytes del hash FNV-1a del hostname
  salida[i] = rol8(clave[i & 7], i >> 3) XOR cifrado[i] - i   (mod 256)
  si salida[:7] == "NAVSYNC": imprime salida[7:]  (el informe con la ruta)

Truco: como sabemos que empieza por "NAVSYNC", despejamos clave[0..6]
directamente y hacemos fuerza bruta solo del clave[7] (256 valores).

Uso:
    python3 resolver_reto02.py            # usa ./extraido_05/persistencia.elf
    python3 resolver_reto02.py <elf>      # ruta directa al binario
Si no existe, descomprime material_05-persistencia-silenciosa.zip.
"""
import sys, os, zipfile

HERE = os.path.dirname(os.path.abspath(__file__))
CIPHER_OFF = 0x2020      # offset de .rodata confirmado con readelf -S
CIPHER_LEN = 0x224       # 548 bytes
MAGIC = b"NAVSYNC"

def localizar_elf(arg):
    if arg and os.path.isfile(arg):
        return arg
    carpeta = os.path.join(HERE, "extraido_05")
    elf = os.path.join(carpeta, "persistencia.elf")
    if not os.path.isfile(elf):
        z = os.path.join(HERE, "material_05-persistencia-silenciosa.zip")
        os.makedirs(carpeta, exist_ok=True)
        with zipfile.ZipFile(z) as zf:
            zf.extractall(carpeta)
        print(f"[*] Descomprimido {os.path.basename(z)} en {carpeta}")
    return elf

def rol8(x, n):
    n &= 7
    return ((x << n) | (x >> (8 - n))) & 0xff if n else x

def decode(cipher, key):
    return bytes(((rol8(key[i & 7], i >> 3) ^ cipher[i]) - i) & 0xff
                 for i in range(len(cipher)))

def main():
    elf = localizar_elf(sys.argv[1] if len(sys.argv) > 1 else None)
    data = open(elf, "rb").read()
    cipher = data[CIPHER_OFF:CIPHER_OFF + CIPHER_LEN]

    # clave[0..6] despejada con el texto conocido "NAVSYNC" (rotacion = 0 para i<8)
    need = [((MAGIC[i] + i) & 0xff) ^ cipher[i] for i in range(7)]
    print("[1] clave[0..6] despejada con known-plaintext 'NAVSYNC':",
          [hex(x) for x in need])

    # clave[7] por fuerza bruta: solo afecta a 1 de cada 8 bytes
    for k7 in range(256):
        out = decode(cipher, need + [k7])
        if out[:7] == MAGIC and all(9 <= b <= 13 or 32 <= b <= 126 for b in out):
            print(f"[2] clave[7] = {hex(k7)} (texto 100% imprimible)")
            print("[3] Mensaje descifrado del binario:\n" + "=" * 60)
            print(out.decode())
            return
    print("[!] No se encontro clave[7] valida")

if __name__ == "__main__":
    main()
