# lz1 LZ77 Decompression Stack Buffer Overflow — Public Exploit PoC with Full ROP Chain to Reverse Shell (STAR Labs Summer Pwnables 2025)

> STAR Labs published a complete solution writeup for its 'lz1' Summer Pwnables 2025 challenge, documenting a stack buffer overflow in the andyherbert/lz1 educational LZ77 decompressor and providing full proof-of-concept exploit code: a ROP chain that calls _dl_make_stacks_executable to make the stack executable, pivots via a jmp rsp gadget, and lands shellcode that opens a reverse TCP shell. No CVE has been assigned and the maintainer has declined to fix the underlying bug.

- **Published:** 2026-10-31T23:50:30Z
- **Last reviewed:** 2026-10-31T23:50:30Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-4938
- **ID:** TL-2026-4938
- **Severity:** MEDIUM
- **Category:** VULNERABILITY
- **Status:** TRACKING
- **Detections:** 9 · **IOCs:** 16 (full data via the Threadlinqs MCP server — Purple tier)

## Description

In September 2025, STAR Labs (a Singapore-based offensive security research firm) published a detailed technical writeup, authored by Zafir Rasyidi Taufik, solving its own 'lz1' challenge from the 'Summer Pwnables 2025' competition series (announced publicly on 2025-08-12 via the post 'When the Heat Rises, So Do the C-Shells', restricted to currently-enrolled Singapore students, with a cash/prize incentive for first exploiters and best writeups).

The challenge target is andyherbert/lz1, a small C implementation of the LZ77 lossless compression algorithm (45 GitHub stars, 12 forks) explicitly described by its author as educational code that 'lacks boundary and error checks for the sake of brevity.' The original implementation used fixed 16-bit pointers (12-bit offset / 4-bit length) with a default 4096-byte window; the library was later extended to support configurable pointer bit-widths, and the challenge instance uses a 7-bit position / 9-bit length split. The root-cause defect was in fact publicly flagged nine years earlier: on 2016-08-06, GitHub user rwhitworth opened issue andyherbert/lz1#1, reporting that AFL-fuzz had found ten distinct crashing input files in the decompression routine (attaching a crashes.zip of test cases but no root-cause analysis). Maintainer andyherbert closed the issue the same day as won't-fix, replying that 'the code is meant to be instructional, and lacks boundary and error checks for the sake of brevity' — confirming the omissions were intentional rather than an oversight.

The decompressor reads a 4-byte uncompressed_size value directly from attacker-controlled compressed input and uses it to bound the outer decompression loop counter (coding_pos is bounded at this check site). However, within that loop, LZ77 'pointer' tokens (16-bit values encoding a back-reference position and length) are copied into the stack-allocated uncompressed_text buffer via a raw per-byte copy loop that is bounded only by the token's own length field, not by the destination buffer's actual size (unbounded at the copy site) — and because copied bytes can themselves become valid back-reference sources mid-copy, the read side can overlap already-written output. With a 9-bit length field, a pointer's length can independently reach up to 511-512 bytes regardless of the small uncompressed_size used to pass the outer-loop check, so a crafted ~0xc0-byte compressed stream can write far past the end of the stack buffer (allocated via alloca, alongside compressed_text, uncompressed_size/compressed_size, saved FILE pointers, saved RBP, and the return address, in that stack order), corrupting saved registers and the return address.

STAR Labs' PoC targets a statically linked, non-PIE binary built against glibc 2.39 with no stack protector. Because the fixed, predictable addresses of a no-ASLR/no-PIE static binary made a hardcoded gadget chain feasible, the PoC builds an approximately 0x80-byte ROP chain using fixed gadgets including pop_rdi_rbp (0x40dfa1), pop_rax (0x4027c5), pop_rcx (0x40266b), pop_rbx (0x46da97), pop_rsi_rbp (0x40d402), pop_rdx (0x453095), a plain ret (0x4018da), and a mov_dword_rax_7_ecx / xchg_edx_eax_xor_eax_eax / add_edx_eax_pop_rbx sequence used to synthesize a custom RDX value. Because the target binary runs with a non-executable stack guarded by glibc's read-only __stack_prot flag (hardened in glibc 2.39), the chain first constructs this crafted RDX permission value and calls the libc-internal function _dl_make_stacks_executable (0x4647d0, invoked via a secondary gadget at 0x4647ef) to mark the stack executable at runtime, defeating the NX protection without ever writing to __stack_prot directly. The chain then redirects execution through a jmp rsp gadget (0x4441d6) directly onto attacker-supplied shellcode (at most 0x3c bytes) staged in the overflow data. That shellcode, built with pwntools' shellcraft, issues raw syscalls — SYS_socket (AF_INET/SOCK_STREAM), SYS_connect to an attacker-controlled IP/port (the published exploit.py ships with the IP placeholder value 'ffffffff' intended for manual substitution), and SYS_execve with dup2 file-descriptor redirection onto the socket — to open an outbound TCP connection and spawn an interactive Unix shell, i.e., a classic shellcode-based reverse shell that avoids any dependency on libc shell-spawning helpers.

