Perfectroot CTF 2024
Table of Contents
- This is the writeup for challenges I solved in the P3rf3ctr00t 2024 CTF.
1. Pores #
- 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:
-
The file was an
ELF 64-bit LSB pie executable
,dynamically linked
andnot 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. -
There was nothing overtly obvious like a flag but it contained strings
flag
andprintFlag
. 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
-
The process immediately exited with no output.
Analysis Using Ghidra #
- The next step was to load the binary into Ghidra
- From the Symbol Tree in Ghidra, 2 functions of interest were
main
(program entry point) andprintFlag
(function to give the flag supposedly)
Analysis of main
: #
- The decompiled code showed that
main
didn’t invokeprintFlag
directly—it just exited with a return value of 0
Analysis of printFlag
#
- To execute correctly, the
printFlag
function requires the following arguments:
- A valid memory address (
param_1
) pointing to the flag data. - 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 callprintFlag
, we can manually invoke it to execute it.
- First, we load the binary into gdb.
- Next, we set a breakpoint at
main
- 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: - 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 ofprintFlag
function. - When calling
printFlag
in GDB, we have to cast the call to avoid
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) and8
(the number of blocks indicating how many 8-byte chunks of data exist). - And we get the flag :)
r00t{p4tch_th3_bin_and_h4ve_fun}
Approach 2 #
main
Control Flow #
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: #
- mov DWORD PTR [rbp-0x4], 0x0: The program initializes a local variable at [rbp-0x4] to 0.
Comparison: #
- cmp DWORD PTR [rbp-0x4], 0x1: The program compares the value at [rbp-0x4] with 1.
Conditional Jump: #
- 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:
- Using GDB: Modify the value of [rbp-0x4] in the debugger.
- 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:
- 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 #
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