106 lines
3.9 KiB
Markdown
106 lines
3.9 KiB
Markdown
# Echo Chamber
|
|
|
|
Hello there! Welcome to the write-up for **Echo Chamber**. This is a classic "pwn" challenge that demonstrates a very common but powerful vulnerability: the **Format String Vulnerability**.
|
|
|
|
In this challenge, we are given a compiled binary. When we don't have the original source code, we use tools like **Ghidra** to decompile the binary and see what the program is doing "under the hood."
|
|
|
|
---
|
|
|
|
## 1. Initial Reconnaissance
|
|
|
|
When we run the program, it asks for some input and "echoes" it back:
|
|
|
|
```text
|
|
Welcome to the Echo Chamber!
|
|
Give me a phrase, and I will shout it back: Hello!
|
|
You said: Hello!
|
|
```
|
|
|
|
The developer's description gives us a hint:
|
|
> "The developer claims it's perfectly secure because 'it doesn't execute any code, it just prints text.'"
|
|
|
|
This is a classic "famous last words" situation in security! Let's look at the decompiled code to see why.
|
|
|
|
## 2. Analyzing the Decompiled Code (Ghidra)
|
|
|
|
Opening the binary in Ghidra, we find the `vuln()` function. Here is the pseudo-code it gives us:
|
|
|
|
```c
|
|
void vuln(void)
|
|
{
|
|
char acStack_a0 [64]; // Our input buffer
|
|
char local_60 [72]; // Where the flag is stored
|
|
FILE *local_18;
|
|
|
|
local_18 = fopen64("flag.txt","r");
|
|
if (local_18 == (FILE *)0x0) {
|
|
puts("Flag file is missing!");
|
|
exit(0);
|
|
}
|
|
|
|
// 1. The flag is read into local_60
|
|
fgets(local_60,0x40,local_18);
|
|
fclose(local_18);
|
|
|
|
puts("Welcome to the Echo Chamber!");
|
|
printf("Give me a phrase, and I will shout it back: ");
|
|
|
|
// 2. Our input is read into acStack_a0
|
|
fgets(acStack_a0,0x40,(FILE *)stdin);
|
|
|
|
printf("You said: ");
|
|
// 3. VULNERABILITY: Our input is passed directly to printf!
|
|
printf(acStack_a0);
|
|
putchar(10);
|
|
return;
|
|
}
|
|
```
|
|
|
|
Do you see that line `printf(acStack_a0);`? This is our "Golden Ticket."
|
|
|
|
## 3. The Vulnerability: Format Strings
|
|
|
|
In C, `printf` expects its first argument to be a **format string** (like `"%s"` or `"Hello %s"`). If a developer passes user input directly to `printf`, the user can provide their own format specifiers.
|
|
|
|
When `printf` sees a specifier like `%p` (print pointer) or `%x` (print hex), it looks for the next argument on the **stack**. If we don't provide any arguments, `printf` will just start leaking whatever is already on the stack!
|
|
|
|
### Where is the flag?
|
|
Looking at the Ghidra output, notice that both `acStack_a0` (our input) and `local_60` (the flag) are **local variables**. This means they are both stored on the stack right next to each other.
|
|
|
|
## 4. Exploiting the "Echo"
|
|
|
|
If we send a string of format specifiers like `%p %p %p %p %p %p...`, we can trick `printf` into printing the contents of the stack. Since the flag is sitting on the stack, it will eventually be printed!
|
|
|
|
Try providing this as input:
|
|
`%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p`
|
|
|
|
The program will respond with a series of hex addresses. Some of these values will actually be the ASCII characters of our flag.
|
|
|
|
### Little Endianness
|
|
When you see the hex values, remember that modern systems use **Little Endian** byte ordering. This means the bytes are stored in reverse order.
|
|
|
|
For example, if you see `0x7b67616c66`, and you convert those bytes from hex to ASCII:
|
|
- `66` = `f`
|
|
- `6c` = `l`
|
|
- `61` = `a`
|
|
- `67` = `g`
|
|
- `7b` = `{`
|
|
|
|
The value `0x7b67616c66` represents `flag{` in reverse!
|
|
|
|
## 5. Putting it all Together
|
|
|
|
To solve the challenge:
|
|
1. Connect to the service.
|
|
2. Send many `%p` specifiers to leak the stack.
|
|
3. Identify the hex values that look like printable text (starting with `0x...` and containing ASCII values).
|
|
4. Reverse the bytes (Endianness) and convert them to characters.
|
|
5. Combine the parts to find the flag!
|
|
|
|
## Lessons Learned
|
|
|
|
Even if a program "just prints text," it's not safe if it uses `printf` incorrectly. The fix is simple: always use `printf("%s", buffer);`. This ensures the input is treated as a literal string, not as code or instructions for the function.
|
|
|
|
Happy Hacking!
|
|
|