Skip to content

Feature engineering for a macOS malware classifier, and why the Mac makes it harder than Windows

Calvin So Calvin So
Jacky Xue Jacky Xue
Cristian Molina Cristian Molina
Feature engineering for a macOS malware classifier, and why the Mac makes it harder than Windows

The threat landscape for macOS malware is expanding, while the number of specialists dedicated to defending against it has not kept pace. Unlike Windows, where machine learning (ML) models are supported by extensive documentation and standardized datasets, the macOS environment presents a significant knowledge gap. At Iru, we captured SSTAR Agent, a crypto drainer targeting web3 technology developers via a fake interview lure, which we documented in an earlier post using an ML triage we built.

The current landscape

The broader security industry is heavily concentrated on Windows, with smaller contingents focused on Android and Linux. Consequently, deep expertise in Apple’s ecosystem remains rare, and even experienced security generalists may lack familiarity with core system internals.

However, building an ML classifier for Windows executables benefits from decades of robust research, including public labeled datasets like EMBER and extensive academic literature on Portable Executable (PE) format features. Researchers building a Windows-focused classifier can lean on established feature extraction pipelines, well-understood header structures, and a mature body of work on adversarial evasion techniques specific to the PE format. No equivalent infrastructure exists for Mach-O binaries.

This post focuses on the stage where the absence of institutional infrastructure is felt most acutely: feature engineering, the work of deciding what measurable signals to extract from a binary so a model can learn to distinguish malicious from benign.

Why ML at all, for example, macOS ships with XProtect? Signature-based detection asks whether a binary matches something seen before, and a recompile or altered string is often enough to slip past a rule. An ML classifier instead asks whether a binary structurally resembles known malware, a property that is much harder for an attacker to trivially modify.

The dataset

All results in this post are drawn from a single corpus of 7,138 Mach-O samples: 3,254 malicious and 3,884 benign. Malicious samples were sourced from VirusTotal and internal Iru telemetry, collected between June 2025 and June 2026. Benign samples came from notarized software plus manually vetted Mach-O binaries over the same period, and duplicates were removed by SHA-256 hash.

Two properties of this corpus matter for everything that follows. First, the benign set skews heavily toward software distributed through Apple's official channels, which means it is signed and notarized by construction; this sourcing bias resurfaces repeatedly in the analysis. Second, minor variations in sample counts across figures reflect a small number of samples that failed to parse for a given feature extractor, not different datasets. Figure 2, for example, reports 7,131 samples because 7 failed entropy extraction.

A note on the unit of analysis: Fat binaries

Before any feature is extracted, a question with no Windows equivalent arises: what is a "sample"? Most Mach-O files are universal (fat) binaries carrying multiple architecture slices, typically x86_64 and arm64, and nearly every feature is a property of a slice, not the file. Whole-file entropy and byte histograms blend slices together; code signing is evaluated per slice (a file can be validly signed on arm64 but only ad-hoc signed on x86_64); entitlements are read per slice, and by default only for the host's architecture; symbols such as __auth_stubs exist only on arm64e.

Extraction here was mixed: byte-level features used the whole file, codesign evaluated the host-matching slice, and segment parsing read only the first slice. The cleaner design, and the plan going forward, is to extract and predict per slice, taking the maximum slice probability as the file-level verdict.

Challenge 1: Feature engineering from scratch

Applying Windows ML concepts to macOS necessitates a complex translation process. While certain metrics map directly from Portable Executable (PE) structures to the Mach-O format, the Apple ecosystem requires the integration of unique signals, including code signing, notarization status, and entitlements.

The initial feature set for this analysis included:

  • Byte histograms: Quantifying the frequency of all 256 possible byte values.
  • Shannon entropy: Measuring the randomness of the file content.
  • Code signing status: Identifying whether the binary is signed and notarized.
  • Extracted strings: Analyzing text tokens within the binary.
  • Suspicious keyword counts: Utilizing a curated list of indicators associated with known threats, such as the SSTAR Agent.

