Skip to main content
  1. ctf-writeups/

Perfectroot CTF 2024

·5 mins
Perfectroot CTF
  • This is the writeup for challenges I solved in the P3rf3ctr00t 2024 CTF.

1. Pores #

Perfectroot CTF
  • We were given a binary that we needed to reverse engineer to find the flag.
  • There 2 approaches that I used to get the flag. I’m going to go through both

Approach 1 #

Initial Analysis #

  • I downloaded the binary and examined the file type using the file utility found on Linux:
Perfectroot CTF
  • The file was an ELF 64-bit LSB pie executable, dynamically linked and not stripped, meaning it contained debugging symbols for easier analysis

  • Next, I ran strings on the binary to extract any readable text that could provide hints about the flag or the internal workings of the program.

    Perfectroot CTF

  • There was nothing overtly obvious like a flag but it contained strings flag and printFlag. I noted them down to look into them further using Ghidra.

  • Next, I decided to execute the program to observe its behavior. I first made it executable using chmod +x poresssss then ran it using ./poresssss

    Perfectroot CTF

  • The process immediately exited with no output.

Analysis Using Ghidra #

  • The next step was to load the binary into Ghidra
    Perfectroot CTF
  • From the Symbol Tree in Ghidra, 2 functions of interest were main (program entry point) and printFlag (function to give the flag supposedly)

Analysis of main: #

Perfectroot CTF
  • The decompiled code showed that main didn’t invoke printFlag directly—it just exited with a return value of 0

Analysis of printFlag #

Perfectroot CTF
Perfectroot CTF

  • To execute correctly, the printFlag function requires the following arguments:
  1. A valid memory address (param_1) pointing to the flag data.
  2. The number of blocks (param_2) indicating how many 8-byte chunks of data exist.
  • Without these inputs, the function cannot print the flag.

Analysis Using GDB #

  • Since main does not call printFlag, we can manually invoke it to execute it.
    Perfectroot CTF
  1. First, we load the binary into gdb.
  2. Next, we set a breakpoint at main
  3. Finally, we run the program and hit the breakpoint
  • Now we have control of the program in the running (but paused) state!
  • We can view/edit memory, modify registers, continue execution, jump to another part of the code, and much much more!

Disassemble main #

  • When we disassemble main we got the following control flow:
    Perfectroot CTF
  • We can see that the flag is stored at address 0x555555558040.
  • I tried examining the string at that address using x/s 0x555555558040 but it gave me some non-printable ascii characters.

Manually calling the printFlag function #

  • We’re going to move the program’s execution to the printFlag function.
  • Specifically, we’re just going to use gdb’s call command to resume execution at the start of printFlag function.
  • When calling printFlag in GDB, we have to cast the call to a void return type in GDB for it to be recognized. This is because GDB doesn’t automatically know how to handle a function that returns void, so we use the cast to prevent errors
  • Remember we need to pass 2 arguments to the function, 0x555555558040 (address of flag) and 8 (the number of blocks indicating how many 8-byte chunks of data exist).
    Perfectroot CTF
  • And we get the flag :)

r00t{p4tch_th3_bin_and_h4ve_fun}

Approach 2 #

Perfectroot CTF

main Control Flow #

Perfectroot CTF

Code Explanation #

  • The key part of the code is the sequence of instructions responsible for comparing a local variable and printing the flag if a specific condition is met:

Setting the Variable: #

  1. mov DWORD PTR [rbp-0x4], 0x0: The program initializes a local variable at [rbp-0x4] to 0.

Comparison: #

  1. cmp DWORD PTR [rbp-0x4], 0x1: The program compares the value at [rbp-0x4] with 1.

Conditional Jump: #

  1. jne 0x5555555552c1 <main+41>: If the value at [rbp-0x4] is not equal to 1, the program jumps to address 0x5555555552c1 (skipping the flag-printing logic).

Flag Printing Logic: #

  • If the value at [rbp-0x4] is 1, the program continues execution:
  • mov esi, 0x8: Sets up a parameter (likely for the flag length).
  • lea rax, [rip+0x2d87]: Loads the address of the flag into the rax register.
  • mov rdi, rax: Passes the flag address as the first argument to the printFlag function.
  • call printFlag: Calls the printFlag function, which prints the flag.
  • Exit: The program then exits by setting eax to 0, indicating a successful run.

How to Bypass the Flag Check #

  • The key to this challenge is the conditional check in cmp DWORD PTR [rbp-0x4], 0x1. By default, this variable is set to 0, which causes the program to skip the flag printing logic via the jne instruction.
  • To get the flag, we need to modify the value at [rbp-0x4] to 1. This can be done through various methods, such as:
  1. Using GDB: Modify the value of [rbp-0x4] in the debugger.
  2. Patching the Binary: Modify the binary to set the value to 1 directly.

Steps Using GDB #

  • Run the Program in GDB: Launch the program in GDB to set a breakpoint and inspect the execution:
Perfectroot CTF
  • I set a breakpoint at main and stepped through the instructions until [rbp-0x4] was set to 0.
  • Then, I manually changed [rbp-0x4] to 1 using set *(int*)($rbp-4)=1, continuing execution:
  • And we got the flag :)

2. Adversary Within - Part 1 #

Perfectroot CTF

Approach: #

  • Breaking down the key elements of the challenge:

1. “…roasts”: This was the most telling clue. The term “roasts” is a subtle reference to Kerberos, the authentication protocol used by Active Directory (AD). In the context of AD, the term “Kerberoasting” refers to an attack technique where attackers request service tickets for service accounts in AD and attempt to crack them offline. The “roasts” here were a direct hint toward this attack method.

2. “backbone connecting users and resources in every environment”: This phrase clearly pointed to a system responsible for managing users and their access to resources in an environment. Active Directory (AD) is the most ubiquitous service for managing identities, permissions, and resource access in a network, especially in enterprise environments.

3. “I’m everywhere, supporting every interaction”: Active Directory is indeed the backbone of user authentication and resource management in virtually every corporate network, supporting all interactions that require authentication and authorization.

4. “What are my rules called?”: The reference to “rules” is crucial. In Active Directory, the schema defines the structure of the directory, including object types, attributes, and how those objects interact. The schema essentially sets the “rules” for how the data is organized and used in AD.

  • And so the flag was schema :)

  • Until next time