# BUGS — Vulnerability Descriptions & Exploitation Notes

**SPOILERS BELOW.** Try to find the bugs yourself first.

---

## Bug 1: Heap Buffer Overflow → House of Force

**Location**: `handle_info()` — the `X-Device-Id` header processing

**Root cause**: `strcpy(g_device_id, dev)` copies an attacker-controlled
header value into a `malloc(32)` buffer with no length check.

**What makes it interesting**: When `g_device_id` is the last allocation
on the heap, the overflow writes directly into the **top chunk** (the
wilderness). On glibc ≤ 2.28, there's no validation of the top chunk
size, enabling House of Force.

**Exploitation flow (glibc 2.23)**:

1. Send `GET /api/debug` to leak heap addresses, GOT addresses, and
   function pointers.

2. Send `GET /api/info` with a crafted `X-Device-Id` header:
   - 32 bytes of padding (fills the dev_id buffer)
   - 8 bytes for the top chunk's `prev_size` field
   - 8 bytes of `\xff` to overwrite the top chunk `size` with
     `0xffffffffffffffff`

   The top chunk now appears to span the entire address space.

3. Send `POST /api/firmware` with:
   - `X-Firmware-Size: <evil_size>` where:
     `evil_size = target_addr - top_chunk_addr - 2*sizeof(size_t) - chunk_overhead`
   - This `malloc(evil_size)` advances the top chunk pointer to just
     before your target (e.g., `free@GOT`)
   - `X-Firmware-Desc: <payload>` — the data to write at the target
   - A small body starting with `FWUP` to pass validation

   The handler then does `malloc(64)` for the metadata buffer, which
   returns a pointer at the target address. `strcpy(meta, desc_str)`
   writes your payload there.

**Target ideas**:
- Overwrite `free@GOT` → `system`, then free a buffer containing
  `/bin/sh` (e.g., via the diagnostic endpoint)
- Overwrite `__free_hook` (writable on glibc 2.23 with partial RELRO)

**Key constraint**: `strcpy` stops at null bytes, so the overflow payload
and the fake top chunk size must not contain `\x00`. The value
`0xffffffffffffffff` has no null bytes, so it works.

**glibc version notes**: House of Force was killed in glibc 2.29 by
adding `assert(size == top_size)` in the top chunk. Works on all uClibc
versions.

---

## Bug 2: Use-After-Free → Function Pointer Hijack

**Location**: `handle_logout()` frees the session, `handle_status()`
uses it afterward.

**Root cause**: `handle_logout()` calls `free(s)` but does **not** set
`g_sessions[i] = NULL`. The pointer remains in the global table.
`find_session()` still returns it. `handle_status()` calls
`s->log_action(logmsg)` — a function pointer read from freed memory.

**Exploitation flow**:

1. `POST /api/login` — create a session, note the session ID.

2. `GET /api/logout` with `X-Session-Id: <id>` — frees the `session_t`
   struct (size: ~72 bytes on x86-64, fits in a specific bin).

3. Trigger a new allocation of the **same size** that you control.
   The allocator recycles the freed chunk. Your data overlaps the
   old `session_t` fields, including the `log_action` function pointer
   at a known offset.

   Possible ways to get a same-size allocation with controlled content:
   - `POST /api/config` — the `strdup(key)` or `malloc(PARAM_VAL_SZ)`
     allocations might match. `strdup` allocates `strlen+1`.
   - `POST /api/login` with a username of the right length — the
     `create_session()` call does `malloc(sizeof(session_t))`.

4. `GET /api/status` with the **same session ID** — `find_session()`
   finds the old pointer (still in `g_sessions[]`), and calls
   `s->log_action(logmsg)`. If you've placed `system` at the
   `log_action` offset, this becomes `system(logmsg)`, and `logmsg`
   is `"status check by <username>"` — partially controlled via the
   username in the overlapping struct.

**Key detail**: `sizeof(session_t)` determines which bin the freed chunk
lands in. Calculate this on your target (should be around 72 bytes with
padding, landing in a smallbin or fastbin depending on exact size).

**Advanced variant**: Chain with Bug 3 to get more precise control over
the overlapping allocation content.

---

## Bug 3: Heap Buffer Overflow in Parameter Parsing

**Location**: `handle_config()` — URL-encoded parameter value handling.

**Root cause**: Each parameter value is allocated as `malloc(PARAM_VAL_SZ)`
(64 bytes), but the actual value is copied with `strcpy(values[count], eq + 1)`.
If a parameter value exceeds 64 bytes, the copy overflows into the
adjacent heap chunk.

