Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ magic-module = [
# The `math` module.
math-module = []

# The `msi` module parses MSI files.
msi-module = [
"olecf-module",
"crypto",
]

# The `olecf` module
olecf-module = [
"dep:nom"
Expand All @@ -234,6 +240,8 @@ time-module = []

# The `vba` module
vba-module = [
"olecf-module",
"zip-module",
"dep:codepage",
"dep:encoding_rs",
"dep:nom",
Expand All @@ -259,19 +267,18 @@ default-modules = [
"dex-module",
"dotnet-module",
"elf-module",
"lnk-module",
"macho-module",
"math-module",
"msi-module",
"hash-module",
# Still experimental
# "olecf-module",
"olecf-module",
"pe-module",
"string-module",
"time-module",
"lnk-module",
# Still experimental
# "vba-module",
"test_proto2-module",
"test_proto3-module",
"vba-module",
"vt-module",
"zip-module",
]
Expand Down
2 changes: 2 additions & 0 deletions lib/src/modules/add_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ add_module!(modules, "macho", macho, "macho.Macho", Some("macho"), Some(macho::_
add_module!(modules, "magic", magic, "magic.Magic", Some("magic"), Some(magic::__main__ as MainFn));
#[cfg(feature = "math-module")]
add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn));
#[cfg(feature = "msi-module")]
add_module!(modules, "msi", msi, "msi.Msi", Some("msi"), Some(msi::__main__ as MainFn));
#[cfg(feature = "olecf-module")]
add_module!(modules, "olecf", olecf, "olecf.Olecf", Some("olecf"), Some(olecf::__main__ as MainFn));
#[cfg(feature = "pe-module")]
Expand Down
14 changes: 13 additions & 1 deletion lib/src/modules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,14 @@ pub enum ModuleError {
#[derive(Default)]
pub struct ModuleContext<'a> {
module_metadata: FxHashMap<&'static str, &'a [u8]>,
#[cfg(any(
feature = "olecf-module",
feature = "msi-module",
feature = "vba-module"
))]
pub(crate) olecf_cache: Option<utils::olecf::CachedOlecf<'a>>,
#[cfg(any(feature = "zip-module", feature = "vba-module"))]
pub(crate) zip_cache: Option<utils::zip::ZipCache<'a>>,
pub(crate) zip_cache: Option<utils::zip::CachedZip<'a>>,
}

impl<'a> ModuleContext<'a> {
Expand Down Expand Up @@ -267,6 +273,11 @@ pub mod mods {
/// Data structure returned by the `olecf` module.
pub use super::protos::olecf::Olecf;

/// Data structures defined by the `msi` module.
pub use super::protos::msi;
/// Data structure returned by the `msi` module.
pub use super::protos::msi::Msi;

/// Data structures defined by the `vba` module.
///
/// The main structure produced by the module is [`vba::Vba`]. The rest
Expand Down Expand Up @@ -379,6 +390,7 @@ pub mod mods {
info.vba = protobuf::MessageField(invoke::<Vba>(data));
info.crx = protobuf::MessageField(invoke::<Crx>(data));
info.dex = protobuf::MessageField(invoke::<Dex>(data));
info.msi = protobuf::MessageField(invoke::<Msi>(data));
info
}

Expand Down
2 changes: 2 additions & 0 deletions lib/src/modules/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ mod macho;
mod magic;
#[cfg(feature = "math-module")]
mod math;
#[cfg(feature = "msi-module")]
mod msi;
#[cfg(feature = "olecf-module")]
mod olecf;
#[cfg(feature = "pe-module")]
Expand Down
39 changes: 39 additions & 0 deletions lib/src/modules/msi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*! YARA module that parses Microsoft Software Installer (MSI) files.

MSI files are OLE Compound Files (CFB) containing installation packages.
This module specializes in validating digital signatures in MSI files.
*/

use crate::errors::ModuleError;
use crate::mods::prelude::*;
use crate::modules::protos::msi::*;
use crate::modules::utils::olecf::CachedOlecf;

pub mod parser;

fn main<'a>(
ctx: &mut ModuleContext<'a>,
data: &'a [u8],
) -> Result<Msi, ModuleError> {
let cached = ctx.olecf_cache.get_or_insert_with(|| CachedOlecf::new(data));

let olecf = match cached {
CachedOlecf::Olecf(olecf) => olecf,
CachedOlecf::NotOlecf => {
let mut msi = Msi::new();
msi.set_is_signed(false);
return Ok(msi);
}
};

match parser::Msi::parse(olecf) {
Ok(msi) => Ok(msi),
Err(_) => {
let mut msi = Msi::new();
msi.set_is_signed(false);
Ok(msi)
}
}
}

register_module!("msi", Msi, main);
79 changes: 79 additions & 0 deletions lib/src/modules/msi/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::modules::olecf::parser::Olecf;
use crate::modules::protos;
use crate::modules::protos::msi::Msi as MsiProto;
use crate::modules::utils::authenticode::{
AuthenticodeHasher, AuthenticodeParser,
};

pub struct Msi;

struct MsiHasher<'a> {
olecf: &'a Olecf<'a>,
}

impl AuthenticodeHasher for MsiHasher<'_> {
fn hash(&self, digest: &mut dyn digest::Update) -> Option<()> {
let data = self.olecf.data();
let sector_size = self.olecf.sector_size();
if data.len() < sector_size {
return None;
}

// 1. CFB Header:
// First sector_size bytes, with CLSID (bytes 8..24)
// and State Bits (bytes 24..28) zeroed out.
let header_bytes = &data[..sector_size];
if header_bytes.len() >= 28 {
digest.update(&header_bytes[..8]);
digest.update(&[0u8; 20]);
digest.update(&header_bytes[28..]);
} else {
digest.update(header_bytes);
}

// 2. Stream contents:
// Hash stream data for all streams except DigitalSignature and MsiDigitalSignatureEx.
for (name, _) in self.olecf.streams() {
let clean_name = name.trim_start_matches(|c: char| c < '\u{20}');
if clean_name.eq_ignore_ascii_case("DigitalSignature")
|| clean_name.eq_ignore_ascii_case("MsiDigitalSignatureEx")
{
continue;
}
if let Ok(stream_data) = self.olecf.get_stream_data(name) {
digest.update(&stream_data);
}
}

Some(())
}
}

impl Msi {
pub fn parse<'a>(olecf: &Olecf<'a>) -> Result<MsiProto, &'static str> {
// Find the digital signature stream
let sig_stream = olecf.streams().find(|(name, _)| {
let clean = name.trim_start_matches(|c: char| c < '\u{20}');
clean.eq_ignore_ascii_case("DigitalSignature")
});

let (sig_name, _) =
sig_stream.ok_or("No digital signature stream found")?;
let sig_data = olecf
.get_stream_data(sig_name)
.map_err(|_| "Failed to read digital signature stream")?;

let hasher = MsiHasher { olecf };
let signatures = AuthenticodeParser::parse(&sig_data, &hasher)
.map_err(|_| "Failed to parse signature")?;

let mut msi_proto = MsiProto::new();
let pb_signatures: Vec<_> =
signatures.iter().map(protos::pe::Signature::from).collect();

msi_proto.set_is_signed(!pb_signatures.is_empty());
msi_proto.signatures = pb_signatures;

