Deflate 압축 스트림의
NPNCB 은닉 취약점
The NPNCB hiding
vulnerability in DEFLATE
원본 데이터를 만들지 않는 0길이 비압축 블록을 정상 블록 사이에 삽입하고, 그 블록의 바이트 정렬 비트에 조각난 데이터를 기록해도 일반 압축 해제 결과는 달라지지 않을 수 있습니다. 이 사이트는 그 구조와 방어적 PoC를 시각적으로 설명합니다. Zero-length stored blocks can be placed between ordinary DEFLATE blocks, while fragmented data is carried in their byte-alignment bits. A normal inflater may still produce exactly the same output. This site explains the structure and a defensive proof of concept.
- 2022원 논문 발표paper published
- 0–7 bitsNPNCB당 DBADBA per NPNCB
- 0 bytesNPNCB 출력 데이터NPNCB output
방어적 연구와 검증을 위한 자료For defensive research and validation
이 통합 페이지와 포함된 C# PoC는 파일 포맷 연구, 상호운용성 시험, 탐지 규칙 개발을 위한 것입니다. 여기서 다루는 문제는 메모리 손상이나 암호 해독이 아니라, 정상 디코더가 무시하는 표현 영역을 별도 판독기가 다시 해석할 수 있다는 포맷 수준의 은닉 채널입니다.This integrated page and the included C# PoC are intended for format research, interoperability testing, and defensive detection engineering. The issue is not memory corruption or a cryptographic break; it is a format-level covert channel created when a separate reader reinterprets representation bits that compliant inflaters ignore.
핵심 개요Executive summary
NPNCB는 압축 해제 출력에 바이트를 추가하지 않는 0길이 Stored block입니다. 이 블록의 3비트 헤더 뒤에는 다음 바이트 경계까지 정렬 비트가 생길 수 있으며, 연구에서는 이를 DBA라고 부릅니다. 여러 DBA 조각을 연결하면 낮은 용량의 별도 데이터 채널이 됩니다.An NPNCB is a zero-length stored block that adds no bytes to decompressed output. After its 3-bit header, alignment bits may appear before the next byte boundary; the cited research calls them the DBA. Chaining many DBA fragments creates a low-capacity auxiliary data channel.
NPNCB 삽입NPNCB insertion
출력 데이터가 없는 zero-length Stored block을 정상 데이터 블록 사이에 배치할 수 있습니다.Zero-length stored blocks that emit no payload can be placed between ordinary data blocks.
BTYPE=00 · LEN=0DBA 은닉DBA hiding
바이트 정렬을 위해 소비되지만 디코더가 출력으로 사용하지 않는 비트에 작은 데이터 조각을 기록할 수 있습니다.Small fragments can be written into alignment bits that are consumed structurally but never emitted as output.
0…7 bits / block정상 해제Normal extraction
해제된 파일 바이트가 같다면 그 데이터에 대한 CRC도 유지되므로 결과 비교만으로는 이상을 놓칠 수 있습니다.When extracted file bytes remain unchanged, their CRC also remains valid, so output-only checks can miss the difference.
same output · same CRC구조 기반 탐지Structure-aware detection
비영 DBA, NPNCB 밀도, 반복 오버헤드와 정상 flush 가능성을 함께 평가해야 합니다.Detection should correlate non-zero DBA values, NPNCB density, repeated overhead, and legitimate flushing context.
parse · correlate · normalize왜 중요한가?Why does it matter?
문제의 핵심은 “해제된 콘텐츠”와 “그 콘텐츠를 표현한 압축 스트림”이 서로 다른 검증 대상을 가진다는 점입니다.The central issue is that “decompressed content” and “the compressed representation of that content” are different validation targets.
Deflate는 ZIP뿐 아니라 다양한 형식과 통신 계층에서 사용됩니다. 압축 해제 후 얻은 파일만 검사하는 파이프라인은 보통 원본 바이트, 파일 형식, CRC에는 집중하지만, raw DEFLATE 안에서 디코더가 버린 비트까지는 보존하거나 분석하지 않습니다.DEFLATE is used by ZIP and many other formats and protocol layers. Pipelines that inspect only extracted files usually focus on content, file type, and CRC, while discarding or ignoring the exact raw DEFLATE representation.
따라서 같은 파일을 복원하는 두 압축 스트림이 보안상 동일하다고 가정하면 blind spot이 생깁니다. 하나는 일반적인 인코더가 만든 스트림일 수 있고, 다른 하나는 출력에 영향을 주지 않는 블록과 정렬 비트에 별도 정보를 담을 수 있습니다.Treating every compressed stream that reconstructs the same file as security-equivalent creates a blind spot. One stream may be ordinary; another may carry auxiliary information in blocks and alignment bits that do not affect output.
정상 압축 해제기는 원본 파일을 그대로 복원하는 반면, 별도의 DBA 인식 판독기는 NPNCB에 흩어진 비트 조각을 다시 연결해 추가 메시지를 복원할 수 있습니다.An ordinary inflater can reproduce the original file unchanged, while a DBA-aware reader separately reconnects bit fragments distributed across NPNCBs and recovers an additional message.
두 경로를 함께 봐야 “출력은 정상, 스트림은 비정상”인 경우를 구분할 수 있습니다.Both paths are needed to distinguish “normal output” from “anomalous representation.”
핵심 개념 세 가지Three core concepts
세 가지 유효 DEFLATE 블록 유형, 정렬 비트 영역 DBA, 그리고 payload가 없는 Stored block인 NPNCB를 구분하면 이후 구조를 쉽게 읽을 수 있습니다.Once the three valid DEFLATE block types, the DBA alignment region, and the zero-payload stored block called NPNCB are distinguished, the rest of the structure becomes straightforward.
Deflate의 블록 유형DEFLATE block types
Stored, Fixed Huffman, Dynamic Huffman 세 형식이 유효합니다. 블록은 1비트 BFINAL과 2비트 BTYPE으로 시작합니다.Stored, fixed Huffman, and dynamic Huffman are the three valid forms. Every block begins with one BFINAL bit and two BTYPE bits.
DBA · Disposed Bit Area
Stored block의 LEN 필드를 다음 바이트 경계에서 시작시키기 위해 헤더 뒤에 생기는 0–7비트 정렬 영역을 원 논문이 DBA라고 명명했습니다.The paper names the 0–7 alignment bits after a stored-block header—the bits used to make LEN begin on the next byte boundary—the DBA.
NPNCB
No-Payload Non-Compressed Block. `BTYPE=00`, `LEN=0`, `NLEN=0xFFFF`이므로 literal data가 없고 출력도 생성하지 않는 Stored block입니다.No-Payload Non-Compressed Block. A stored block with `BTYPE=00`, `LEN=0`, and `NLEN=0xFFFF`, so it carries no literal bytes and emits no output.
네 가지 2비트 값, 세 가지 유효 블록Four 2-bit values, three valid block types
00StoredLEN · NLEN · literal bytes0길이일 때 NPNCBNPNCB when length is zero
01Fixed Huffman표준 고정 트리predefined trees실데이터 후보data candidate
10Dynamic Huffman블록별 트리 포함per-block trees실데이터 후보data candidate
11Reserved—사용 불가invalid
비압축 블록과 NPNCB 비교Stored block versus NPNCB
NPNCB 한 블록을 비트 단위로 펼치기Unfolding one NPNCB, bit by bit
블록은 항상 3비트 헤더로 시작하지만 바이트 경계에서 시작한다는 보장은 없습니다. DBA 크기는 헤더가 끝난 위치에 따라 0–7비트로 달라집니다.Every block starts with a 3-bit header, but not necessarily on a byte boundary. DBA capacity therefore varies from 0 to 7 bits, depending on where that header ends.
왜 표준 압축 해제기는 메시지를 못 보는가?Why does a normal inflater not see the message?
RFC 1951은 Stored block에서 다음 바이트 경계까지의 입력 비트를 무시하도록 정의합니다. 이후 `LEN=0`이므로 출력으로 복사할 리터럴 바이트도 없습니다.RFC 1951 directs the decoder to ignore input bits up to the next byte boundary. With `LEN=0`, there are then no literal bytes to copy to output.
왜 `00 00 FF FF` 검색만으로는 부족한가?Why is searching for `00 00 FF FF` insufficient?
정상적인 zlib의 sync flush도 empty stored block과 이 4바이트를 만들 수 있고, 같은 바이트열이 압축 데이터 안에 우연히 나타날 수도 있습니다. 유효한 블록 경계를 따라 파싱해야 합니다.A normal zlib sync flush can emit an empty stored block with these four bytes, and the byte sequence can also occur coincidentally. Detection must validate the actual bit-level block boundary.
헤더 시작 위치에 따른 DBA 용량DBA capacity by header start offset
실제 스트림에서는 각 NPNCB의 시작 오프셋이 서로 다를 수 있습니다. 이 계산기는 구조를 이해하기 위한 동일 오프셋 예시입니다.In a real stream, each NPNCB may start at a different offset. This calculator repeats one offset only to illustrate the structure.
삽입과 복원은 서로 반대 방향의 파이프라인Embedding and recovery are inverse pipelines
메시지는 압축 해제 데이터에 섞이지 않습니다. raw DEFLATE 비트스트림 안의 정렬 영역에만 존재합니다.The message is never mixed into the decompressed file. It exists only inside alignment regions of the raw DEFLATE bitstream.
메시지 삽입기Message inserter
- UTF-8문자열을 바이트로 변환Encode text to bytesstrict UTF-8 · optional 0x00 terminator
- LSB각 바이트를 LSB-first 비트로 분해Split each byte LSB-firstb0 → b1 → … → b7
- DBANPNCB별 0–7비트에 이어 쓰기Continue across 0–7-bit DBA slicesphysical block order
- ZIPraw DEFLATE를 ZIP method 8로 저장Wrap raw DEFLATE as ZIP method 8standard extraction remains valid
메시지 복원기Message restorer
- ZIPCentral Directory와 Local Header 분석Parse Central Directory and local headerlocate method-8 compressed bytes
- BTYPEStored · Fixed · Dynamic 블록 순회Walk stored, fixed, and dynamic blocksrespect physical bit boundaries
- 0/FFFFLEN=0 · NLEN=FFFF인 Stored block 판별Identify LEN=0 · NLEN=FFFF stored blockscollect only their alignment bits
- TEXTDBA 연결 → 바이트 → UTF-8 복원DBA chain → bytes → UTF-8 textstop at 0x00 when present
DBA를 무시하고 원본 파일만 출력Ignores DBA and emits only the original file
PAYLOAD OK원본 파일과 별개로 정렬 비트를 수집Collects alignment bits separately from file output
MESSAGE FOUND실데이터 압축은 적응형, NPNCB 삽입은 동일하게Adaptive data compression, unchanged NPNCB insertion
PoC는 C#/.NET 8의 두 독립 프로젝트로 나뉩니다. 삽입기는 각 16KiB 데이터 구간에서 Stored·Fixed·Dynamic 후보의 실제 비트 비용을 비교하고 가장 작은 형식을 선택합니다. 복원기는 어떤 조합이 선택되어도 블록 헤더를 따라 파싱합니다.The PoC is split into two independent C#/.NET 8 projects. For each 16 KiB data region, the inserter measures the real bit cost of stored, fixed, and dynamic candidates and emits the smallest. The restorer follows the block headers, so it works with any resulting mixture.
DbaMessageInserter
- 입력 파일을 16KiB 구간으로 계획Plans the input in 16 KiB regions
- LZ77 토큰과 실제 Huffman 헤더 비용 측정Measures LZ77 payload and real Huffman-header cost
- Stored / Fixed / Dynamic 중 최소 비트 후보 선택Chooses the smallest stored / fixed / dynamic candidate
- 각 데이터 블록 뒤 NPNCB train 삽입Inserts an NPNCB train after each data block
- UTF-8 메시지를 DBA에 LSB-first 기록Writes UTF-8 message bits LSB-first into DBA
- 표준 ZipArchive로 원본 복원 검증Verifies the original through standard ZipArchive extraction
dotnet run -- "input.bin" "output.zip" "message" 32
DbaMessageRestorer
- ZIP Central Directory와 Local Header 직접 파싱Parses ZIP Central Directory and local header directly
- 첫 번째 method 8 엔트리의 raw DEFLATE 추출Extracts raw DEFLATE from the first method-8 entry
- Stored / Fixed / Dynamic 블록을 비트 단위로 순회Walks stored / fixed / dynamic blocks at bit granularity
- NPNCB의 DBA만 물리적 순서로 수집Collects only NPNCB DBA bits in physical order
- 8비트 LSB-first 바이트와 strict UTF-8 복원Rebuilds LSB-first bytes and strict UTF-8 text
- 텍스트 또는 원시 바이트로 저장 가능Can save decoded text or raw bytes
dotnet run -- "output.zip"
DBA 용량 계산과 비트 기록DBA capacity and bit writing
// BFINAL + BTYPE=00
WriteBlockHeader(writer, isFinal, 0);
// Bits remaining before the next byte boundary become DBA.
int capacityBits = writer.BitsUntilByteBoundary;
for (int i = 0; i < capacityBits; i++)
{
uint bit = ReadNextMessageBitOrZero();
writer.WriteBits(bit, 1);
}
// Empty Stored block: LEN=0, NLEN=0xFFFF
writer.WriteUInt16LittleEndian(0x0000);
writer.WriteUInt16LittleEndian(0xFFFF);
독립 프로젝트 또는 전체 솔루션Standalone projects or the complete solution
소스만 포함된 .NET 8 C# 프로젝트입니다. 다운로드 후 `dotnet build -c Release`로 빌드할 수 있습니다.Source-only .NET 8 C# projects. Build after download with `dotnet build -c Release`.
패턴 검색보다 구조 검증이 중요하다Structural validation beats byte-pattern matching
`00 00 FF FF`는 정상 flush에도 나타납니다. 방어 도구는 raw DEFLATE를 실제 블록 경계대로 파싱하고, DBA 값과 NPNCB 밀도, 압축 오버헤드를 함께 봐야 합니다.`00 00 FF FF` also appears in legitimate flush markers. Defensive tooling should parse raw DEFLATE at real block boundaries, then evaluate DBA values, NPNCB density, and compression overhead together.
탐지 신호의 상대적 의미Relative value of detection signals
단독 판정 금지Do not use in isolationRaw stream까지 검사Inspect the raw stream
압축 해제된 파일만 스캔하면 DBA는 사라집니다. ZIP 엔트리의 원시 compressed bytes를 보존하고 분석 경로에 포함합니다.DBA disappears from extracted content. Preserve and inspect the entry’s raw compressed bytes, not only the decompressed file.
비트 단위 DEFLATE 파서 사용Use a bit-accurate parser
BFINAL/BTYPE, Huffman payload, EOB, LEN/NLEN을 따라 다음 블록 시작점을 계산합니다. 단순 hex 검색은 오탐과 누락을 모두 만듭니다.Follow BFINAL/BTYPE, Huffman payload, EOB, and LEN/NLEN to derive each next block boundary. Hex search alone creates both false positives and misses.
정책 기반 정규화Policy-driven normalization
정확한 compressed bytes 보존이 필요하지 않은 수신 경로라면, 검증된 라이브러리로 압축 해제 후 재압축하여 ignored-bit channel을 제거할 수 있습니다.Where exact compressed bytes need not be preserved, decompress with a trusted library and recompress to remove ignored-bit channels.
여러 신호를 결합Correlate multiple signals
비영(非0) DBA, NPNCB 수, 블록당 오버헤드, 출처 신뢰도, 파일 유형을 함께 점수화합니다. 정상 sync flush를 악성으로 단정하지 않습니다.Score non-zero DBA, NPNCB count, per-block overhead, source reputation, and file type together. Do not label legitimate sync flushes as malicious by default.
정확히 무엇이 위험하고, 무엇은 아닌가?What is—and is not—the security issue?
- 무시되는 비트를 이용한 저용량 covert channela low-capacity covert channel in ignored bits
- “압축 해제 결과만 검사”하는 파이프라인의 blind spota blind spot for pipelines that scan only extracted output
- 포맷 정규화와 raw-stream 검사의 필요성을 보여주는 사례a case for format normalization and raw-stream inspection
- DEFLATE 디코더의 메모리 손상 취약점a memory-corruption bug in DEFLATE decoders
- 암호화 또는 안전한 비밀 통신encryption or a secure secret channel
- `00 00 FF FF`만으로 확정 가능한 악성 지표a malicious indicator provable from `00 00 FF FF` alone
자주 생기는 오해Common misconceptions
DBA 비트는 반드시 0이어야 하나?Must DBA bits be zero?
RFC 1951은 그 비트들을 “무시”한다고 정의하며 특정 값으로 제한하지 않습니다. 많은 encoder가 0을 쓰지만, 비영 값 자체만으로 비준수 스트림이라고 단정할 수는 없습니다. 따라서 탐지 규칙은 구조와 맥락을 함께 보아야 합니다.RFC 1951 says those bits are ignored; it does not constrain their value. Many encoders write zero, but a non-zero value alone does not prove non-compliance. Detection therefore needs structural and contextual evidence.
NPNCB 하나에 메시지 몇 바이트를 넣을 수 있나?How many message bytes fit in one NPNCB?
0–7비트뿐이므로 단일 블록에는 완전한 1바이트도 보장되지 않습니다. 여러 NPNCB의 DBA를 연결해야 하며, 그만큼 4바이트 LEN/NLEN과 헤더 오버헤드가 반복되어 은닉 효율은 낮습니다.Only 0–7 bits, so one block cannot guarantee even a complete byte. Multiple DBA regions must be chained, repeating header and four-byte LEN/NLEN overhead. The channel is therefore inefficient.
ZIP CRC가 메시지를 발견하지 못하는 이유는?Why does the ZIP CRC not reveal the message?
PoC가 바꾸는 것은 압축 해제기가 무시하는 raw DEFLATE 비트입니다. 복원된 파일 바이트는 그대로이므로 그 파일 데이터에 대해 계산한 CRC도 그대로 통과합니다. raw compressed stream 자체의 무결성을 별도로 검증해야 합니다.The PoC changes raw DEFLATE bits that the inflater ignores. The recovered file bytes remain unchanged, so a CRC over that file data still verifies. Integrity of the raw compressed stream must be checked separately.
재압축하면 메시지는 없어지나?Does recompression remove the message?
일반적으로 그렇습니다. 압축 해제 후 새로 DEFLATE하면 원래의 ignored alignment bits와 블록 배치가 보존되지 않습니다. 다만 재압축은 압축 바이트 동일성, 디지털 서명, 재현 가능한 빌드 요구와 충돌할 수 있으므로 정책적으로 적용해야 합니다.Generally yes. A fresh DEFLATE encoding does not preserve the original ignored alignment bits or block layout. However, recompression can conflict with byte-for-byte archive preservation, signatures, or reproducible-build requirements, so it must be policy-driven.
표준과 연구 근거Standards and research basis
용어와 주장 사이를 구분하기 위해 1차 표준과 원 논문을 함께 제시합니다.Primary specifications and the original paper are listed together to separate standardized behavior from research terminology.
-
[1]
RFC 1951 — DEFLATE Compressed Data Format Specification version 1.3
Stored block의 3비트 헤더, 바이트 정렬, LEN/NLEN 구조와 “다음 바이트 경계까지의 비트는 무시” 규칙.Defines the 3-bit block header, stored-block byte alignment, LEN/NLEN, and the rule that bits up to the next byte boundary are ignored.
IETF Datatracker ↗ -
[2]
PKWARE .ZIP File Format Specification — APPNOTE.TXT
ZIP local/central headers, CRC-32 필드, compression method 8(DEFLATE)의 컨테이너 구조.Defines ZIP local/central headers, CRC-32 fields, and compression method 8 (DEFLATE).
PKWARE specification ↗ -
[3]
zlib 1.3.1 Manual
Z_SYNC_FLUSH가 3비트와 filler, 이어지는 `00 00 FF FF`로 구성된 empty stored block을 만들 수 있음을 설명합니다.Documents that Z_SYNC_FLUSH can append an empty stored block consisting of three bits, filler to the next byte, and `00 00 FF FF`.
zlib manual ↗ -
[4]
Jung-hoon Kim, “Malicious Code Injection Vulnerability Analysis in the Deflate Algorithm,” JKIISC 32(5), 2022, 869–879.
NPNCB와 DBA라는 용어를 사용하고, 정상 블록 사이에 DBA가 변형된 NPNCB를 삽입하는 은닉 채널을 분석한 원 논문. DOI: 10.13089/JKIISC.2022.32.5.869The original paper using the terms NPNCB and DBA and analyzing insertion of NPNCBs with modified DBA between ordinary blocks. DOI: 10.13089/JKIISC.2022.32.5.869
KCI record ↗ -
[5]
safe.zipdeflate.com — NPNCB Vulnerability Brief
이 통합판의 개요·중요성·핵심 개념 정보 구조를 참고한 기존 소개 페이지입니다. 본 통합판의 문장은 별도로 재작성했습니다.The earlier introduction whose information structure informed the overview, significance, and core-concept chapters. The wording in this integrated edition was independently rewritten.
Open original brief ↗
“무시되는 비트”도 공격 표면의 일부다.Ignored bits are still part of the attack surface.
안전한 포맷 처리는 해석된 콘텐츠뿐 아니라 원시 표현, 정규화 규칙, 서로 다른 파서가 보는 데이터의 차이까지 다뤄야 합니다.Secure format handling must account not only for interpreted content, but also raw representation, normalization rules, and differences between what separate parsers observe.