**What you can corrupt**: The overflow writes past the 64-byte buffer
into whatever chunk follows it on the heap. This could be:
- Another parameter's `key` or `value` allocation
- A `strdup`'d key from the same parsing loop
- Any other active heap chunk

**Exploitation ideas**:

- **Chunk metadata corruption**: Overflow into the next chunk's `size`
  and `prev_size` fields. On glibc 2.23, this enables:
  - **Fastbin corruption**: Overwrite a freed fastbin chunk's `fd`
    pointer to get an arbitrary write on the next `malloc` of that size.
  - **Unsorted bin corruption**: Overwrite `bk` to get a write to
    `&main_arena` on glibc's unsorted bin scan.
  - **Overlapping chunks**: Forge chunk sizes to make a later `malloc`
    return a buffer that overlaps a live object (e.g., a `session_t`).

- **Chain with Bug 2**: Overflow to corrupt a freed session_t's
  `log_action` pointer, then trigger the UAF status check.

**Trigger**: Send a POST to `/api/config` with:
```
ssid=AAAA...130_bytes...&password=short
```
The `ssid` value overflows its 64-byte buffer.

**Note**: The overflow is via `strcpy`, so no null bytes in the payload.
The `strtok_r(&)` parsing means `&` terminates each value.

---

## Bug 4: Double Free in Diagnostic Error Path

**Location**: `handle_diag()` — the "unrecognized command" error path.

**Root cause**: When the diagnostic command is not recognized, the
handler does `free(req->body)` before returning. But `cleanup_request()`
in the main loop **also** frees `req->body`. The pointer is not set to
NULL after the first free in `handle_diag()`.

**Double free sequence**:
1. `handle_diag()`: `free(req->body)` — first free
2. Return to `handle_connection()`
3. `cleanup_request()`: `free(req->body)` — second free (same pointer)

**Exploitation (glibc 2.23)**:

The exploitability depends on the size of `req->body` (== `Content-Length`):

