Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mach-o certificate parsing implemented #235

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
56 changes: 56 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ clap = "4.5.11"
clap_complete = "4.5.11"
const-oid = "0.9.6"
crc32fast = "1.4.2"
cryptographic-message-syntax = { version = "0.27.0", default-features = false }
der-parser = "9.0.0"
digest = "0.10.7"
dsa = "0.6.3"
Expand Down
51 changes: 16 additions & 35 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ rust-version.workspace = true

# Exclude test files from the package published to crates.io, as there's a
# limit of 10MB for the total package size.
exclude = [
"src/modules/**/*.zip",
"src/modules/**/*.out"
]
exclude = ["src/modules/**/*.zip", "src/modules/**/*.out"]

[features]
# Enables constant folding. When constant folding is enabled, expressions
Expand Down Expand Up @@ -100,44 +97,24 @@ console-module = []
cuckoo-module = []

# The `dotnet` module parses .NET files.
dotnet-module = [
"pe-module",
"dep:nom",
]
dotnet-module = ["pe-module", "dep:nom"]

# The `elf` module parses ELF files.
elf-module = [
"dep:tlsh-fixed",
"dep:nom",
"dep:md-5",
]
elf-module = ["dep:tlsh-fixed", "dep:nom", "dep:md-5"]

# The `hash` module provides functions for computing md5, sha1, sha-256,
# crc32 and checksum.
hash-module = [
"dep:md-5",
"dep:sha1",
"dep:sha2",
"dep:crc32fast",
]
hash-module = ["dep:md-5", "dep:sha1", "dep:sha2", "dep:crc32fast"]

# The `lnk` module parses LNK files.
lnk-module = [
"dep:uuid",
"dep:nom",
]
lnk-module = ["dep:uuid", "dep:nom"]

# The `macho` module parses Mach-O files.
macho-module = [
"dep:nom",
"dep:roxmltree",
]
macho-module = ["dep:nom", "dep:roxmltree", "dep:cryptographic-message-syntax"]

# The `magic` allows recognizing file types based on the output of the
# Unix `file` command. This feature is disabled by default.
magic-module = [
"dep:magic"
]
magic-module = ["dep:magic"]

# The `math` module.
math-module = []
Expand All @@ -157,7 +134,7 @@ pe-module = [
"dep:p384",
"dep:sha1",
"dep:sha2",
"dep:x509-parser"
"dep:x509-parser",
]

# The `string` modules offer some functions for parsing strings as integers,
Expand All @@ -170,9 +147,7 @@ test_proto3-module = []

# The `text` module is an example module described in the Module's Developer
# Guide. Not very useful in real life.
text-module = [
"dep:lingua"
]
text-module = ["dep:lingua"]

# The `time` module allows you to retrieve epoch in seconds that can be used in
# conditions of a rule to check against other epoch time.
Expand Down Expand Up @@ -257,7 +232,13 @@ yansi = { workspace = true }
yara-x-macros = { workspace = true }
yara-x-parser = { workspace = true, features = ["serde"] }

lingua = { version = "1.6.2", optional = true, default-features = false, features = ["english", "german", "french", "spanish"] }
lingua = { version = "1.6.2", optional = true, default-features = false, features = [
"english",
"german",
"french",
"spanish",
] }
cryptographic-message-syntax = { workspace = true, optional = true }

[build-dependencies]
anyhow = { workspace = true }
Expand Down
119 changes: 80 additions & 39 deletions lib/src/modules/macho/parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::modules::protos;
use bstr::{BStr, ByteSlice};
use cryptographic_message_syntax::SignedData;
use itertools::Itertools;
#[cfg(feature = "logging")]
use log::error;
Expand Down Expand Up @@ -34,7 +35,7 @@ const _CS_MAGIC_REQUIREMENTS: u32 = 0xfade0c01;
const _CS_MAGIC_CODEDIRECTORY: u32 = 0xfade0c02;
const _CS_MAGIC_EMBEDDED_SIGNATURE: u32 = 0xfade0cc0;
const _CS_MAGIC_DETACHED_SIGNATURE: u32 = 0xfade0cc1;
const _CS_MAGIC_BLOBWRAPPER: u32 = 0xfade0b01;
const CS_MAGIC_BLOBWRAPPER: u32 = 0xfade0b01;
const CS_MAGIC_EMBEDDED_ENTITLEMENTS: u32 = 0xfade7171;

