Full Report
In the previous post, a format string vulnerability was found that led to a crash. This post is all about exploiting the vulnerability to get code execution. The vulnerability occurs in the stdout logging because of a call to fprintf taking in a user controlled string with no other parameters. Simply sending a %99999s will crash the program. What else can we do besides crash? With format string bugs, the identifier %n can be used to perform writes. However, the format string is stored on the heap. This means that the simple %x$n doesn't work. This is because the format string parser will attempt to find data on the stack for pointers and it will not be our controlled data. Although we cannot control the location being written to via %n, we can still write to ANY pointer on the stack with a user controlled value. What if there are user controlled pointers on the stack though? By design, the Base Pointer (BP) is exactly this. So, here's the trick. Since we can perform this format string vulnerability multiple times, we can abuse that. On the first write, we'll use a pointer on the stack to write a user controlled value on the stack. This will be the address we want to write in the future. On the second write, we will use the %x$n trick with this address and other user controlled value by incrementing the amount of spaces in use. With this technique, we have an arbitrary write primitive! Let's make this primitive better! Currently, it is writing 134.5 MB of padding to stdout because of the amount of filler bytes required for %n to write the address. To get around this, the %hhn specifier can be used to write a single byte at a time. This led to a 1/16 chance that the attack would work because of base pointer alignment. The author decided that the speed gain was worth it but wanted to figure out the 1/16 problem beforehand. They noticed that the RPC GetVersion returned a global variable. By performing a write to corrupt the string in the version with the format string bug, we can leak a stack address. In particular, using a relative write on the global address to the name to point to the .bss section argv pointer, we can leak a stack address. This requires a slow 2 byte write but makes this much faster later on. Once we have the arbitrary write primitive, we need something to write! The binary is compiled without PIE, meaning that we can corrupt the global variables for the binary without needing a memory leak. From the arbitrary write and the stack leak, we can trivially ROP the binary. The chain, which is made easier by the binary being statically compiled, calls mprotect to make the stack executable then jumping to shellcode written beforehand. They created a reverse shell with this. The code for the ROP is interesting since it makes heavy use of the pwntools functionality instead of hardcoding addresses and sycalls. Pretty neat to see and something I'd use in the future. Overall, great post on format string exploitation.
Analysis Summary
# Tool/Technique: Format String Vulnerability Exploitation
## Overview
This summary details the exploitation of a format string vulnerability found in the stdout logging mechanism of a Linux-based Trackmania Nations Forever (TMNF) server. The core objective of the exploitation was to move from a denial-of-service (crash) condition to achieving Remote Code Execution (RCE) by developing an arbitrary memory write primitive.
## Technical Details
- Type: Technique
- Platform: Linux (Targeting Trackmania Forever server)
- Capabilities: Information disclosure, arbitrary memory write, Remote Code Execution (RCE) via Return-Oriented Programming (ROP).
- First Seen: The analysis was published around October 2022.
## MITRE ATT&CK Mapping
- T1059 - Command and Scripting Interpreter
- T1059.004 - Command and Scripting Interpreter: Unix Shell
- T1071 - Application Layer Protocol
- T1071.001 - Application Layer Protocol: Web Protocols (Likely related to RPC/XML communication)
- T1204 - User Execution
- T1204.002 - User Execution: Malicious File
- T1190 - Exploit Public-Facing Application
- T1566 - Phishing (Initial access often involves exploiting a service)
- T1218 - Signed Binary Proxy Execution
- T1218.011 - Signed Binary Proxy Execution: Mismatched Permissions on Executable Wrapper / Shellcode execution after memory manipulation.
## Functionality
### Core Capabilities
* **Denial of Service (DoS):** Initial observation was a crash caused by sending `' %999999s '` to an affected RPC endpoint (`GetChallengeInfo`), leveraging the vulnerable `fprintf` call taking user-controlled input as the format string.
* **Information Leakage (Memory Reading):** Basic format specifiers (`%p`) allow leaking stack pointers, providing visibility into memory layout. A specific technique was used involving corrupting a global variable returned by the RPC `GetVersion` to leak a stack address using a slow two-byte write.
* **Arbitrary Write Primitive:** The primary goal was achieved by chaining two format string writes targeting user-controlled pointers on the stack, specifically the Base Pointer (BP), to first write a desired address onto the stack, and then use that newly written address as the target for a subsequent `%n` style write, resulting in an arbitrary write primitive.
### Advanced Features
* **Arbitrary Write Optimization:** The initial arbitrary write required excessive padding (134.5 MB) due to the distance between the format string arguments and the target pointer when using `%n`. This was optimized by switching to the `%hhn` specifier to write one byte at a time, significantly speeding up the process, although this introduced a 1 in 16 chance of failure possibly due to base pointer alignment.
* **Bypassing Protections:** The binary was compiled **without Position Independent Executables (PIE)**, simplifying the exploitation by making global variable addresses static and predictable.
* **Remote Code Execution (RCE):** Achieved via a Return-Oriented Programming (ROP) chain.
1. **ROP Chain Construction:** Made easier by static compilation. The chain utilized available gadgets.
2. **Payload Execution:** The ROP chain calls `mprotect` to mark the stack as executable, followed by jumping to pre-written shellcode.
3. **Shellcode:** The shellcode implemented a **reverse shell** connection. The ROP chain construction leveraged the `pwntools` framework rather than hardcoding syscalls or addresses.
## Indicators of Compromise
- File Hashes: Not specified in the context for the final shellcode or exploit script.
- File Names: Not specified.
- Registry Keys: Not applicable (Linux target).
- Network Indicators: The final payload established a **reverse shell**, implying outbound connections to an attacker-controlled IP/port. (Specific indicators not provided, but connection behavior is expected.)
- Behavioral Indicators:
- Unexpected invocation of `fprintf` with user-supplied, un-sanitized string input.
- Unusual stack manipulation leading to data being written to locations outside the intended scope.
- Execution of code from non-standard memory regions (e.g., the stack) after successful memory permission modification (`mprotect`).
## Associated Threat Actors
- Not explicitly attributed to a known threat actor group; this appears to be research/proof-of-concept exploitation published by an independent researcher.
## Detection Methods
- **Signature-based detection:** Signatures targeting specific format string payloads (e.g., sequences involving high numbers of `%s`, `%x`, or `%n`).
- **Behavioral detection:** Monitoring for processes that attempt to change memory page permissions (e.g., calls to `mprotect`, `mmap` with `PROT_EXEC`) on segments like the stack or heap.
- **Static Analysis:** Detection of vulnerable code patterns, specifically `fprintf` or `printf` calls where the format string argument comes directly from untrusted input without proper sanitization or a constant format string.
## Mitigation Strategies
- **Input Validation:** Ensuring that user-controlled strings are never used directly as format strings in functions like `fprintf` or `printf`. A safe alternative is `fprintf(stderr, "%s\n", user_input);`.
- **Firewalling:** Restricting network access to the vulnerable RPC service port (5000-5100 range was scanned in the article).
- **Security Banners/Configuration:** For the specific application, ensuring the `/nodaemon` flag is **not** used during server startup, as this opens the vulnerable code path.
- **Compilation Flags:** Modern binaries should be compiled with PIE enabled to counter the simplification provided by static addresses.
## Related Tools/Techniques
- Binary Exploitation Techniques: Return-Oriented Programming (ROP).
- Format String Attack Variants: %n, %hn, %hhn writes.
- Exploitation Frameworks: `pwntools` (used for crafting the final ROP chain).
- Vulnerable Logging Functions: `printf`, `sprintf`, `syslog`, etc.