13 September 2026
Converting Hangul Word Processor (HWP) to Markdown: OLE CFB & Deflate Forensics
In South Korea, word processing in the public sector, courts, school systems, and civil institutions is overwhelmingly centered around Hancom Hangul (formerly Hangul Word Processor). While Microsoft Word dominates most of the globe, the statutory standard in South Korea has long been the .hwp format.
For security researchers, legal professionals, and digital archivists outside South Korea, opening .hwp files presents a formidable challenge. Legacy HWP files cannot be viewed in standard macOS or Linux environments without proprietary software or slow third-party cloud services.
This deep dive breaks down the internal architecture of HWP 5.x, explaining how its Microsoft OLE 2.0 Compound File Binary (CFB) structure, raw Deflate stream compression, and record-based paragraph tags work. We also explain how convrtr's HWP to Markdown extractor transforms complex binary HWP documents into clean, searchable GitHub Flavored Markdown with 100% client-side privacy.
The OLE 2.0 Compound File Binary (CFB) Architecture
Modern HWP 5.x files do not use custom flat binary headers. Instead, they are packaged as Microsoft OLE 2.0 Compound Documents (the same structured storage format used by legacy .doc and .xls files).
An OLE CFB file acts as a mini virtual filesystem partitioned into 512-byte sectors:
[512-Byte Header] -> [FAT Sector Pointers] -> [Directory Stream] -> [Data Streams]
Locating the Directory Stream
The first 512 bytes contain the compound document header:
- Magic Signature:
0xD0 0xCF 0x11 0xE0 0xA1 0xB1 0x1A 0xE1(identifies OLE CFB). - Sector Size: Offset
0x1Estores the sector shift (usually9, meaning $2^9 = 512$ bytes per sector). - Master Sector Allocation Table (MSAT): Offset
0x4Cholds pointers to the sectors containing the File Allocation Table (FAT). - Directory Start Sector: Offset
0x30points to the first sector of the directory tree.
Each directory entry is exactly 128 bytes, storing a UTF-16LE stream name, stream type (storage vs. stream), and starting sector index. In an authentic HWP 5.x file, the directory includes:
FileHeader: Global document properties and compression flags.DocInfo: Document styles, fonts, and numbering schemes.BodyText: A storage container holding stream sections:Section0,Section1,Section2, etc.
The FileHeader Stream
The FileHeader stream contains metadata vital for decoding the rest of the document:
| Offset | Size (Bytes) | Field Name | Description |
| :--- | :--- | :--- | :--- |
| 0x00 | 32 bytes | Signature | ASCII string "HWP Document File" |
| 0x20 | 4 bytes | Version | Major, minor, build, and revision numbers |
| 0x24 | 4 bytes | Document Flags | Bit 0: Compressed (1) or Uncompressed (0) |
If bit 0 of the document flags is set, every subsequent section stream in the BodyText storage is compressed using raw Deflate (RFC 1951) without zlib or gzip wrapper headers.
Decompressing BodyText Sections
The actual textual contents of an HWP document are split across Section* streams inside the BodyText storage directory.
When reading Section0:
- The FAT sector chain is traversed to concatenate all sectors into a contiguous byte buffer.
- If
compressed === true, the raw byte buffer is passed directly to an in-memory Deflate inflator. - The decompressed output reveals a sequential stream of HWP 5.x Records.
HWP Record Architecture and HWPTAG_PARA_TEXT
HWP 5.x organizes decompressed section data into a tag-length-value (TLV) record stream. Each record begins with a 32-bit integer header:
Bits 0–9: Tag ID (10 bits, values 0–1023)
Bits 10–19: Hierarchical Level (10 bits)
Bits 20–31: Record Size (12 bits)
If the 12-bit record size equals 0xFFF (4095), the record is larger than 4KB, and the exact 32-bit byte length is stored immediately following the 4-byte header.
Decoding Paragraph Text (Tag 68)
The most important record for text extraction is Tag 68 (HWPTAG_PARA_TEXT). This record stores the text of a paragraph formatted as UTF-16LE characters.
However, Hancom embeds proprietary inline control codes directly into the character stream:
- Characters below
32(0x0020) represent inline object markers, footnotes, table boundaries, or section breaks. - For example, character
0x000Arepresents a line break, while0x0018denotes an inline shape or table cell.
convrtr's HWP parser filters out non-printable control characters, normalizes line breaks, and extracts paragraphs into clean text strings.
Converting HWP to GitHub Flavored Markdown
Once raw paragraph records are extracted, convrtr synthesizes them into structured Markdown:
- YAML Frontmatter: Generates YAML headers recording the document title, HWP version (e.g.
5.0), section counts, and total paragraphs. - Heading Detection: Analyzes Korean legal and bureaucratic numbering conventions (such as
"제 1 장"or"1.") to assign appropriate##and###Markdown heading levels. - Zero Server Uploads: All OLE directory tree traversal, FAT sector chaining, Deflate decompression, and UTF-16LE decoding take place 100% locally in your browser memory via TypeScript.
Your confidential Korean administrative, legal, and educational files never touch an external server or API endpoint.
Related reading
Converting Emacs Org Mode to Markdown: Outlines, Tables, and Checklists
Explore the syntax and semantics of Emacs Org Mode (.org). Learn how asterisk headings, TODO states, priority tags, Org Calc tables, and code blocks translate to GitHub Flavored Markdown.
Converting Evernote ENEX Notebooks to Markdown: Free Your Notes from Proprietary XML
Learn how Evernote XML Export (.enex) files structure notes, timestamps, tags, and ENML layout markup, and how to convert them into clean GitHub Flavored Markdown with YAML frontmatter.
Converting FictionBook 2.0 (FB2) E-Books to Markdown: XML Structure and Semantics
Explore the FictionBook 2.0 (.fb2) architecture. Learn how semantic XML e-book structures, epigraphs, poems, footnotes, and metadata are converted into clean GitHub Flavored Markdown.
Converting PalmDoc (PDB) to Markdown: Vintage Handheld E-Book Architecture
Explore the Palm OS PalmDoc (.pdb / .prc) architecture. Discover 78-byte database headers, record indices, 4KB LZ77 compressed blocks, and browser-based Markdown extraction.
Converting AbiWord (.abw, .zabw) to Markdown: Open-Source Word Processing Recovery
Examine AbiWord's native AWML XML document architecture. Learn how Dublin Core metadata, 2D table grids, styled character spans, GZIP compression, and base64 illustrations convert into clean Markdown.
Converting ClarisWorks and AppleWorks CWK to Markdown: Recovering Vintage Mac Documents
Learn how to extract text, paragraphs, and headings from vintage ClarisWorks and AppleWorks (.cwk) MacBinary files into clean GitHub Flavored Markdown with 100% private in-browser decoding.
Converting OpenOffice SXW to Markdown: Unpacking Pre-ODF XML Document Archives
Learn how OpenOffice.org 1.x and StarOffice Writer (.sxw) XML packages work. Discover how to extract text, tables, and lists into clean Markdown directly in your browser.
Converting StarOffice & StarWriter (SDW) to Markdown: OLE Compound Binary Forensics
Recover text, headings, bullet lists, and metadata from vintage StarOffice 3.x–5.x (.sdw) word processing documents. Learn how OLE CFB streams unpack to GitHub Flavored Markdown.
Converting Compressed AbiWord (ZABW) to Markdown: Gzip XML Document Extraction
Learn how AbiWord's compressed .zabw document format packages rich word processing XML inside Gzip containers, and how to convert it to clean GitHub Flavored Markdown.
Converting Apple RTFD Bundles to Markdown: Rich Text & Attachment Extraction
Learn how Apple macOS RTFD compound packages bundle Rich Text Format documents with graphic attachments, and how to convert them into GitHub Flavored Markdown 100% offline.