Skip to content

Toolkit Architecture — Analysis to Code

How the spec analysis in ../research/ maps to the Python office-password-toolkit at /root/projects/office_collection/office-password-toolkit/.

Related documents:

Pipeline Overview

flowchart TD
  FILE["Office File"] --> DETECT["detect.py\nRead first 512 bytes\nClassify by magic bytes"]
  DETECT --> KIND{"ContainerKind?"}
  KIND -->|"D0CF11E0 = OLE/CFB"| OLE["OleHandler\n.doc .xls .ppt\nencrypted .docx/.xlsx/.pptx"]
  KIND -->|"504B0304 = ZIP"| OOXML["OoxmlHandler\n.docx .xlsx .pptx"]
  KIND -->|"guidFileType = OneNote"| ON["OneNoteHandler\n.one"]
  KIND -->|"!BDN = PST"| PST["OutlookHandler\n.pst .ost"]
  KIND -->|"Jet/ACE header"| ACC["AccessHandler\n.mdb .accdb"]
  KIND -->|"PK + mimetype = ODF"| ODF["OdfHandler\n.odt .ods .odp"]

  OLE --> GATES["handler.gates(path)\nEnumerate all permission gates"]
  OOXML --> GATES
  ON --> GATES
  PST --> GATES
  ACC --> GATES
  ODF --> GATES

  GATES --> EXTRACT["handler.extract(path)\nEmit hash lines for crackable gates"]
  GATES --> FAST["Fast recovery paths\ndefault → read → decode → bypass\nstrip → collide"]

  EXTRACT --> HC["hashcat-compatible line\n$office$*2013*... → mode 9600"]
  EXTRACT --> OPT["self-describing line\n$office$*sha512*aes256*cbc*..."]

Container → Handler → Crypto Mapping

flowchart LR
  subgraph "Format Handlers (../research/formats/)"
    OLE["OleHandler\nole.py (2036L)"]
    OOXML["OoxmlHandler\nooxml.py (931L)"]
    ODF_H["OdfHandler\nodf.py (499L)"]
    ON_H["OneNoteHandler\nonenote.py (412L)"]
    PST_H["OutlookHandler\noutlook.py (743L)"]
    ACC_H["AccessHandler\naccess.py (932L)"]
    MDW_H["MdwHandler\nmdw.py (80L)"]
  end
  subgraph "Crypto Foundation (crypto/)"
    CIPH["ciphers.py\nAll cipher ops\n(sole cryptography import)"]
    HASH["hashes.py\nAll hash/KDF ops\n(sole argon2-cffi import)"]
  end
  subgraph "Crypto Protocol (crypto/)"
    XOR["xor.py\nXOR obfuscation"]
    RC4["rc4legacy.py\nRC4+MD5, RC4+SHA1"]
    E376["ecma376.py\nStandard + Agile"]
    PROT["protection.py\nISO hash"]
    ODF_C["odf.py\nPBKDF2 + BF/AES/GCM"]
    VBA["vba.py\nDPB decode + SHA-1"]
    PST_C["pst.py\nCRC-32 + body ciphers"]
    MPP["mpp.py\nProject XOR decode"]
    MSISAM["msisam.py\nMoney RC4 pages"]
    ULS["uls.py\nJet ULS 3DES decode"]
  end
  RC4 & E376 & ODF_C & MSISAM & ULS --> CIPH
  E376 & RC4 & ODF_C & PROT & VBA & MSISAM --> HASH
  OLE --> XOR & RC4 & E376 & VBA & MPP
  OOXML --> PROT & VBA
  ODF_H --> ODF_C
  ON_H --> E376 & RC4
  PST_H --> PST_C
  ACC_H --> E376 & RC4 & MSISAM & VBA
  MDW_H --> ULS

Analysis → Code Mapping

Format docs → Format handlers

