Back to Write-ups
Write-upDecember 5, 20254 min read

Reverse Engineering Challenge: Silent Oracle

A walk-through of an HTB Neurogrid reversing challenge where you analyze a stripped 64-bit PIE binary in Ghidra, identify a hardcoded comparison function, discover the local flag is a decoy, and brute-force the real flag from the remote service using a Python script that tests prefixes based on response differences.

ReverseEngineeringHTB

Overview

This reverse-engineering challenge was part of the Neurogrid CTF hosted by HTB.

The task included a Docker instance and a local source file mirroring the remote service (chall). Running it displayed an ASCII witch and prompted for an unknown input string. Any incorrect input produced a failure message.

The initial objective was simple: determine the exact string the program expected and check whether submitting it would reveal a flag.

The Tools used are the following;
  1. Ghidra
  2. Python

Initial File Analysis

Before starting this challenge I did some small analysis on what this file is. I ran file on chall.

chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a198e4a69af44b16ca4a19e59794582502f451a4, for GNU/Linux 4.4.0, stripped

That tells me that this file is;

  1. A 64 bit linux binary using little endian
  2. Compiled for normal 64 bit linux systems
  3. Stripped, so all symbols and function names removed.

Nothing unusual here.


Core techniques

Analyzing the binary in Ghidra revealed several functions.
Two stood out: FUN_00101283 and FUN_001011fc.

FUN_00101283

This function handled input, printed the banner, and passed the user’s string to the comparison function:

cVar1 = FUN_001011fc(local_58, local_60);

Based on structure and behavior, this was effectively the main function.

FUN_001011fc

This function compared the user input against a hardcoded string:

if (*(char *)(param_1 + (int)local_c) !=
    PTR_s_HTB{test_flag_hahaha}_0015d068[(int)local_c])
{
    puts(&DAT_0015b040);
    sleep(5);
    return 0;
}

The logic compared each character of the input with the corresponding character in the embedded string HTB{test_flag_hahaha}. Entering that string locally returned a success message.

However, submitting it to the remote Docker instance resulted in a failure. That confirmed the local string was only a placeholder.

Testing small prefixes showed different failure messages. For example:

  • "HTB{" → partially correct

  • "HTB{1" → fully incorrect Silent Oracle Completely Correct

This implied the remote instance performed partial validation.
So brute-forcing it character-by-character became possible.


Enumeration Script

To automate the process, I wrote a simple Python script that:

  1. Connects to the remote instance
  2. Sends a candidate prefix
  3. Checks the returned message
  4. Extends the prefix whenever the response indicates “partially correct”
import socket
import time

charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!@#$%^&*()-+=[];:'\",./<>?|\\"
prefix = "HTB{"

def talk(payload):
    time.sleep(0.15)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    try:
        s.connect(("94.237.52.235", 59894))
    except:
        return ""

    data = ""

    try:
        while True:
            chunk = s.recv(1024)
            if not chunk:
                break
            data += chunk.decode("utf-8", errors="ignore")
    except:
        pass

    try:
        s.send((payload + "\n").encode())
    except:
        s.close()
        return ""

    try:
        while True:
            chunk = s.recv(1024)
            if not chunk:
                break
            data += chunk.decode("utf-8", errors="ignore")
    except:
        pass

    s.close()
    return data

while True:
    for char in charset:
        attempt = prefix + char
        resp = talk(attempt)

        if "YOU ARE BANISHED" in resp:
            prefix += char
            print(prefix)
            break

        if "CONTINUE ON WITH YOUR ADVENTURE" in resp:
            print("DONE:", prefix)
            exit()

Retrieve the flag

Running the script recovered the correct flag;

HTB{Tim1ng_z@_h0ll0w_t3ll5}

Silent Oracle Done