TryHackMe 5 Minute Hacks - Compiled
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
![]() | Level: 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
- [TryHackMe 5 Min Challenges](/blog/Writeups/challenges
