◄ BACKPOST2026-10-02

TryHackMe 5 Minute Hacks - Compiled

tryhackmechallengebruteforcepythonghidrabinary

Abstract

This is yet another very simple challenge, judging by the its instructions. It is basically a binary analysis challenge, but you can solve it using different methods such as our old friend Bruteforce.

The Challenge - Compiled

CompiledLevel: Easy
Time: 5 min
Type: Binary Analysis
Requirements:
- Basics of Binary Analysis
- Bruteforcing and file manipulation for wordlist creation
Resources Used:
- Kali Linux (VM)
- Basic BInary Analysis
- Python Script

Walkthrough

The challenge instructions say that this file will not execute on TryHackMe provided AttackBox, but it can still be solved. Like in every challenge, I’m using a Kali Linux virtual machine on my main PC or my main pc directly, so it gives me more options on what to do.

Once the challenge task file named “Compiled-1688545393558.Compiled” is downloaded and extracted it contains a binary called “Compiled-1688545393558.Compiled”.

The file extension doesn’t give away what filetype that is so let’s check that with ‘file’ command:

file Compiled-1688545393558.Compiled

Compiled-1688545393558.Compiled: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=06dcfaf13fb76a4b556852c5fbf9725ac21054fd, for GNU/Linux 3.2.0, not stripped

This is an ELF 64-bit LSB pie executable file. Let’s try to give it execution rights:

chmod a+x Compiled-1688545393558.Compiled

After executing the file we get a Password prompt. When trying to add a random string, it gives us an error:

./Compiled-1688545393558.Compiled 
Password: asdf
Try again!

Binary Analysis 101

OK, let’s go binary analysis 101 and ‘strings’ this file to see if we can find the ‘Password’ string.

strings Compiled-1688545393558.Compiled 

The results are:

... sniped...

BC_2.2.5
GLIBC_2.34
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
PTE1
u+UH
StringsIH
sForNoobH
Password: 
DoYouEven%sCTF
__dso_handle
_init
Correct!
Try again!
;*3$"
GCC: (Debian 11.3.0-5) 11.3.0
Scrt1.o
__abi_tag

... snipped ...

After locating the “Password:” and “Correct!” we can infer that the string ‘DoYouEven%sCTF” looks like a password, at least partially. I’ve executed the file and used this string, but to no avail. The response was “Try again!”. So, I kept executing the file and using different strings based on binary behavior using different strings.

This are the results from different tests with similar password strings:

  • When using “DoYouEven%sCTF” => responds “Try again!”
  • WHen using “DoYouEven_” => responds “Try again!”
  • When using “DoYouEven” => The program freezes

That is an logical indication that the password needs to start with the string “DoYouEven”. Juding by the flag format that says *********_****, we can infer that the password must start with “DoYouEven_” followed by 4 characters.

Solution 1 - Bruteforce Method 1 (Python Script)

The first and simple method of breaking this flag is simply a little crude bruteforce. Yes, not very ellegant, just using the evidence we found, and constantly kicking the door to see if it opens.

The flag’s naming convention, as in the challenge password field, is “*******_****”. That string kinda matches the one we found on the strings command results. Also, the fact that it has 4 characters after the “_” symbol makes me think that this could be bruteforced, although we don’t know the charset in use for these last 4 characters, they could be letters, numbers or even symbols.

With a little bit of help from a friend (AI) I’ve created the following bruteforce python script, which will use the full charset (letters, numbers, upper/lower case) for the last 4 unkonwn characters of the password.

#!/usr/bin/env python3
"""Threaded brute force of ./Compiled-1688545393558.Compiled

Password form: DoYouEven_XXXX
Prints nothing on "Try again!". Prints only a hang or a different reply.
"""

import os
import sys
import string
import itertools
import subprocess
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed

BIN = os.environ.get("BIN", "./Compiled-1688545393558.Compiled")
PREFIX = os.environ.get("PREFIX", "DoYouEven_")
CHARSET = os.environ.get(
    "CHARSET", string.digits + string.ascii_lowercase + string.ascii_uppercase
)
TIMEOUT = float(os.environ.get("TIMEOUT_SECS", "2"))
WORKERS = int(os.environ.get("WORKERS", str(min(64, (os.cpu_count() or 4) * 8))))
START = os.environ.get("START", "")
BATCH = int(os.environ.get("BATCH", "512"))

_stop = threading.Event()


def attempt(password: str):
    if _stop.is_set():
        return password, "SKIP", ""
    try:
        proc = subprocess.run(
            [BIN],
            input=password + "\n",
            capture_output=True,
            text=True,
            timeout=TIMEOUT,
        )
    except subprocess.TimeoutExpired as exc:
        out = ((exc.stdout or "") + (exc.stderr or "")).replace("\r", "")
        return password, "HANG", out
    except FileNotFoundError:
        return password, "MISSING", f"binary not found: {BIN}"

    raw = ((proc.stdout or "") + (proc.stderr or "")).replace("\r", "")
    reply = raw.replace("Password:", "").strip()
    if reply == "Try again!" or reply.replace("Try again!", "").strip() == "":
        return password, "FAIL", ""
    return password, "HIT", raw


