#!/usr/bin/env python3
"""FLARE-On 13 #4 ToxicMiner - static solver.

Usage: python3 -I solve.py [--serial HEX]
Needs: gcc with OpenMP. Nothing from the challenge is executed: the hash is
re-implemented from Ghidra's decompilation (tools/ghidra2c.py), compiled, and
used to brute-force the 32-bit volume serial number.

ToxicMiner.exe = tpruvot cpuminer-multi 1.3.7 whose sha256d scanhash
(FUN_140134f70, algo index 0x27) is backdoored:
  * hash(VolumeSerialNumber || nonce) is compared with the 64-hex --user value;
  * on a match key = hash(VolumeSerialNumber || 0x0b501e7e)[:16] and
    RC4(key) decrypts 0x33 bytes at 0x1401bd3b0 -> "Accepted: <flag>".
"hash" is a toxic double-SHA-256 (FUN_140135240): 32 rounds only, K0/K1
swapped, Ch=((f+g)&e)^g, Maj=((b&c)+(c&a))^(a&b), plus a final
st0 ^= Ktab_bytes[st0&0xff] ^ *(u32*)(st + st1 % 28).
The --user target is irrelevant; only the 32-bit serial is secret.
"""
import os
import subprocess
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
LOOT = os.path.join(HERE, "loot")
TOOLS = os.path.join(HERE, "tools")
EXE = os.path.join(LOOT, "extracted", "ToxicMiner.exe")
GHIDRA_C = os.path.join(LOOT, "ghidra", "sha256_transform.c")   # Ghidra output of FUN_140135240
CT_VA, CT_LEN, TAB_VA = 0x1401BD3B0, 0x33, 0x1401BD270


def build():
    import pefile
    pe = pefile.PE(EXE)
    ib = pe.OPTIONAL_HEADER.ImageBase
    tab = pe.get_data(TAB_VA - ib, 256)
    ct = pe.get_data(CT_VA - ib, CT_LEN)
    with open(os.path.join(TOOLS, "tox_tab.h"), "w") as f:
        f.write("/* 256-byte table at 0x1401bd270 in ToxicMiner.exe */\nuint8_t TOX_TAB[256] = {"
                + ",".join(f"0x{b:02x}" for b in tab) + "};\n")
    src = open(os.path.join(HERE, "brute_serial.c")).read()
    assert ",".join(f"0x{b:02x}" for b in ct[:8]) in src.replace("\n", "").replace(" ", ""), \
        "ciphertext in brute_serial.c does not match the PE"
    with open(os.path.join(TOOLS, "toxic_transform.h"), "w") as f:
        subprocess.run([sys.executable, "-I", os.path.join(TOOLS, "ghidra2c.py"), GHIDRA_C],
                       stdout=f, check=True)
    binp = os.path.join(LOOT, "brute_serial")
    subprocess.run(["gcc", "-O3", "-march=native", "-fopenmp", "-fwrapv", "-w",
                    "-I", HERE, os.path.join(HERE, "brute_serial.c"), "-o", binp], check=True)
    return binp


def main():
    args = sys.argv[1:]
    binp = build()
    if "--serial" in args:
        serial = args[args.index("--serial") + 1]
    else:
        print("[*] brute-forcing all 2^32 volume serials (OpenMP) ...")
        out = subprocess.run([binp], capture_output=True, text=True).stdout
        print("    " + out.strip())
        serial = out.split("serial=")[1].split()[0]
    out = subprocess.run([binp, serial], capture_output=True, text=True).stdout.strip()
    print("[*] " + out)
    pt = bytes.fromhex(out.split("pt=")[1])
    assert b"@flare-on.com" in pt, "serial does not decrypt the flag"
    flag = pt.decode()
    print(f"[+] FLAG: {flag}")
    with open(os.path.join(HERE, "flag.txt"), "w") as f:
        f.write(flag + "\n")


if __name__ == "__main__":
    main()
