HackTheBox I’m gRoot Challenge
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.