Repository navigation
feat: introduce \ooxml module for inspecting OOXML documents - #803
RustyNoob-619 wants to merge 3 commits into
Conversation
The new `ooxml` module parses the ZIP end of central directory record and the central directory file headers of Office Open XML documents (DOCX, XLSX, PPTX) and other ZIP archives. It exposes whether the file is an OOXML document (it has a `[Content_Types].xml` entry), the entry counts, the central directory size and offset, the archive comment, and for every entry its name, sizes, CRC-32, compression method, MS-DOS timestamps, flags, the ZIP version needed, and the host operating system and ZIP version that created it. Entries are not decompressed. The per-entry metadata reflects the tool that produced a document, which is useful for clustering malicious documents created by the same builder. The module is a port of an OOXML module for YARA 4.x. It is experimental and not part of the default modules; enable it with the `ooxml-module` feature.
| // ZIP file comment. Undefined if the archive has no comment. | ||
| optional bytes zip_comment_str = 7; | ||
| // Entries in the central directory. | ||
| repeated Entry entries = 8; |
There was a problem hiding this comment.
Most of the fields in each Entry seem to belong to the ZIP format actually, and should be exposed by the zip module. In fact, the zip modules already exposes some information about each entry, like the compressed size and compression method. Duplicating that information in two modules doesn't make sense, let's put everyting belonging to the ZIP format in the zip module, and use the ooxml module for putting any information that can be obtained from the word/document.xml file or any other file containing metadata about the document.
In fact, it looks like all the information exposed by this module is actually ZIP-related stuff.
There was a problem hiding this comment.
Thanks, that does make a whole lot of sense, when I first started working on this module it was intended towards detecting malicious docs conforming to the ooxml format but at the end of the day, they are zip files. I did look into the zip module and it does seem to be limited, it would be nice to add the exposed fields from here and merge it in the modules, so totally agree with your thoughts.
For the ooxml, as you suggested we could rework it to expose document metadata only, using the existing ZIP cache such as docProps/core.xml and app.xml properties, the document type from [Content_Types].xml, and relationships with external targets (e.g. remote templates). To the best of my knowledge, the dependencies are already part of other modules and we could have something like ooxml-module = ["dep:tinyzip", "dep:flate2", "dep:roxmltree"]
So happy for this PR to be merged into the zip module (I can raise a new one for the zip module and conform with its structure/fields) and can create a new separate PR for the ooxml module, once it is ready
feat: introduce
ooxmlmodule for inspecting OOXML documentsThis PR adds an
ooxmlmodule that parses the ZIP central directory of OfficeOpen XML documents (DOCX, XLSX, PPTX) and other ZIP archives, and exposes the
metadata of every entry. It's a port of an OOXML module I wrote for YARA 4.x
(RustyNoob-619/YARA — Modules/OOXML).
Like
olecf,vbaandmsi, it is experimental and not in the defaultmodules; it's enabled with the
ooxml-modulefeature.Why
Weaponised Office documents remain one of the most common initial-access
vectors. Besides content, the ZIP metadata of a document carries a fingerprint
of the tool that built it: MS-DOS timestamps, host OS and ZIP version in
"version made by", flags, CRC-32s, entry ordering. These are useful for
clustering documents produced by the same builder, and for spotting
inconsistencies (e.g. on-disk vs. total entry counts, odd compression
methods, archive comments).
What it exposes
is_ooxml(bool): true if one of the entries is[Content_Types].xml, falsefor other ZIP archives, undefined if the file isn't a ZIP archive.
number_of_on_disk_entries,number_of_total_entries,central_dir_size,central_dir_offset,zip_comment_len,zip_comment_str.entries[]:name_string,name_length,compressed_size,uncompressed_size,crc32_checksum,compression_method_value,compression_method_name,mod_time_raw,mod_date_raw,flags,version_needed,version_made_by,os_name,spec_version.Relationship with the
zipmoduleThe existing
zipmodule exposes each entry's path, compression method andsizes.
ooxmlfocuses on the central directory metadata thatzipdoesn'texpose (CRC-32, timestamps, flags, ZIP versions, host OS, entry counts, central
directory location, archive comment) and on recognising OOXML documents. If
you'd prefer these fields to be added to
zipinstead of living in a separatemodule, I'm happy to rework the PR that way.
Behaviour
PK\x03\x04) are parsed, asin the original module.
the file and accepted only if its comment ends exactly at the end of the
file; comments up to the maximum 65,535 bytes are supported.
parsed until then are kept, so
number_of_total_entriesmay exceed thenumber of
entries.Files
Following the layout of the
zipmodule commit (e050078):lib/src/modules/protos/ooxml.proto(with field comments forfield_docs.rs)lib/src/modules/ooxml/mod.rslib/src/modules/ooxml/tests/mod.rsandtests/testdata/<sha256>.in.zip/<sha256>.out, prepared as described in the Module Developer's Guide(
objcopy -I binary -O ihex, thenzip); all inputs are synthetic andbenign, and each is described next to its hash in
tests/mod.rslib/Cargo.toml:ooxml-modulefeature, listed as experimental indefault-modulesmodules.rs,protos/generated/{mod.rs,ooxml.rs}, and theooxmlentries infield_docs.rs(only those; I didn't regenerate thewhole file); one line in
add_modules.rs, as done formsi.site/content/docs/modules/ooxml.md. Happy to drop it whilethe module is experimental.
yr dumpsupport (yr dump --module ooxml), mirroring thecommit that added
ziptodump(3bbe876):mods.proto,lib/src/modules/mod.rs(re-exports andinvoke_all),cli/src/commands/dump.rs, and the regeneratedprotos/generated/mods.rs.Without the
ooxml-modulefeature,dump --module ooxmlprints nothing,as with the other optional modules.
Testing
cargo test -p yara-x --features ooxml-module --lib: 340 passed (6 new ruletests plus the golden-file test over the new testdata).
cargo test -p yara-x --lib(default features): 334 passed; default buildsare unaffected.
cargo test -p yara-x-cli --bin yr --features yara-x/ooxml-module(with
CARGO_BIN_EXE_yrset): 56 passed.cargo fmt --all --checkandcargo clippy -p yara-x -p yara-x-cli --features yara-x/ooxml-module --tests --no-deps -- --deny clippy::all:clean.
identical module output on 50 files (real DOCX/XLSX/PPTX, plain and crafted
ZIPs, truncated archives, ZIP64, non-ZIP files — 923,608 values) and
identical rule results on 28 rules; the only difference is that this
version also handles a maximum-length (65,535-byte) archive comment, which
the original missed.