Skip to content

SwiftSecur/OneNoteParser

Repository files navigation

OneNote Parser

A Python tool for parsing OneNote package (.onepkg) files. Extracts notebook structure, sections, pages, text content, and embedded images.

Features

  • Parse .onepkg cabinet archives
  • Extract text content and embedded images
  • Search across multiple notebooks
  • Generate HTML reports
  • Threat intelligence analysis (detect credentials, API keys, tokens, etc.) with modular, user-extensible pattern files
  • OCR image text extraction via Tesseract automatically runs pattern matching on extracted text
  • AI-powered image descriptions via Ollama

How .one File Parsing Works

This tool does not implement the full specification; instead it uses structural properties of the binary to reconstruct the notebook hierarchy to extract forensically interesting artifacts.

PropertyIDs used by the parser:

PropertyID Bytes (LE) Kind Type MS-ONE property Role in parser
0x08001CB5 B5 1C 00 08 Non-string Bool IsTitleText (true variant) Proximity marker string within 8–36 bytes after this ID classified as title
0x14001D09 09 1D 00 14 Non-string FourBytesOfData CreationTimeStamp 4-byte Unix timestamp; earliest value per section exported as created_at
0x14001D7A 7A 1D 00 14 Non-string FourBytesOfData LastModifiedTime 4-byte Unix timestamp; latest value per section exported as modified_at
0x18001D77 77 1D 00 18 Non-string EightBytesOfData LastModifiedTimeStamp 8-byte Windows FILETIME; latest value per section exported as modified_at
0x1C001C0A 0A 1C 00 1C String ObjectSpaceStream FontName In rgPrids block what precedes the font string in packed data is an LCID, not this PropertyID
0x1C001CF3 F3 1C 00 1C String ObjectSpaceStream CachedTitleString Explicit title tag any string under this ID is a page title
0x1C001D3C 3C 1D 00 1C String ObjectSpaceStream CachedTitleStringFromPage Explicit title tag any string under this ID is a page title
0x1C001D75 75 1D 00 1C String ObjectSpaceStream Author Filtered author names discarded
0x1C001DD7 D7 1D 00 1C String ObjectSpaceStream ImageFilename Optional (rarely set); not present in test files
0x1C001E58 58 1E 00 1C String ObjectSpaceStream ImageAltText Image alt text; not present in test files
0x1C00349B 9B 34 00 1C String ObjectSpaceStream SectionDisplayName Spec-defined section name; not present in test files

AuthorOriginal (0x20001D78) and AuthorMostRecent (0x20001D79) are ObjectID-typed properties; they reference author entries rather than carrying string data directly, and require no separate filtering.

IsTitleText (0x08001CB4) is a Bool property: per MS-ONESTORE §2.6.1, the boolean value is encoded in bit 0 of the PropertyID itself rather than in the data vector. The true variant (0x08001CB5) is scanned as a proximity marker; a string appearing 8–36 bytes after it in the binary is classified as a page title.

Object-level JCIDs (FileNode headers present in binary but not adjacent to string data):

JCID Value Meaning Used in parser
jcidSectionNode 0x00060007 Section object
jcidPageSeriesNode 0x00060008 Page-series container
jcidPageNode 0x0006000B Individual page object Image-to-page assignment
jcidOutlineNode 0x0006000C Outline / content block
jcidTitleNode 0x0006002C Page-title object (distinct-tag classification used instead)

Image extraction - signature carving

ImageFilename (0x1C001DD7) is only referenced by jcidImageNode and is not set by default; metadata-driven extraction is therefore unreliable in practice. The parser falls back to signature (magic byte) carving, scanning the raw binary data for known image file headers.

Format Signature (hex) End detection
PNG 89 50 4E 47 0D 0A 1A 0A IEND chunk + CRC (AE 42 60 82)
JPEG / JFIF / Exif FF D8 FF FF D9 EOI marker¹
GIF87a / GIF89a GIF8 (47 49 46 38) 00 3B block-terminator + trailer
BMP 42 4D File size at header offset 2–5
TIFF little-endian 49 49 2A 00 IFD chain traversal²
TIFF big-endian 4D 4D 00 2A IFD chain traversal²
WEBP 52 49 46 46 … 57 45 42 50 RIFF chunk-size field (offset 4–7)³

¹ Exif data can embed a thumbnail JPEG before the main image stream; both end with FF D9, so the carved extent may stop at the thumbnail boundary.

² TIFF has no footer. The parser traverses every IFD and its referenced value blocks, using the maximum byte offset reached as the practical end of the image.

³ WEBP is only accepted when bytes 8–11 of the RIFF container equal WEBP; the total file size is 8 + RIFF_fileSize_field.

Image-to-page assignment