Ok(msi_proto)
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
is_signed: true
signatures:
- subject: "/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Time Stamping Signer"
issuer: "/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Object"
thumbprint: "3dbb6db5085c6dd5a1ca7f9cf84ecb1a3910cac8"
version: 3
algorithm: "sha1WithRSAEncryption"
algorithm_oid: "1.2.840.113549.1.1.5"
serial: "47:8a:8e:fb:59:e1:d8:3f:0c:e1:42:d2:a2:87:07:be"
not_before: 1273449600 # 2010-05-10 00:00:00 UTC
not_after: 1431302399 # 2015-05-10 23:59:59 UTC
verified: false
digest_alg: "sha1"
digest: "046bddfc01a3a99d43f12d2eb398ca319de70bd7"
file_digest: "fd498f5737d19a6cd212dddc66876cb99bbe34a4"
number_of_certificates: 2
number_of_countersignatures: 1
signer_info:
program_name: "RAD PDF"
more_info: "http://www.radpdf.com "
digest: "32b81b8da4ad354b928ccc08fb5285fcbd83e199"
digest_alg: "sha1"
chain:
- issuer: "/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Object"
subject: "/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Time Stamping Signer"
thumbprint: "3dbb6db5085c6dd5a1ca7f9cf84ecb1a3910cac8"
version: 3
algorithm: "sha1WithRSAEncryption"
algorithm_oid: "1.2.840.113549.1.1.5"
serial: "47:8a:8e:fb:59:e1:d8:3f:0c:e1:42:d2:a2:87:07:be"
not_before: 1273449600 # 2010-05-10 00:00:00 UTC
not_after: 1431302399 # 2015-05-10 23:59:59 UTC
certificates:
- issuer: "/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Object"
subject: "/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Time Stamping Signer"
thumbprint: "3dbb6db5085c6dd5a1ca7f9cf84ecb1a3910cac8"
version: 3
algorithm: "sha1WithRSAEncryption"
algorithm_oid: "1.2.840.113549.1.1.5"
serial: "47:8a:8e:fb:59:e1:d8:3f:0c:e1:42:d2:a2:87:07:be"
not_before: 1273449600 # 2010-05-10 00:00:00 UTC
not_after: 1431302399 # 2015-05-10 23:59:59 UTC
- issuer: "/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Object"
subject: "/C=US/postalCode=92024/ST=CA/L=Encinitas/O=Red Software/CN=Red Software"
thumbprint: "66d2ec68659e9dd90646258905b59353d5a5d259"
version: 3
algorithm: "sha1WithRSAEncryption"
algorithm_oid: "1.2.840.113549.1.1.5"
serial: "1f:37:b6:d1:4d:22:11:b9:4b:d4:44:2d:da:ef:8a:1d"
not_before: 1294963200 # 2011-01-14 00:00:00 UTC
not_after: 1389657599 # 2014-01-13 23:59:59 UTC
countersignatures:
- verified: true
sign_time: 1381695422 # 2013-10-13 20:17:02 UTC
digest: "1e59b862158311a3fcfbf779dd908d157992ba2b"
digest_alg: "sha1"
chain:
- issuer: "/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Object"
subject: "/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Time Stamping Signer"
thumbprint: "3dbb6db5085c6dd5a1ca7f9cf84ecb1a3910cac8"
version: 3
algorithm: "sha1WithRSAEncryption"
algorithm_oid: "1.2.840.113549.1.1.5"
serial: "47:8a:8e:fb:59:e1:d8:3f:0c:e1:42:d2:a2:87:07:be"
not_before: 1273449600 # 2010-05-10 00:00:00 UTC
not_after: 1431302399 # 2015-05-10 23:59:59 UTC
51 changes: 29 additions & 22 deletions lib/src/modules/olecf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,45 @@ https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b
use crate::errors::ModuleError;
use crate::mods::prelude::*;
use crate::modules::protos::olecf::*;
use crate::modules::utils::olecf::CachedOlecf;

pub mod parser;

#[cfg(test)]
mod tests;

fn main(_ctx: &mut ModuleContext, data: &[u8]) -> Result<Olecf, ModuleError> {
fn main<'a>(
ctx: &mut ModuleContext<'a>,
data: &'a [u8],
) -> Result<Olecf, ModuleError> {
let cached = ctx.olecf_cache.get_or_insert_with(|| CachedOlecf::new(data));
let mut olecf = Olecf::new();

match parser::OLECF::parse(data) {
Ok(parsed) => {
olecf.set_is_olecf(parsed.is_valid_header());
olecf.streams = parsed
.streams()
.map(|(name, entry)| {
let mut s = Stream::new();
s.set_name(name.to_string());
s.set_size(entry.size);
s.set_type(match entry.stream_type {
parser::DirEntryType::Storage => StreamType::STORAGE,
parser::DirEntryType::Stream => StreamType::STREAM,
parser::DirEntryType::RootStorage => StreamType::ROOT,
_ => StreamType::UNKNOWN,
});
s
})
.collect();
}
Err(_) => {
let cached = match cached {
CachedOlecf::Olecf(olecf) => olecf,
CachedOlecf::NotOlecf => {
olecf.set_is_olecf(false);
return Ok(olecf);
}
}
};

olecf.set_is_olecf(cached.is_valid_header());

olecf.streams = cached
.streams()
.map(|(name, entry)| {
let mut s = Stream::new();
s.set_name(name.to_string());
s.set_size(entry.size);
s.set_type(match entry.stream_type {
parser::DirEntryType::Storage => StreamType::STORAGE,
parser::DirEntryType::Stream => StreamType::STREAM,
parser::DirEntryType::RootStorage => StreamType::ROOT,
_ => StreamType::UNKNOWN,
});
s
})
.collect();

Ok(olecf)
}
Expand Down
Loading
Loading