/// Mach-O export flag constants
Expand Down Expand Up @@ -875,48 +876,88 @@ impl<'a> MachOFile<'a> {
for (offset, blob) in blobs {
let length = blob.length as usize;
let size_of_blob = std::mem::size_of::<CSBlob>();
if blob.magic == CS_MAGIC_EMBEDDED_ENTITLEMENTS {
let xml_data = match super_data
.get(offset + size_of_blob..offset + length)
{
Some(data) => data,
None => continue,
};

let xml_string =
std::str::from_utf8(xml_data).unwrap_or_default();

let opt = roxmltree::ParsingOptions {
allow_dtd: true,
..roxmltree::ParsingOptions::default()
};

if let Ok(parsed_xml) =
roxmltree::Document::parse_with_options(
xml_string, opt,
)
{
for node in parsed_xml.descendants().filter(|n| {
n.has_tag_name("key") || n.has_tag_name("array")
}) {
if let Some(entitlement) = node.text() {
if node.has_tag_name("array") {
node.descendants()
.filter_map(|n| n.text())
.filter(|t| !t.trim().is_empty())
.unique()
.map(|t| t.to_string())
.for_each(|array_entitlement| {
self.entitlements
.push(array_entitlement)
});
} else {
self.entitlements
.push(entitlement.to_string());
match blob.magic {
CS_MAGIC_EMBEDDED_ENTITLEMENTS => {
let xml_data = match super_data
.get(offset + size_of_blob..offset + length)
{
Some(data) => data,
None => continue,
};

let xml_string =
std::str::from_utf8(xml_data).unwrap_or_default();

let opt = roxmltree::ParsingOptions {
allow_dtd: true,
..roxmltree::ParsingOptions::default()
};

if let Ok(parsed_xml) =
roxmltree::Document::parse_with_options(
xml_string, opt,
)
{
for node in parsed_xml.descendants().filter(|n| {
n.has_tag_name("key")
|| n.has_tag_name("array")
}) {
if let Some(entitlement) = node.text() {
if node.has_tag_name("array") {
node.descendants()
.filter_map(|n| n.text())
.filter(|t| !t.trim().is_empty())
.unique()
.map(|t| t.to_string())
.for_each(|array_entitlement| {
self.entitlements
.push(array_entitlement)
});
} else {
self.entitlements
.push(entitlement.to_string());
}
}
}
}
}
CS_MAGIC_BLOBWRAPPER => {
if let Some(ber_blob) = super_data.get(
offset + size_of_blob
..offset.saturating_add(length),
) {
if let Ok(signage) =
SignedData::parse_ber(ber_blob)
{
let signers = signage.signers();
let certs = signage.certificates();
let mut cert_info = Certificates {
common_names: Vec::new(),
signer_names: Vec::new(),
};

certs.for_each(|cert| {
let name =
cert.subject_common_name().unwrap();
cert_info.common_names.push(name);
});

signers.for_each(|signer| {
let (name, _) = signer
.certificate_issuer_and_serial()
.unwrap();
cert_info.signer_names.push(
name.user_friendly_str()
.unwrap()
.to_string(),
);
});

self.certificates = Some(cert_info);
}
}
}
_ => {}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,13 @@ dyld_info:
lazy_bind_size: 2464
export_off: 31816
export_size: 2448
certificates:
common_names:
- "Developer ID Certification Authority"
- "Apple Root CA"
- "Developer ID Application: EFI Inc (82PCFB3NFC)"
signer_names:
- "CN=Developer ID Certification Authority, OU=Apple Certification Authority, O=Apple Inc., C=US"
uuid: "B23FC3D5-BDF8-3056-930A-C93E0F547B78"
min_version:
device: MACOSX
Expand Down
Binary file not shown.
Loading
Loading