Evaluating the effectiveness of these features is indispensable in ensuring model accuracy. Rather than relying on qualitative instinct, this research utilizes Mutual Information (MI) scoring to quantitatively assess the informativeness of each feature. MI provides a rigorous framework for determining the extent to which a specific feature contributes to distinguishing between malicious and benign samples, allowing for a more objective feature selection process.

image5

Figure 1: code_signing_status is by a wide margin the most informative single feature, roughly 70% higher than the next. A feature that strong invites a classifier to rely on it almost exclusively, and an attacker who obtains a valid Developer ID or abuses notarization collapses the very signal the model depends on. The remaining features (byte histogram, strings, and entropy) matter precisely because they degrade more gracefully under evasion.

The scoring process is subject to two specific limitations that must be addressed to prevent model bias.

  • MI rewards frequency rather than strength: rare but critical indicators of malicious activity may receive low scores simply because they appear infrequently, causing them to be overlooked during feature selection.
  • MI evaluates features in isolation, which limits the analysis of malicious behavior, since the most indicative threats often manifest only through the interaction of multiple signals.

Code signing therefore provides a powerful initial split, but must be integrated with behavioral signals rather than treated as a standalone indicator of malice.

Challenge 2: Entropy fails to separate malicious and benign Mac binaries

Entropy is a foundational metric in Windows-based malware detection: packed or encrypted payloads exhibit high randomness, while benign code tends to keep a lower, more structured byte pattern. In this research, that separation did not hold. The entropy of malicious and benign samples overlapped heavily rather than pulling apart.

image3Figure 2: Malicious and benign software accumulate at the same entropy values, sharing a dominant peak between 6 and 7 and overlapping across most of the range. Malware separates out in only a few narrow bands, none wide enough to establish a cut-off that reliably sorts one class from the other. Entropy alone reveals little about whether a binary is malicious, so the feature is retained only as a supporting signal. n = 7,131 (3,882 benign, 3,249 malicious); 7 samples failed entropy extraction and are excluded.

The overlap is a macOS-specific problem. On Windows, high entropy usually means a binary has been packed or encrypted to hide its code. But a great deal of legitimate Mac software carries high-entropy content for valid reasons: application bundles ship with compressed images and media, and programs built with runtimes such as Go or Electron produce large binaries with elevated entropy. Mac malware often runs the other way, arriving unpacked as plainly written credential stealers or padded application bundles. The two end up at the same entropy values, which is why the feature cannot separate them.

The problem may be that we're measuring the whole file at once. A Mach-O binary is made of distinct parts, and averaging across all of them hides the details: a fat binary contains a separate copy of the program for each chip type, and even a single copy mixes compressed images and media (which look random) with the actual program code (which doesn't). One score for the entire file smears these together, so a legitimate app with lots of media can look just as "random" as packed malware. Scoring each part of the binary separately should make the difference visible again; others building Mach-O classifiers report that per-segment entropy is one of their most useful measurements — so entropy stays in the feature set, with this more fine-grained version as the planned next step.

Challenge 3: Code signing and the limits of provenance

Windows secures the system primarily by checking who is trying to act, meaning the user and process context. macOS shifts the priority toward verifying what an application package is permitted to do. Because Apple's Gatekeeper and notarization pipeline mean that mainstream benign software is signed and notarized by construction, signing status separates the classes unusually well.

image4Figure 3: Label composition within each signing status, extracted by scripting the macOS codesign utility. The gradient is clean: fully signed binaries with entitlements ("ok") are only 25% malicious, binaries signed without entitlements are close to a coin toss at 48%, and unsigned binaries are 93% malicious. Signing status alone sorts the classes remarkably well, which is why it dominated the mutual-information ranking. Signed-but-no-entitlements binaries are common in legitimate software: entitlements are opt-in, and dylibs, frameworks, and helper binaries inside notarized apps typically carry none.

