https://app.hackthebox.com/challenges/518

Description

After decrypting the communication, you uncover the identity of the mole as the senior blockchain developer. Shockingly, the developer had embedded a backdoor in the government's decentralized blockchain network, originally designed to prevent corruption. You report this critical finding to the government council and are assigned with the task of detecting and fixing the backdoor, ensuring the integrity and security of the network.

Reference

https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/

Exploitation

#!/usr/bin/env python3
from hashlib import sha256
import socket, sys, signal

def handler(signum, frame):
    print("\n[-] Interrupted by user")
    sys.exit(1)

def recv_until(s):
    data = b""
    try:
        s.settimeout(TIMEOUT)
        start_time = 0
        while b"> " not in data:
            try:
                chunk = s.recv(1024)
                if not chunk:
                    if data:
                        break
                    raise ConnectionError("Connection closed by remote host")
                data += chunk
            except socket.timeout:
                if data:
                    break
                raise TimeoutError(f"No response after {TIMEOUT} seconds")
    except Exception as e:
        raise e
    return data.decode()

def get_last_tx(s):
    print("[+] Sending transaction request")
    s.send(b"1\n")
    data = recv_until(s)
    print("[+] Received response:", data.split('\n')[0] if data else "No data")
    try:
        for line in reversed(data.split('\n')):
            if "Transactions:" in line:
                tx = eval(line.split("Transactions: ")[1].strip())
                print(f"[+] Found transactions: {tx[:20]}...")
                return tx
    except Exception as e:
        print(f"[-] Error parsing transactions: {str(e)}")
    return None

def exploit(host, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(TIMEOUT)
    try:
        print(f"[+] Connecting to {host}:{port}")
        s.connect((host, port))
        print("[+] Receiving initial prompt")
        initial = recv_until(s)
        print(f"[+] Initial response: {initial.split('\n')[0]}")
        tx = get_last_tx(s)
        if not tx:
            print("[-] Failed to get transactions")
            return
        print("[+] Computing hashes")
        h = lambda b: sha256(bytes.fromhex(b)).hexdigest()
        r0 = list(map(h, tx))
        r1 = [h(r0[i] + r0[i+1]) for i in range(0, len(r0), 2)]
        r2 = [h(r1[i] + r1[i+1]) for i in range(0, len(r1), 2)]
        forged = r2[0] + r2[1]
        print(f"[+] Forged hash: {forged[:20]}...")
        print("[+] Sending option 2")
        s.send(b"2\n")
        menu = recv_until(s)
        print(f"[+] Menu response: {menu.split('\n')[0]}")
        print("[+] Sending forged hash")
        s.send(forged.encode() + b"\n")
        print("[+] Waiting for result")
        result = recv_until(s).strip()
        print(f"[+] Result: {result}")
    except TimeoutError as e:
        print(f"[-] Timeout: {str(e)}")
    except ConnectionError as e:
        print(f"[-] Connection error: {str(e)}")
    except Exception as e:
        print(f"[-] Error: {str(e)}")
    finally:
        print("[+] Closing connection")
        s.close()

def main():
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <host:port>")
        sys.exit(1)
    try:
        host, port = sys.argv[1].split(':')
        exploit(host, int(port))
    except ValueError:
        print("[-] Invalid host:port format")
        sys.exit(1)
    except Exception as e:
        print(f"[-] Error: {str(e)}")

TIMEOUT = 10
signal.signal(signal.SIGINT, handler)

if __name__ == "__main__":
    main()

Summary

I’m gRoot: reduce the hash constraint to a small search, test candidates, and recover the flag.