def suffixes():
    gen = itertools.product(CHARSET, repeat=4)
    if START:
        if len(START) != 4 or any(c not in CHARSET for c in START):
            raise SystemExit(f"START must be 4 chars from CHARSET, got {START!r}")
        target = tuple(START)
        for combo in gen:
            if combo >= target:
                yield "".join(combo)
    else:
        for combo in gen:
            yield "".join(combo)


def main():
    if not os.path.isfile(BIN):
        sys.exit(f"Binary not found: {BIN}\nRun from its directory or set BIN=...")
    if not os.access(BIN, os.X_OK):
        os.chmod(BIN, 0o755)

    gen = suffixes()
    with ThreadPoolExecutor(max_workers=WORKERS) as pool:
        while not _stop.is_set():
            batch = list(itertools.islice(gen, BATCH))
            if not batch:
                break
            futures = [pool.submit(attempt, PREFIX + s) for s in batch]
            for fut in as_completed(futures):
                password, status, raw = fut.result()
                if status in ("HIT", "HANG", "MISSING"):
                    _stop.set()
                    print(f"[{status}] {password}", flush=True)
                    if raw:
                        print(raw, flush=True)
                    with open("found_password.txt", "w", encoding="utf-8") as fh:
                        fh.write(password + "\n")
                    return 0 if status != "MISSING" else 1
    if not _stop.is_set():
        print("exhausted charset with no different answer", flush=True)
        return 2
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

After running, It was just a matter of time to get our flag using this method:

time python3 bf.py 
[HIT] DoYouEven_REDACTED
Password: Correct!

real	10m19.870s
user	24m29.552s
sys	10m34.225s

Solution 2 - Binary Analysis

Let’s investigate now the way we were supposed to win this flag and find the password. As the challenge says “Strings can only help you so far’, we’ll have to investigate our binary file further than a simple ‘strings’ command.

STRACE

Let´ s run our binary with “strace” to trace system calls and signals, and give it the “DoYouEven%sCTF” string as a password too see what happens.

strace ./Compiled-1688545393558.Compiled 
execve("./Compiled-1688545393558.Compiled", ["./Compiled-1688545393558.Compile"...], 0x7ffd2e23ff90 /* 69 vars */) = 0
brk(NULL)                               = 0x5d842227b000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f820ca60000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=122039, ...}) = 0
mmap(NULL, 122039, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f820ca42000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\243\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
fstat(3, {st_mode=S_IFREG|0755, st_size=2129424, ...}) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2174352, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f820c800000
mmap(0x7f820c828000, 1609728, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f820c828000
mmap(0x7f820c9b1000, 323584, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b1000) = 0x7f820c9b1000
mmap(0x7f820ca00000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ff000) = 0x7f820ca00000
mmap(0x7f820ca06000, 52624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f820ca06000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f820ca3f000
arch_prctl(ARCH_SET_FS, 0x7f820ca3f740) = 0
set_tid_address(0x7f820ca3fa10)         = 241335
set_robust_list(0x7f820ca3fa20, 24)     = 0
rseq(0x7f820ca40060, 0x20, 0, 0x53053053) = 0
mprotect(0x7f820ca00000, 16384, PROT_READ) = 0
mprotect(0x5d83e33fc000, 4096, PROT_READ) = 0
mprotect(0x7f820ca9e000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f820ca42000, 122039)          = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
getrandom("\x70\xd9\x96\x8f\x0d\xbe\xa9\xbd", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x5d842227b000
brk(0x5d842229c000)                     = 0x5d842229c000
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "Password: ", 10Password: )              = 10
read(0, DoYouEven%sCTF
"DoYouEven%sCTF\n", 1024)       = 15
write(1, "Try again!", 10Try again!)              = 10
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)
exit_group(0)                           = ?
+++ exited with 0 +++

As you can see, the software just replied with “Try again!”, but didn’t give us much information, apart from what we already knew, the partial password string “DoYouEven%sCTF”.

LTRACE

Let’s now run our binary with “ltrace” so we can trace all libraries it calls

ltrace ./Compiled-1688545393558.Compiled 
fwrite("Password: ", 1, 10, 0x7a191c4055c0)                                              = 10
__isoc99_scanf(0x56548978500f, 0x7ffe62d9b320, 0, 0Password: DoYouEven%sCTF              = 1
strcmp("%sCTF", "__dso_handle")                                                          = -58
strcmp("%sCTF", "REDACTED")                                                                 = -58
printf("Try again!")                                                                     = 10
Try again!+++ exited (status 0) +++

OK, let’s analyse this now. Notice that immediately after the function “fwrite” writes “Password:” on the screen and waits for an input, other functions “strcmp” run.

The “strcmp()” is a C function that compares two strings character by character to see if they are equal o rwhich one comes frist in alphabetical order.

Notice that the first “strcmp()” function is comparing this two strings “%sCTF” with “__dso_handle”. Thje last string is a but a GCC/ELF runtime symbol. Decompilers generally dump it next to the real data because it lives in the same binary.

The next “strcmp()” function though compares the same initial string with another string, in this case here “REDACTED”.

So taking the initial string “DoYouEven%sCTF” and substiting the “%sCTF” part with the new comparison string, gets you the flag.

And there you have it!

Related Posts