The writeup documents that an initial exploitation attempt targeting heap-adjacent memory was a dead end, and the working approach pivoted to targeting the stack buffers allocated via alloca inside the decompression function. The published PoC artifact set includes exploit.py (builds the LZ77-encoded ROP payload and interacts with pwntools), listen.py (a reverse-shell listener bound to TCP port 8080), the intermediate compressed-ROP-chain file rop_payload.lz77, and the final delivery artifact payload-exp submitted to the remote challenge server.

The original challenge distribution was lz1.zip (SHA256 5b0c41b916530b16a2a7d87eff4fb426357fefba0e8c0cf41c06bd6725c2b8fc), password-protected with the password 'baby_blastpass', downloadable from the STAR Labs 'star-sg/challenges' GitHub repository, with a live remote submission target at 159.223.33.156:9101 where participants submitted working payloads to retrieve a flag, and a Google Form (https://forms.gle/r9UF5LMqE7vsHpjo7) used to submit exploit source code and writeups for prize consideration.

No CVE identifier has been assigned to this issue. There is no evidence of in-the-wild exploitation; this is a CTF/educational-context disclosure with full public PoC source and a from-scratch, reproducible attack chain. However, because the vulnerable library is small, genuinely named for LZ77/LZ1 compression, and publicly available on GitHub without disclaimers prominent outside the issue tracker, any project that vendors or imports andyherbert/lz1 to process untrusted compressed input inherits an unauthenticated, remotely triggerable memory-corruption primitive with a public, working, fully-chained exploit (overflow → NX bypass → arbitrary code execution → reverse shell).

## MITRE ATT&CK

- T1588.006 Obtain Capabilities: Vulnerabilities
- T1587.004 Develop Capabilities: Exploits
- T1588.002 Obtain Capabilities: Tool
- T1190 Exploit Public-Facing Application
- T1203 Exploitation for Client Execution
- T1059.004 Command and Scripting Interpreter: Unix Shell
- T1106 Native API
- T1620 Reflective Code Loading
- T1211 Exploitation for Stealth
- T1095 Non-Application Layer Protocol
- T1571 Non-Standard Port

## Sources

- [Summer Pwnables: lz1 Solution (Zafir Rasyidi Taufik, STAR Labs)](https://starlabs.sg/blog/2025/09-summer-pwnables-lz1-solution/)
- [Summer Pwnables: When the Heat Rises, So Do the C-Shells (challenge announcement incl. lz1)](https://starlabs.sg/blog/2025/08-summer-pwnables-when-the-heat-rises-so-do-the-c-shells/)
- [andyherbert/lz1 GitHub Issue #1 — Decompression crashes (AFL-fuzz findings, closed won't-fix)](https://github.com/andyherbert/lz1/issues/1)
- [andyherbert/lz1 — LZ77 compressor/decompressor source repository](https://github.com/andyherbert/lz1)
- [LZ77 compression algorithm background reference (cited in the STAR Labs writeup)](https://www.youtube.com/watch?v=goOa3DGezUA)
- [STAR Labs Summer Pwnables 2025 challenge archive (lz1.zip, star-sg/challenges repository)](https://github.com/star-sg/challenges/raw/refs/heads/main/Aug%202025/lz1.zip)
- [Summer Pwnables 2025 exploit/writeup submission form (Google Forms)](https://forms.gle/r9UF5LMqE7vsHpjo7)

## Full data

Detection queries (Splunk SPL / Microsoft KQL / Sigma) and IOC values require the Threadlinqs MCP server (Purple tier): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-4938