- **Fastbin range (CL ≤ ~120 bytes)**: glibc 2.23 checks
  `fastbin_head == chunk` and aborts ("double free or corruption
  (fasttop)"). To bypass: you need an intervening `free` of a same-size
  chunk between the two frees, which doesn't happen here. However, if
  `handle_diag` also frees `result` (256 bytes) before `req->body`,
  and both are in different size classes, the check doesn't trigger
  for the body's fastbin.

  **Actually**: look carefully — in the error path, `free(result)` is
  called before `free(req->body)`. If `result` is in a different size
  class (which it is — 256 bytes vs. your CL), the fastbin head for
  body's size class hasn't changed, so glibc WILL catch it... unless
  you use a `Content-Length` outside fastbin range.

- **Smallbin/unsorted bin range (CL ≥ ~128 bytes)**: The check is
  weaker. The double free puts the same chunk in the unsorted bin
  twice, creating a cycle. The next two `malloc`s of that size return
  the same pointer, giving you overlapping allocations — write through
  one, read/use through the other.

**Practical approach**:
1. Send `POST /api/diag` with `Content-Length: 200` and a body that
   does NOT start with "PING" or "UPTIME".
2. The double free occurs on a 200-byte chunk.
3. On the next connection (or same connection, next request), two
   allocations of ~200 bytes return the same address.
4. Use one to write controlled data, the other is used as a live
   object — overlapping allocations achieve type confusion.

**Chain idea**: Double-free the body, then `POST /api/login` (which
allocates a `session_t`) and `POST /api/config` (which allocates
param values). If sizes align, the login session and a config value
share the same memory — write through config, hijack through the
session's function pointer.

---

## General Exploitation Tips

- **Start with `/api/debug`**: It leaks heap addresses, GOT entries,
  and function pointers. In real research you'd need an info-leak bug
  first; this endpoint simulates having already found one.

- **Keep-alive is essential**: The server loops on requests within a
  single connection. Multi-step exploits (overflow → controlled alloc →
  trigger) work within one TCP session.

- **pwntools skeleton**:
  ```python
  from pwn import *

  r = remote('localhost', 8080)

  def send_req(method, path, headers={}, body=None):
      req = f"{method} {path} HTTP/1.1\r\n"
      if body:
          headers["Content-Length"] = str(len(body))
      for k, v in headers.items():
          req += f"{k}: {v}\r\n"
      req += "\r\n"
      r.send(req.encode())
      if body:
          r.send(body if isinstance(body, bytes) else body.encode())
      return r.recvuntil(b"\r\n\r\n")  # headers; body follows

  # Step 1: Leak
  send_req("GET", "/api/debug")
  resp = r.recvuntil(b"</pre>")
  # parse addresses...

  # Step 2: Exploit...
  ```

- **ASAN for validation**: Build with `make asan` to confirm your trigger
  hits the right bug. ASAN prints the exact allocation and free sites.

- **Progression**: Start with the info leaks (Bugs 5 and 6) to get
  addresses without `/api/debug`. Then trigger Bugs 3 and 4 (easiest
  corruption). Finally, chain a full exploit: info leak → Bug 1 (House
  of Force) or Bug 2 (UAF) → code execution.

---

## Bug 5: Heap Over-Read (Heartbleed-style)

**Location**: `handle_log()` — the X-Log-Size header controls response size

**Root cause**: The handler allocates a 128-byte buffer (`LOG_BUF_SZ`)
for the log text, then allocates a `log_cursor_t` struct right after it
on the heap. The response length is controlled by the client's
`X-Log-Size` header, but the `write()` call sends `resp_sz` bytes
starting from the 128-byte buffer. When `resp_sz > 128`, the write
reads past the buffer into the adjacent `log_cursor_t` struct — which
contains a **function pointer** (`emit`) and a **heap pointer**
(`buf_ref`).

**What gets leaked (offsets from start of response)**:

The exact layout depends on the allocator, but on glibc 2.23 x86-64:
- Bytes 0–127: log text (the intended response)
- Bytes 128–143: chunk metadata (prev_size + size of the cursor chunk)
- Bytes 144+: `log_cursor_t` fields:
  - `seq` and `total`: 8 bytes (two ints)
  - `emit`: 8 bytes — **code address** (points to `log_admin`)
  - `tag[24]`: string "activity"
  - `buf_ref`: 8 bytes — **heap address** (points to logbuf)

**Exploitation**:

```python
# Request 320 bytes when only 128 are allocated
resp = send_req("GET", "/api/log", {"X-Log-Size": "320"})
# Parse the response body for 8-byte aligned pointer-like values
# starting at offset 128
```

This gives you:
- The address of `log_admin` → calculate the binary base (if PIE) or
  confirm known function addresses (no PIE). From this you can find
  `system@plt`, GOT entries, etc.
- A heap address → calculate heap base for House of Force targeting.

**Real-world parallel**: This is structurally identical to CVE-2014-0160
(Heartbleed): a client-controlled length field causes the server to
return more data than was intended, leaking adjacent memory. Many
embedded HTTP servers have similar bugs where Content-Length or a
custom header controls the response size without validation against
the actual data length.

---

## Bug 6: Format String Information Leak

**Location**: `handle_diag()` — the `ECHO` command

**Root cause**: When the diagnostic body starts with `ECHO `, the
handler does `snprintf(result, 256, req->body + 5)`. The user's
input is used directly as the format string. Format specifiers like
`%p`, `%x`, `%s` read values from the stack.

**What gets leaked**:

The stack contains:
- **Return addresses** → code/binary addresses (useful for bypassing
  PIE, or confirming function locations)
- **Saved frame pointers** → stack addresses
- **Local variables** from caller frames → may include heap pointers
  (e.g., the `req` pointer, `logbuf` allocations from previous calls)
- **libc internal values** → libc base address

**Exploitation**:

```bash
# Simple test — leak the first 15 stack values
curl -X POST http://localhost:8080/api/diag \
  -d 'ECHO %p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p'
```

```python
# Targeted leak — use %N$p to read specific stack positions
# (positional arguments, works on glibc)
body = b"ECHO %9$p"    # Leak the 9th stack value (often a code addr)
```

To find the useful positions, walk the stack:
- Code addresses look like `0x40XXXX` (no PIE) or `0x55XXXXXXXX` (PIE)
- libc addresses look like `0x7fXXXXXXXXXX`
- Heap addresses look like `0xXXXXXXXX` (below libc, above code)

**Beyond info leak**: Format string bugs are also a *write* primitive
via `%n`, which writes the number of bytes printed so far to a pointer
on the stack. With positional arguments (`%N$n`), this can write to
arbitrary addresses — though `snprintf` (not `sprintf`) limits the
damage since it respects the buffer size. On glibc 2.23, `%n` through
`snprintf` still works for small writes.

**Real-world parallel**: Format string bugs in embedded daemons are
common (CVE-2012-1493 in F5 BIG-IP, numerous router httpd bugs).
Many are found by grepping for `printf(user_input)` patterns.