Each image's byte offset in the file is compared against the sorted list of page title offsets using the same logic as content assignment. Images are heuristically assigned to the last page whose title offset precedes them in the binary. jcidPageNode markers (0x0006000B) are present in the binary and confirm page boundaries, but each page generates multiple jcidPageNode references (manifest, file-node list, etc.), making direct counting unreliable; title-offset comparison gives cleaner results without needing to understand the full FileNode structure.

Content assignment

Once titles and content strings are classified, each content string is assigned to the page whose title offset is the largest value ≤ the content string's offset - i.e. the last page title that appeared before it in the binary.

Threat intelligence matching

Once content strings are extracted and assigned to pages, threat analysis walks the parsed structure in order: notebook → sections → pages → content strings (including the page title itself). Each string is run through every compiled regex pattern via re.finditer, so a single string can produce multiple findings from different pattern categories.

A per-run seen_values set deduplicates matches: if the same credential value is extracted from more than one string or page, only the first occurrence is reported. False positives (localhost, 0.0.0.0, example.com, etc.) are filtered immediately after matching. Values longer than 100 characters (e.g. private key blobs) are truncated for display while the full match is still used for deduplication.

The page title is also scanned - credentials may occasionally appear in note titles rather than body content.

OCR text (when --ocr is active) is scanned after content strings using the same pattern engine. Findings from OCR carry "source": "ocr_image" in the JSON output so they can be distinguished from text-layer findings. Before scanning, common Tesseract character-substitution errors in credential keywords are normalised (e.g. passwardpassword, usernaneusername, l0ginlogin) so that OCR typos do not silently suppress findings. The separator pattern also accepts - in addition to =/: to handle config-dump screenshot formats such as password-secret;.

.onetoc2 - section ordering

Each OneNote notebook directory contains a .onetoc2 file that lists the .one section files in display order (as UTF-16 strings). The parser reads this file to present sections in the same order they appear in the OneNote sidebar.


Requirements

  • Python 3.10+
  • cabextract system utility (for extracting .onepkg archives)
  • Python dependencies - install via pip:
pip install -r requirements.txt
Package Purpose Required?
pyyaml Load threat intelligence pattern files Yes (for --threat-intel / --ocr)
pytesseract Python bindings for Tesseract OCR Yes (for --ocr)
Pillow Image decoding for Tesseract input Yes (for --ocr)
rich Coloured terminal output, progress bar, summary table Optional (degrades gracefully)
cabarchive Pure-Python .onepkg extraction fallback Optional

Installation

# Install system dependencies (Debian/Ubuntu)
sudo apt install cabextract tesseract-ocr

# macOS
brew install cabextract tesseract

# Install Python dependencies
pip install -r requirements.txt

Usage

# Parse a single file and pretty-print JSON
python3 onenote_parser.py -f notebook.onepkg --pretty

# Parse a raw .one section file
python3 onenote_parser.py -f section.one --pretty

# Parse all notebooks in a directory (recursive)
python3 onenote_parser.py -d ./notebooks/ --pretty

# Generate an HTML report from a single file
python3 onenote_parser.py -f notebook.onepkg --html report.html

# Generate an HTML report from a directory
python3 onenote_parser.py -d ./notebooks/ --html report.html

# Parse multiple files into one report
python3 onenote_parser.py -f notebook1.onepkg -f notebook2.onepkg --html report.html

# Re-generate an HTML report from a previously saved JSON result (no re-parsing)
python3 onenote_parser.py --from-json results/MyNotebook_20260101_120000.json --html report.html

# Search for a keyword across a notebook
python3 onenote_parser.py -f notebook.onepkg -s "password"

# Search across a whole directory
python3 onenote_parser.py -d ./notebooks/ -s "api_key" --regex

# Extract images to an auto-named directory next to the input
python3 onenote_parser.py -f notebook.onepkg --extract-images

# Extract images to an explicit directory
python3 onenote_parser.py -f notebook.onepkg --extract-images ./images/

# Threat intelligence analysis - HTML report with findings
python3 onenote_parser.py -f notebook.onepkg --threat-intel --html report.html

# Threat intel across a directory, excluding low-severity findings
python3 onenote_parser.py -d ./notebooks/ --threat-intel --no-low-severity --html report.html

# Threat intel with a custom patterns directory
python3 onenote_parser.py -f notebook.onepkg --threat-intel --patterns-dir ./my-patterns/

# OCR - extract text from images and run pattern matching on results
python3 onenote_parser.py -f notebook.onepkg --ocr --html report.html

# OCR combined with full threat-intel (content text + image text both scanned)
python3 onenote_parser.py -f notebook.onepkg --ocr --threat-intel --html report.html

# Extract images to disk and generate a linked HTML report (no base64 bloat)
python3 onenote_parser.py -f notebook.onepkg --extract-images ./images/ --html report.html

# AI image descriptions via Ollama
python3 onenote_parser.py -f notebook.onepkg --analyse-images --html report.html