Signing status is not a behavioral property, however. It largely records whether a binary passed through Apple's official distribution path, and that path is where benign software originates by construction. An attacker who obtains a valid Developer ID moves from the unsigned column into the signed column without changing a line of malicious code, and the fact that a binary is signed says nothing about what it is permitted to do. A further wrinkle is that signing is evaluated per architecture slice: a fat binary can be validly signed on arm64 while carrying only an ad-hoc signature on x86_64, so any single per-file signing status is already a simplification.

The refinement is to examine entitlements, Apple's declared permission system with no real Windows equivalent, which reveal what a binary is actually allowed to do:

  • disable library validation, allowing unsigned or third-party code to load
  • access sensitive resources such as the camera and microphone
  • run unsigned executable memory or bypass standard code-signing checks

One extraction caveat: entitlements are embedded per slice, and standard tooling reads only the slice matching the host's architecture. An Apple Silicon analysis machine can therefore miss an entitlement declared only in the x86_64 slice.

image1Figure 4: Entitlements expose capability rather than provenance, and the split is sharp: the malware-leaning permissions cluster around debugging and sandbox escapes, while the benign-leaning ones map to ordinary consumer features such as keychain access and iCloud. The clearest single marker is com.apple.security.get-task-allow, carried by 403 binaries, 99% of them malicious.

get-task-allow marks a binary as debuggable. Xcode attaches it to development builds by default, and Apple requires it to be stripped before notarization. Its presence therefore means the binary is signed but never went through Apple's front door: it carries a development or ad-hoc signature rather than a notarized one.

This is also where the limits of the signal show. Much of what makes signing and entitlements so discriminating is a build-pipeline artifact rather than evidence of behavior. Benign software from official channels is notarized by construction, so part of the separation simply reflects where the samples were sourced. get-task-allow is worth flagging on any endpoint, but an attacker can strip it in seconds, so it belongs alongside behavioral features, never in place of them.

Challenge 4: When extracted features measure the toolchain

Extracting strings and allowing the model to identify the suspicious ones appears straightforward. Naive extraction, however, pulls every string in the file regardless of where it resides within the Mach-O structure, and the tokens that score highest as malware indicators prove to be the following:

image8

Figure 5: Top 10 malware-indicating string tokens by mutual information. None of __la_symbol_ptr, __stub_helper, or dyld_stub_binder represents malicious behavior. Each is a structural component of the lazy symbol binding process emitted by the static linker, and each correlates with the label only because it reflects how the malware in this dataset was compiled.

The pattern reflects a limitation noted earlier: a high mutual information score does not establish that the model is learning malice. In this case it is learning toolchains. The correction is to parse the Mach-O structure, group the strings by the section in which they reside, and filter out linker artifacts so that only behavior-relevant strings remain. Building that parser has a useful side effect: it enables a whole family of structural features at no extra cost, such as segment and section counts, section sizes, and the share of the file occupied by __TEXT. These describe the shape of a binary rather than its provenance, and practitioners report them among the most discriminating Mach-O features, so they join the roadmap as features in their own right.

The same pattern recurs in symbols, meaning imported and exported function names. Certain genuinely behavioral signals surface. The malicious association of fork, pipe, execl, and waitpid corresponds to the behavior of a shell-spawning stealer, while the benign association of Apple's os_log family is equally consistent, since legitimate software logs its activity and most malware does not. These signals are nonetheless surrounded by mangled C++ standard-library names that function as compiler fingerprints rather than indicators of behavior.

image2
Figure 6: Top 30 function-name symbols by mutual information across 7,138 samples (3,254 malicious, 3,884 benign). Red leans malicious, blue benign. The two highest-scoring symbols, __auth_stubs and __auth_got, are loader structures rather than behavior, and they exist only in arm64e binaries, so their score partly reflects which architecture slice was parsed. The raw ranking cannot be trusted at face value: behavioral signals must be separated from toolchain and architecture artifacts by hand.

Symbols such as fork, execl, and pipe describe what a binary does and belong in the feature set. The mangled C++ names and loader structures describe how it was built and must be discarded, though both score similarly on mutual information.

The byte histogram presents a clearer version of the same finding.