Analysis doc Handler class Source file Container magic
../research/formats/word.md OleHandler (.doc) / OoxmlHandler (.docx) formats/ole.py / formats/ooxml.py D0CF11E0 / 504B0304
../research/formats/excel.md OleHandler (.xls) / OoxmlHandler (.xlsx/.xlsb) formats/ole.py / formats/ooxml.py D0CF11E0 / 504B0304
../research/formats/powerpoint.md OleHandler (.ppt) / OoxmlHandler (.pptx) formats/ole.py / formats/ooxml.py D0CF11E0 / 504B0304
../research/formats/onenote.md OneNoteHandler formats/onenote.py guidFileType bytes
../research/formats/outlook.md OutlookHandler formats/outlook.py !BDN NDB magic
../research/formats/access.md AccessHandler formats/access.py Jet/ACE header at offset 0x04
../research/formats/opendocument.md OdfHandler formats/odf.py 504B0304 + mimetype entry
../research/formats/visio.md OleHandler (.vsd) / OoxmlHandler (.vsdx) formats/ole.py / formats/ooxml.py Same as Word/Excel/PPT
../research/formats/project.md OleHandler (.mpp) formats/ole.py D0CF11E0 (OUT-OF-CORPUS)
../research/formats/publisher.md OleHandler (.pub) formats/ole.py D0CF11E0 (OUT-OF-CORPUS)
../research/formats/infopath.md — (no encryption) — CAB archive

Algorithm docs → Crypto modules

Analysis doc Crypto module Key function Output
../research/algorithms/password/ecma376-agile.md crypto/ecma376.py verify_password(), decrypt_package() $office$*2013*... / $office$*sha512*...
../research/algorithms/password/ecma376-standard.md crypto/ecma376.py verify_password(), decrypt_package() $office$*2007*... / $office$*sha1*aes128*...
../research/algorithms/password/rc4-cryptoapi-sha1.md crypto/rc4legacy.py rc4_cryptoapi_verify() $oldoffice$3*... / $office$*sha1*rc4*...
../research/algorithms/password/rc4-md5-legacy.md crypto/rc4legacy.py rc4_md5_verify() $oldoffice$0*... / $office$*md5*rc4*...
../research/algorithms/password/xor-obfuscation.md crypto/xor.py xor_decode_method1(), reconstruct_password() No hash line — DECODE/COLLIDE
../research/algorithms/password/iso-protection-hash.md crypto/protection.py verify(), hash_line_25300() $office$2016$0$... / $protect$*sha512*...
../research/algorithms/password/word2007-documentprotection.md crypto/protection.py word2007_protection_verify() $protect$*sha1*word2007*...
../research/algorithms/password/odf-encryption.md crypto/odf.py verify(), decrypt_entry() $odf$*0*0*... / $odf$*sha1*pbkdf2*bf*...
../research/algorithms/password/vba-project-password.md crypto/vba.py decode_data_encryption(), extract_vba_hash() sha1hex:keyhex / $vba$*sha1*...*...
../research/algorithms/password/pst-crc32.md crypto/pst.py password_crc(), crc_collision() No hash line — COLLIDE
../research/algorithms/non-password/pst-body-cipher.md crypto/pst.py permute_decode(), cyclic_decode() No hash — DECODE
../research/algorithms/non-password/irm-rms-encryption.md — (not recoverable) — RecoveryClass.NONE
../research/algorithms/non-password/digital-signatures.md — (STRIP only) — RecoveryClass.STRIP
../research/algorithms/non-password/access-jet-obfuscation.md formats/access.py Jet 3/4 header XOR decode No hash — DECODE

Recovery Class Decision Tree