# AI summaries of pages, sections, and notebooks
python3 onenote_parser.py -f notebook.onepkg --summarise --html report.html

# Full analysis - threat intel, OCR, AI descriptions, summaries, HTML report
python3 onenote_parser.py -f notebook.onepkg \
    --threat-intel --ocr --analyse-images --summarise \
    --extract-images ./images/ --html report.html

# Full analysis against a remote Ollama instance
python3 onenote_parser.py -f notebook.onepkg \
    --threat-intel --ocr --analyse-images --summarise \
    --ollama-url http://192.168.8.10:11434 --html report.html

Options

Option Description
-f, --file FILE Path to a .onepkg or .one file to parse (can be repeated)
-d, --directory DIR Directory of .one files to parse recursively (can be repeated)
-o, --output Output file path for JSON/text
--format Output format: json or text
--html FILE Generate HTML report
--from-json FILE Re-generate an HTML report from a previously saved JSON result file (requires --html; skips re-parsing)
-s, --search Search for text in notebooks
--extract-images [DIR] Extract images to DIR; omit DIR to auto-name the directory next to the input. Runs alongside all other options (HTML, threat-intel, JSON output)
--threat-intel Analyse for credentials, keys, tokens
--ocr Extract text from images using Tesseract OCR; automatically runs pattern matching on the extracted text. Findings are tagged source: ocr_image in JSON output
--no-low-severity Exclude low-severity findings (IPs, emails) from threat analysis or OCR scan
--patterns-dir DIR Directory containing .yaml threat pattern files (default: patterns/ next to the script)
--analyse-images Use Ollama to describe images
--summarise Generate AI summaries
--ollama-url URL Ollama API URL (default: http://localhost:11434)
--vision-model MODEL Ollama vision model to use (default: auto-detect)
--text-model MODEL Ollama text model for summaries (default: auto-detect)
-v, --verbose Print progress messages

OCR Image Text Extraction

--ocr uses Tesseract to extract text from embedded images and then runs the full threat intelligence pattern engine against what it finds.

# OCR only - pattern-match against image text, no full content scan
python3 onenote_parser.py -f notebook.onepkg --ocr

# OCR combined with full threat-intel - content text AND image text both scanned
python3 onenote_parser.py -f notebook.onepkg --ocr --threat-intel

Requirements - both the Python bindings and the Tesseract binary must be installed:

# System binary
sudo apt install tesseract-ocr        # Debian/Ubuntu
brew install tesseract                # macOS

# Python packages
pip install pytesseract Pillow

If either is missing, a clear warning is printed and the run continues without OCR.

How it works:

  1. After parsing, OCR is run on every embedded image. The extracted text is stored in the ocr_text field of each image object and included in the JSON output.
  2. Before pattern matching, common Tesseract character substitutions in credential keywords are corrected automatically; for example passwardpassword, usernaneusername, l0ginlogin. This prevents OCR noise from suppressing findings.
  3. The same pattern engine used for --threat-intel is applied to the normalised OCR text. Findings from image text carry "source": "ocr_image" in the JSON so they can be distinguished from text-layer findings.
  4. When used alone (--ocr without --threat-intel), only image-text findings are returned. Combined with --threat-intel, all content text and all image text are scanned together.

JSON output example:

{
  "type": "credential",
  "severity": "high",
  "value": "YWRtaW4=",
  "section": "Azure Creds",
  "page": "Azure Creds",
  "source": "ocr_image"
}

Summary table - the Images column shows how many images yielded OCR text (e.g. 5 (3 OCR)). The Sev H/M/L and Match types columns cover all findings including those from OCR.


Threat Intelligence Patterns

Pattern definitions live in the patterns/ directory as YAML files. Each file groups related patterns by category:

patterns/
  cloud_credentials.yaml   # AWS, Azure, GCP keys
  credentials.yaml         # passwords, basic auth, connection strings
  hashes.yaml              # MD5, SHA1, SHA256 hashes
  network.yaml             # IP addresses, URLs, email addresses
  private_keys.yaml        # RSA, PGP, SSH private keys
  tokens.yaml              # JWT, Bearer, GitHub, Slack, generic API keys
  custom/                  # drop your own .yaml files here
    example.yaml           # annotated template

Adding custom patterns

Copy patterns/custom/example.yaml and add your own entries. Any .yaml file dropped into patterns/custom/ is loaded automatically; no script edits required.

Each pattern entry has three fields:

my_pattern_name:
  pattern: 'MYCOMPANY-[A-Z0-9]{24}'
  severity: high          # high | medium | low
  type: api_key           # free-form label shown in reports

Pattern strings are standard Python regex (passed to re.compile with IGNORECASE | MULTILINE). Use --patterns-dir to point at a completely different directory if needed.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors