Post

TryHackMe: Compiled CTF Walkthrough

TryHackMe: Compiled CTF Walkthrough

TryHackMe | Compiled CTF Challenge icon

🧰 Writeup Overview

This writeup explains the reverse-engineering of the provided ELF binary Compiled.Compiled, culminating in finding the correct input that triggers the message Correct!.

Initial Enumeration

File Type Check

1
file Compiled.Compiled
  • ELF 64-bit LSB pie executable, dynamically linked (using ld‑linux‑x86_64.so.2), not stripped: symbol names are available.

Strings Inspection

1
strings Compiled.Compiled

Key findings:

  • Password:
  • DoYouEven%sCTF
  • __dso_handle
  • _init
  • Correct! & Try again!

These strongly hint that input is processed via a custom scanf format string, with logic comparing two strings.


Runtime Behavior

Test Basic Execution

1
2
3
4
./Compiled.Compiled
# Try random password:
Password: test
 Try again!

Monitor with ltrace

1
ltrace ./Compiled.Compiled

Input:

1
test

Output trace:

1
2
3
4
5
6
7
write("Password: ", 1, 10, 0x7f7af50705c0)           = 10
__isoc99_scanf(0x55d159b6700f, 0x7ffe92dfe6b0, 0, 1024Password: test
) = 0
strcmp("", "__dso_handle")                            = -95
strcmp("", "_init")                                   = -95
printf("Try again!")                                  = 10
Try again!+++ exited (status 0) +++

Reverse Engineering the Logic

Disassemble or Use Ghidra / IDA

Here’s a deep dive explanation of decompiled pseudo‑C logic main() function, Let’s break it down line-by-line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
undefined8 main(void)
{
  int iVar1;
  char local_28 [32];  // 32-byte buffer

  fwrite("Password: ", 1, 10, stdout);
  __isoc99_scanf("DoYouEven%sCTF", local_28);

  iVar1 = strcmp(local_28, "__dso_handle");
  if ((-1 < iVar1) && (iVar1 = strcmp(local_28,"__dso_handle"), iVar1 < 1)) {
    printf("Try again!");
    return 0;
  }

  iVar1 = strcmp(local_28, "_init");
  if (iVar1 == 0) {
    printf("Correct!");
  }
  else {
    printf("Try again!");
  }
  return 0;
}

🧠 Line-by-Line Breakdown

char local_28[32];

  • A 32-byte buffer to store the user input.
  • Used to store the result of the scanf.

fwrite("Password: ", 1, 10, stdout);

  • Displays the prompt: Password:
  • Equivalent to printf("Password: "); but done using fwrite() here.

__isoc99_scanf("DoYouEven%sCTF", local_28);, 📌 Key Point

  • The format string DoYouEven%sCTF means:
  • It expects the user to input a string that matches this exact pattern.
  • For example, if user types DoYouEvenABCCTF, then: The string "ABC" will be extracted and stored into local_28.
  • Only the %s part is stored – the surrounding text (DoYouEven and CTF) is just expected for formatting.

🧪 Example:

1
2
Input: DoYouEventestCTF
Then local_28 = "test"

Behavior:

  • The format string “DoYouEven%sCTF” expects:

  • Literal “DoYouEven” at the start.

  • A string (%s) in the middle.

  • Literal “CTF” at the end.

  • Only the %s part is stored in input.

🔑 Important: %s is wrapped by fixed strings, so user must input format as string


strcmp(local_28, "__dso_handle")

  • Compares user input against "__dso_handle".
  • If equal, strcmp() returns 0.

Then we enter this condition:

1
if ((-1 < iVar1) && (iVar1 = strcmp(local_28,"__dso_handle"), iVar1 < 1))

🧠 What’s Going On?

  • This is a tricky condition.
  • It does a double comparison against "__dso_handle", structured like:
1
if (strcmp(...) >= 0 && strcmp(...) <= 0)
  • So the only possible value that passes is when: strcmp(...) == 0

If input is "__dso_handle" until if true:

  • printf("Try again!") is shown. -Function returns early.

🔐 This acts as a blacklist: if you input "__dso_handle" → it gets blocked.


strcmp(local_28, "_init")

  • If the above check is bypassed, the code now compares input with “_init”.

✅ If local_28 == “_init” Message: Correct!

❌ Else Message: Try again!

✅ Correct Summary

  • If input is "__dso_handle" → “Try again!” Failure because Blacklisted.
  • If input is "_init" → “Correct!”
  • Anything else → “Try again!”
1
DoYouEven<password>

And only <password> is stored.


Final Payload & Result

This triggers the “Correct!” message because:

  • scanf extracts _init (between DoYouEven and the end of input).

  • _init is not blacklisted and matches the expected string.

  • If you omit CTF, scanf will still store _init (since %s stops at whitespace or end of input) ==> correct, but some binaries may reject due to extra input such this case.

  • CTF (matches format, but ignored).

%s in scanf stops reading when it encounters several cases:

  • Whitespace (space, tab, newline).
  • End of input (if no more characters are available).

like that

1
./Compiled.Compiled

Enter:

1
DoYouEven_initCTF

Output:

1
Try again!

1
./Compiled.Compiled

Enter:

1
DoYouEven_init

Output:

1
Correct!
1
ltrace ./Compiled.Compiled

Enter:

1
 DoYouEven_init

Output:

1
2
3
4
5
6
7
8
fwrite("Password: ", 1, 10, 0x7f95a0c605c0)           = 10
__isoc99_scanf(0x5601aa8f200f, 0x7ffd41387e90, 0, 1024Password: DoYouEven_init
) = 1
strcmp("_init", "__dso_handle")                       = 10
strcmp("_init", "__dso_handle")                       = 10
strcmp("_init", "_init")                              = 0
printf("Correct!")                                    = 8
Correct!+++ exited (status 0) +++

👏 Clear indication that _init is accepted after bypassing the blacklist & making strcmp("_init", "_init") = 0.


This post is licensed under CC BY 4.0 by the author.