flowchart TD
  GATE["Gate found"] --> Q1{"Has a default\npassword?"}
  Q1 -->|"Yes"| DEFAULT["🟢 DEFAULT\nTry VelvetSweatshop\nor /01Hannes Ruescher/01"]
  Q1 -->|"No"| Q2{"Password stored\nin cleartext?"}
  Q2 -->|"Yes"| READ["🟢 READ\nWord .doc write-res\nPPT .ppt modify"]
  Q2 -->|"No"| Q3{"Reversible\nobfuscation?"}
  Q3 -->|"Yes"| DECODE["🟢 DECODE\nXOR array, Jet XOR\nPST body cipher\nVBA DPB wrapper"]
  Q3 -->|"No"| Q4{"Content is\nplaintext?"}
  Q4 -->|"Yes"| Q5{"Verifier is\n≤32 bits?"}
  Q5 -->|"Yes"| COLLIDE["🟡 COLLIDE\n16-bit verifier\nPST CRC-32"]
  Q5 -->|"No"| STRIP["🟡 STRIP/BYPASS\nDelete XML element\nor zero the record"]
  Q4 -->|"No"| Q6{"Key is\nbrute-forceable?"}
  Q6 -->|"Yes"| KEYSPACE["🔴 KEYSPACE\nRC4-40 bit key\n(2^40 search)"]
  Q6 -->|"No"| Q7{"Password-derived\nverifier?"}
  Q7 -->|"Yes"| CRACK["🔴 CRACK\nGPU hashcat\nor toolkit verify"]
  Q7 -->|"No"| NONE["⚪ NONE\nIRM/RMS certificate\nDigital signature"]

The 4 Unified Hash-Line Families

How the self-describing --format opt lines map to hashcat modes:

$office$ — document-open encryption

Scheme fields hashcat mode Notes
sha1 * aes128 * cbc * 100000 9500 Office 2010 default
sha512 * aes256 * cbc * 100000 9600 Office 2013+ default
sha1 * aes128 * ecb * 50000 9400 Office 2007 standard
md5 * rc4 * stream * 0 9700 Office 97/2000
sha1 * rc4 * stream * 0 9800 Office XP/2003
Any other agile tuple — (GAP) Toolkit verifies in Python

$protect$ — editing/permission verifiers

Scheme fields hashcat mode Notes
sha512 * iso * 100000 25300 Office 2013+ default
Any other primitive/spinCount — (GAP) Toolkit verifies in Python

$odf$ — OpenDocument encryption

Scheme fields hashcat mode Notes
sha1 * pbkdf2 * bf_cfb * 1024 18600 ODF 1.1
sha256 * pbkdf2 * aes256cbc * 100000 18400 ODF 1.2
* argon2id * or * aes256gcm * — (GAP) Toolkit verifies in Python

$vba$ — VBA project password

Scheme fields hashcat mode Notes
sha1 * <hash> * <salt> 110 sha1($pass.$salt) with --hex-salt

GateType × RecoveryClass × HashScheme

The complete mapping from every gate type to its recovery path:

GateType Where RecoveryClass HashScheme hashcat mode
OPEN (agile) \EncryptionInfo vMinor=4 CRACK OFFICE_2013 / OFFICE_2010 / OFFICE_AGILE 9600 / 9500 / —
OPEN (standard) \EncryptionInfo vMinor=2 CRACK OFFICE_2007 9400
OPEN (RC4-MD5) FilePass vMajor=1 KEYSPACE / CRACK OLDOFFICE_RC4_MD5 9700
OPEN (RC4-SHA1) FilePass vMajor=2-4 KEYSPACE / CRACK OLDOFFICE_RC4_SHA1 9800
OPEN (XOR) FilePass wEncType=0 DECODE OFFICEXOR —
OPEN (ODF 1.1) manifest.xml Blowfish CRACK ODF 18600
OPEN (ODF 1.2) manifest.xml AES-CBC CRACK ODF 18400
OPEN (ODF 1.3) manifest.xml Argon2/GCM CRACK ODF —
OPEN (PST) PidTagPstPassword COLLIDE PST_CRC —
MODIFY (.doc) SttbfAssoc[0x11] READ — —
MODIFY (.ppt) ModifyPasswordAtom READ — —
MODIFY (.docx/.xlsx/.pptx) writeProtection / fileSharing / modifyVerifier STRIP / CRACK OFFICE_SHEETPROT / OFFICE_PROTECT 25300 / —
PROTECT_SHEET (.xls) Password record 0x0013 COLLIDE — —
PROTECT_SHEET (.xlsx) sheetProtection element STRIP / CRACK OFFICE_SHEETPROT / OFFICE_PROTECT 25300 / —
PROTECT_WORKBOOK workbookProtection STRIP / CRACK OFFICE_SHEETPROT / OFFICE_PROTECT 25300 / —
RESTRICT_EDIT (.doc) Dop.lKeyProtDoc COLLIDE — —
RESTRICT_EDIT (.docx legacy) documentProtection crypt* STRIP / CRACK DOC_PROTECT —
RESTRICT_EDIT (.docx ISO) documentProtection *Value STRIP / CRACK OFFICE_SHEETPROT / OFFICE_PROTECT 25300 / —
SECTION (OneNote) ObjectDataEncryptionKeyV2 CRACK OFFICE_2013 9600
VBA_PROJECT PROJECT DPB= BYPASS / CRACK VBA_SHA1 110
SIGNATURE _signatures / _xmlsignatures STRIP — —
IRM IRMDS / CertificateKeyEncryptor NONE — —
MAIL_BODY (PST) bCryptMethod DECODE — —

