A binary executable file named "cw2" was provided for analysis. During the analysis process, several issues were discovered including hardcoded credentials, a format string vulnerability, and a stack-based buffer overflow. By combining these weaknesses, it was possible to leak memory values, bypass protections, and eventually construct a working exploit. Let's get right into it.
Binary Reconnaissance
The cw2 file is a Linux executable. It is a 64-bit program designed to run on x86–64 systems. The file accepts user input through a menu-based interface and supports multiple user roles.

A. Security Protection Check
Before diving deeper into the binary, the first step is to understand what security protections are enabled. Tools like checksec help quickly identify built-in protections such as stack canaries, NX, PIE, and RELRO that may affect exploitation.

B. Hardcoded Credentials
Next, I used strings command on the binary. This command prints readable text stored inside the file. It is a quick way to see if anything useful is left inside the program.

The output shows something interesting. The program stores both the username and password inside the binary. They appear in plain text. This means anyone who has the file can read the login details. There is no need to guess the password or try many attempts.
Binary Analysis in Ghidra
After logging in with the recovered credentials, the next step was to look inside the program itself. For this, the binary was opened in Ghidra. Ghidra converts machine code into a readable form. This helps us see how the program works and how it handles user input.
As the binary was loaded into Ghidra, all the functions were loaded and verified. Within the function entitied "modify_existing_privileges", something interesting or rather vulnerable was identified.

A. Format String Vulnerability
A format string vulnerability happens when user input is passed directly to printf as the format. It allows memory values to be read or changed at runtime in the function.
From a simple input as "%p", memory addresses can be leaked.

B. Buffer Overflow
A buffer overflow happens when a program writes more data than a buffer can hold, which can crash the program or let an attacker control its behavior. The program uses a variable called 'data_buffer_size' to decide how much data is handled.
In this case, by the help of Format String Vulnerability, we can make changes in the size of the variable that allows buffer overflow. The 'data_buffer_size' is a global variable and is later used to input in the function "modify_existing_modules".

Attack Narrative
The attack follows a clear and simple path. First, access is gained by using weak login checks that rely on fixed values. This allows entry without strict limits. After access, a format string issue is used to read values from memory. These values help understand how the program stores data during run time. The same issue is then used to change a size value that controls how much input is accepted. Once this limit is raised, the program accepts more data than it should. This extra data is passed into a function that copies input into a fixed buffer. No length check is done at this point. As a result, data flows past the buffer boundary. This confirms a buffer overflow state. The goal here is only to reach and show this state.

Exploitation
1. Fuzzing for PIE and libc Address Leaks
Because stack canary protection is enabled, the canary value must be known before triggering the overflow. If the value changes, the program stops and the payload fails. The format string bug allows stack values to be printed, which makes it possible to leak the canary. Once the value is known, it can be placed back in the payload so the stack check passes.
The binary also has PIE enabled, which means the program loads at a different memory address each time it runs. Because of this, fixed addresses cannot be used in the exploit. A libc address leak is required first so that the runtime address of libc can be identified. Once a libc address is leaked, the base address of libc can be calculated by subtracting the known offset of that function from the leaked value. After the libc base is known, other addresses inside libc can be found by adding their offsets. This makes it possible to get the correct address of functions like system, /bin/sh and so on for the final payload.

Note that in 64-bit systems, the canary is 8 bytes long (64 bits) and it usually ends with 00.

Note that for debugging, the tool being used is pwndbg.
2. Address Leak and Resizing Variable
Initially, we leak the required canary address along with the libc address through the indexes %43$p and $45$p as verified from fuzzer. The exploitaion must be done in one go as you are not allowed to update the privilege i.e., access the format string vulnerability more than once in a single runtime.
The size of the format string vulnerability was found to be hardcoded at 18 bytes.

A. Payload Construction
Because the input field for the format string was limited to 18 bytes, the payload had to remain short while still performing multiple tasks. The payload used was:
%45$800p%1$n%43$pThis payload first prints a large number of characters using %45$800p. This increases the total number of printed characters so that when %1$n runs, that value gets written into the data_buffer_size variable. As a result, the allowed input size becomes much larger than the original limit.

At the same time, %43$p is used to leak the stack canary value from the stack. The earlier fuzzing step showed that index 43 holds the canary and index 45 points to a libc address. By combining these format specifiers into one payload, the exploit is able to resize the buffer limit and leak required values in a single execution, which is necessary since the vulnerable function can only be triggered once per runtime.
figure: sending of payload or block of code
B. Offsets and ROP Chain Setup
Before buffer overflow, all the offsets and ROP chain is set up. A ROP (Return Oriented Programming) chain is a sequence of small instruction blocks already present in the program or libraries that are linked together to control the program flow and execute desired functions.


After calculating the libc base address, the next step was to find useful ROP gadgets inside libc. These are short instruction sequences that end with ret and can be chained to control execution. The tool ropper was used to search the libc file for gadgets, and the command below was used to find the pop rdi; ret gadget, which is used to place a value into the RDI register before calling functions like system().

C. Buffer Overflow
A buffer overflow happens when a program writes more data into a buffer than the buffer can hold. The extra data spills into nearby memory on the stack. This can overwrite important values.
Buffer overflow can be carried out by logging in as 'teacher' in 'verify_existing_modules' function.

From here, the canary offset was calculated. And the following final payload was constucted.

The final payload is built to overflow the buffer and still pass the stack check. First, 616 bytes of padding fill the buffer. After that, the leaked canary value is placed back so the program does not stop. Then 8 bytes overwrite the saved base pointer. Two 'ret' gadgets keep the stack aligned before the next step.
After this, the ROP chain runs. The gadget pop rdi ; ret loads 0 into the RDI register and calls setuid(0) to set the process as root. Another pop rdi ; ret loads the address of "/bin/sh", and system() runs it. This opens a shell and gives root access.

This exploit shows how multiple small weaknesses can combine into a full system compromise. Hardcoded credentials allowed initial access. The format string vulnerability leaked memory and changed the input limit. Finally, the buffer overflow allowed control of the return address and execution of a ROP chain. By calling setuid(0) and system("/bin/sh"), the exploit successfully spawned a root shell. The vulnerability wouldn't even be triggered if the format string vulnerability didn't exist.
Mitigations
- Remove hardcoded credentials from the binary.
- Use fixed format strings in printf to prevent format string bugs (e.g., printf("%s", user_input);).
- Enforce strict input size limits to prevent buffer overflow.
- Apply secure coding practices and enable protections like ASLR, PIE, NX, and stack canaries.
If you want access to the binary file or the final exploit script used in this write-up, feel free to contact me.
#Cybersecurity #BinaryExploitation #Linux #CTF #BufferOverflow #FormatString #HappyHacking