image7

Figure 7: Per-byte correlation with the label. Benign-leaning bytes (green) are readable letters, 0x61 "a", 0x65 "e", and 0x69 "i", while malicious-leaning bytes (red) are scrambled, high-value bytes. The feature therefore measures obfuscation rather than malice, and flags packed content of any kind, including legitimate software that happens to be compressed.

The byte histogram describes how a binary was constructed rather than what it does. It earns a place in the feature set as an obfuscation proxy but cannot separate malicious from benign on its own.

Challenge 5: Hand-built keyword lists and the problem of leakage

The first instinct of most analysts is a hand-picked list of suspicious substrings such as “api.telegram.org”, “stealer”, and “filegrabber” etc.

The list's mutual information score was the lowest of any feature, a result that follows from the frequency limitation described earlier: the counter is zero for nearly every file, so its average informativeness appears negligible, yet when it does fire it is among the strongest malicious signals available. Features of this kind should be evaluated by their precision when triggered rather than by their average informativeness.

The more significant risk is leakage. The token n4mlcg4prngej was taken from a single specific sample. Including it does not teach the model what malware is; it teaches the model to recognize one file from the training set, and hand-curated lists commonly contain this kind of memorized detail. A more durable version of the same approach is to use a YARA match as the feature. A well-written rule keys on a byte pattern or code construct shared across the variants of a malware family rather than on a string unique to one sample, so it continues to match as that family evolves. With YARA's XOR modifier, such a rule also detects single-byte XOR-encoded strings that a plain substring search would miss entirely.

Conclusion

No feature survived scrutiny unchanged. Some were discarded for measuring the build pipeline or memorizing individual samples; the rest were kept with their biases documented. The set retained for the first training run:

  • Code signing status: the strongest single split, used with its sourcing bias in mind
  • Codesign entitlements: capability signals such as get-task-allow, the closest proxy for declared intent
  • Byte histogram: an obfuscation proxy
  • Entropy: a supporting signal, to be scored per segment
  • Mach-O symbols: the genuine behavioral clues such as fork and execl, once loader structures are filtered out
  • Strings: pending Mach-O-aware filtering of linker noise
  • Section-structure features: segment and section counts, sizes, and __TEXT ratios

The central lesson: on Windows, feature engineering is largely a literature review; on macOS, it is original research. Even the unit of analysis, the file or the slice within it, is a design decision with no Windows analogue, and every feature must be checked for whether it measures behavior, Apple's ecosystem, or the toolchain. The signing pipeline that makes macOS safer for users also distorts the training data, because it separates the classes by provenance rather than intent.

 

Recent Articles

Featured image: Teams running Vulnerability Response patch critical CVEs in half the time
Matt Day 2 min read

Teams running Vulnerability Response patch critical CVEs in half the time

We recently measured remediation behavior across anonymized customer fleets. Customer accounts running Vulnerability Response closed more of their critical vulnerabilities, and closed them faster. The pattern held across every customer cohort.

Educational
Featured image: Introducing Policy Management: Create, publish and track compliance policies in Iru
Pedro Ventura 5 min read

Introducing Policy Management: Create, publish and track compliance policies in Iru

It's Thursday afternoon. The audit is in 45 days.

Product News
Featured image: Compliance Automation momentum: new frameworks and industry recognition
Iru Team 4 min read

Compliance Automation momentum: new frameworks and industry recognition

As of this week, Iru Compliance Automation supports three new frameworks: CMMC, NIST SP 800-171, and ISO 27701. These frameworks join the others within Iru Compliance Automation today (SOC 2, ISO 27001, ISO 42001, GDPR, HIPAA, NIST 800-53, and NIST CSF 2.0), bringing the total to ten.

Product News

See Iru in action

Discover why thousands of teams choose Iru

By submitting this form I agree to Iru’s Privacy Policy and consent to be contacted by Iru about its products and services.

Stay up to date

Iru's bi-weekly collection of articles, videos, and research to keep IT & Security teams ahead of the curve.