Module File Sizes

Layer File Lines Role
Crypto foundation crypto/ciphers.py 292 All cipher ops — sole cryptography import point (RC4, AES, Blowfish, 3DES, RC2, DESX)
crypto/hashes.py 493 All hash/KDF ops — sole argon2-cffi import point, pure-Python MD4 (RFC 1320) + MD2 (RFC 1319)
Crypto protocol crypto/pst.py 1063 CRC-32 + collider + permute/cyclic body ciphers
crypto/ecma376.py 600 Standard + agile KDFs, EncryptionInfo parser, decryptor
crypto/odf.py 609 PBKDF2 + Blowfish-CFB / AES-256-CBC / Argon2id + AES-GCM, manifest parser
crypto/uls.py 494 Jet User-Level Security 3DES password decode + MDW parsing
crypto/rc4legacy.py 459 MD5+RC4 and SHA-1+RC4 KDFs, $oldoffice$ lines
crypto/xor.py 406 XOR obfuscation: Method 1/2 verifiers, array, body transform
crypto/vba.py 344 DPB/CMG/GC decode, SHA-1 verifier, bypass triple
crypto/protection.py 307 ISO protection hash (iterator APPENDED), Word 2007 three-stage
crypto/msisam.py 188 MSISAM MD5/SHA-1 + RC4 page encryption (Microsoft Money)
crypto/mpp.py 165 Microsoft Project Props-based XOR password decode
Formats formats/ole.py 2036 OLE/CFB: FilePass, EncryptionInfo, FibBase, DopBase, SttbfAssoc
formats/access.py 932 Access: Jet 3/4 decode, ACE 2007/2010+ agile, VBA extraction
formats/ooxml.py 931 OOXML ZIP: sheetProtection, workbookProtection, documentProtection XML
formats/outlook.py 743 PST: HEADER, bCryptMethod, NID_MESSAGE_STORE, PidTagPstPassword
formats/odf.py 499 ODF ZIP: manifest.xml parsing, per-stream extraction
formats/onenote.py 412 OneNote: FileNode walk, magic-bounded blob, EncryptionInfo carve
formats/mdw.py 80 MDW/MDA: Jet workgroup file detection + ULS dispatch
Core cli/app.py 1532 Typer CLI: info, unlock, advanced (8 subcommands)
detect.py 363 Magic-byte detection, product detection, handler dispatch
models.py 280 ContainerKind, GateType, RecoveryClass, Gate, ExtractedHash, DetectResult
constants.py 59 VelvetSweatshop, /01Hannes Ruescher/01, magic bytes, exit codes
extract.py 43 Facade: detect → handler.extract()
bypass.py 38 Facade: detect → handler.bypass()
decrypt.py 34 Facade: detect → handler.decrypt()
defaults.py 23 Default password list
Total 13,636