Analyzing Supply Chain Malware Artifacts
Overview
Supply chain attacks compromise legitimate software distribution channels to deliver malware through trusted update mechanisms. Notable examples include SolarWinds SUNBURST (2020, affecting 18,000+ customers), 3CX SmoothOperator (2023, a cascading supply chain attack originating from Trading Technologies), and numerous npm/PyPI package poisoning campaigns. Analysis involves comparing trojanized binaries against legitimate versions, identifying injected code in build artifacts, examining code signing anomalies, and tracing the infection chain from initial compromise through payload delivery. As of 2025, supply chain attacks account for 30% of all breaches, a 100% increase from prior years.
When to Use
- When investigating security incidents that require analyzing supply chain malware artifacts
- When building detection rules or threat hunting queries for this domain
- When SOC analysts need structured procedures for this analysis type
- When validating security monitoring coverage for related attack techniques
Detection Gaps & Validation
- Hash/signature checks miss the injection. Trojanized SolarWinds/3CX builds carried valid code signatures because the build pipeline itself was compromised - a passing
sigcheck/codesign is not exoneration. Validate the signing cert chain, timestamp, and whether the cert was issued/used outside the vendor's normal pattern.
- Binary diffing misses small, gated implants. SUNBURST added a tiny dormant class that sleeps 12-14 days and geofences - byte-level diffs drown it in legitimate build churn. Diff at the function level (BinDiff/Diaphora) and hunt for new threads, time/domain checks, and DGA logic, not just section size deltas.
- The malicious section may look low-entropy and benign. Injected .NET/managed code blends into existing assemblies; rely on import/section additions plus new network or process-spawn capability, cross-referenced against the known-good version.
- Confirm a hit by acquiring the genuine vendor binary at the same version, comparing authoritative hashes (vendor advisory / VirusTotal first-seen), and detonating the suspect build in a sandbox to observe the dormant payload's C2/beacon.
- Scope beyond the one artifact. Dependency-confusion and npm/PyPI/NuGet poisoning hide in transitive deps, postinstall scripts, and typosquatted names - run SCA and inspect install hooks, not just the top-level package.
- Benign lookalikes: legitimate auto-updaters, telemetry, and newly added vendor features mimic "suspicious new network calls." Validate against vendor release notes and the signed baseline before declaring compromise.
Prerequisites
- Python 3.9+ with
pefile, ssdeep, hashlib
- Binary diff tools (BinDiff, Diaphora)
- Code signing verification tools (sigcheck, codesign)
- Software composition analysis (SCA) tools
- Access to legitimate software versions for comparison
- Package repository monitoring (npm, PyPI, NuGet)
Workflow
Step 1: Binary Comparison Analysis
#!/usr/bin/env python3
"""Compare trojanized binary against legitimate version."""
import hashlib
import pefile
import sys
import json
def compare_pe_files(legitimate_path, suspect_path):
"""Compare PE file structures between legitimate and suspect versions."""
legit_pe = pefile.PE(legitimate_path)
suspect_pe = pefile.PE(suspect_path)
report = {"differences": [], "suspicious_sections": [], "import_changes": []}
# Compare sections
legit_sections = {s.Name.rstrip(b'\x00').decode(): {
"size": s.SizeOfRawData,
"entropy": s.get_entropy(),
"characteristics": s.Characteristics,
} for s in legit_pe.sections}
suspect_sections = {s.Name.rstrip(b'\x00').decode(): {
"size": s.SizeOfRawData,
"entropy": s.get_entropy(),
"characteristics": s.Characteristics,
} for s in suspect_pe.sections}
# Find new or modified sections
for name, props in suspect_sections.items():
if name not in legit_sections:
report["suspicious_sections"].append({
"name": name, "reason": "New section not in legitimate version",
"size": props["size"], "entropy": round(props["entropy"], 2),
})
elif abs(props["size"] - legit_sections[name]["size"]) > 1024:
report["suspicious_sections"].append({
"name": name, "reason": "Section size significantly changed",
"legit_size": legit_sections[name]["size"],
"suspect_size": props["size"],
})
# Compare imports
legit_imports = set()
if hasattr(legit_pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in legit_pe.DIRECTORY_ENTRY_IMPORT:
for imp in entry.imports:
if imp.name:
legit_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")
suspect_imports = set()
if hasattr(suspect_pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in suspect_pe.DIRECTORY_ENTRY_IMPORT:
for imp in entry.imports:
if imp.name:
suspect_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")
new_imports = suspect_imports - legit_imports
if new_imports:
report["import_changes"] = list(new_imports)
# Check code signing
report["legit_signed"] = bool(legit_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)
report["suspect_signed"] = bool(suspect_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)
return report
def hash_file(filepath):
"""Calculate multiple hashes for a file."""
hashes = {}
with open(filepath, 'rb') as f:
data = f.read()
for algo in ['md5', 'sha1', 'sha256']:
h = hashlib.new(algo)
h.update(data)
hashes[algo] = h.hexdigest()
return hashes
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <legitimate_binary> <suspect_binary>")
sys.exit(1)
report = compare_pe_files(sys.argv[1], sys.argv[2])
print(json.dumps(report, indent=2))
Validation Criteria
- Trojanized components identified through binary diffing
- Injected code isolated and analyzed separately
- Code signing anomalies documented
- Infection timeline reconstructed from build artifacts
- Downstream impact scope assessed across affected systems
- IOCs extracted for detection and blocking
References
1---2name: analyzing-supply-chain-malware-artifacts3description: Investigate supply chain attack artifacts including trojanized software updates, compromised build pipelines, and sideloaded dependencies to identify intrusion vectors and scope of compromise.4license: Apache-2.05---6# Analyzing Supply Chain Malware Artifacts78## Overview910Supply chain attacks compromise legitimate software distribution channels to deliver malware through trusted update mechanisms. Notable examples include SolarWinds SUNBURST (2020, affecting 18,000+ customers), 3CX SmoothOperator (2023, a cascading supply chain attack originating from Trading Technologies), and numerous npm/PyPI package poisoning campaigns. Analysis involves comparing trojanized binaries against legitimate versions, identifying injected code in build artifacts, examining code signing anomalies, and tracing the infection chain from initial compromise through payload delivery. As of 2025, supply chain attacks account for 30% of all breaches, a 100% increase from prior years.111213## When to Use1415- When investigating security incidents that require analyzing supply chain malware artifacts16- When building detection rules or threat hunting queries for this domain17- When SOC analysts need structured procedures for this analysis type18- When validating security monitoring coverage for related attack techniques1920## Detection Gaps & Validation2122- **Hash/signature checks miss the injection.** Trojanized SolarWinds/3CX builds carried *valid* code signatures because the build pipeline itself was compromised - a passing `sigcheck`/`codesign` is not exoneration. Validate the signing cert chain, timestamp, and whether the cert was issued/used outside the vendor's normal pattern.23- **Binary diffing misses small, gated implants.** SUNBURST added a tiny dormant class that sleeps 12-14 days and geofences - byte-level diffs drown it in legitimate build churn. Diff at the function level (BinDiff/Diaphora) and hunt for new threads, time/domain checks, and DGA logic, not just section size deltas.24- **The malicious section may look low-entropy and benign.** Injected .NET/managed code blends into existing assemblies; rely on import/section *additions* plus new network or process-spawn capability, cross-referenced against the known-good version.25- **Confirm a hit** by acquiring the genuine vendor binary at the same version, comparing authoritative hashes (vendor advisory / VirusTotal first-seen), and detonating the suspect build in a sandbox to observe the dormant payload's C2/beacon.26- **Scope beyond the one artifact.** Dependency-confusion and npm/PyPI/NuGet poisoning hide in transitive deps, postinstall scripts, and typosquatted names - run SCA and inspect install hooks, not just the top-level package.27- **Benign lookalikes:** legitimate auto-updaters, telemetry, and newly added vendor features mimic "suspicious new network calls." Validate against vendor release notes and the signed baseline before declaring compromise.2829## Prerequisites3031- Python 3.9+ with `pefile`, `ssdeep`, `hashlib`32- Binary diff tools (BinDiff, Diaphora)33- Code signing verification tools (sigcheck, codesign)34- Software composition analysis (SCA) tools35- Access to legitimate software versions for comparison36- Package repository monitoring (npm, PyPI, NuGet)3738## Workflow3940### Step 1: Binary Comparison Analysis4142```python43#!/usr/bin/env python344"""Compare trojanized binary against legitimate version."""45import hashlib46import pefile47import sys48import json495051def compare_pe_files(legitimate_path, suspect_path):52 """Compare PE file structures between legitimate and suspect versions."""53 legit_pe = pefile.PE(legitimate_path)54 suspect_pe = pefile.PE(suspect_path)5556 report = {"differences": [], "suspicious_sections": [], "import_changes": []}5758 # Compare sections59 legit_sections = {s.Name.rstrip(b'\x00').decode(): {60 "size": s.SizeOfRawData,61 "entropy": s.get_entropy(),62 "characteristics": s.Characteristics,63 } for s in legit_pe.sections}6465 suspect_sections = {s.Name.rstrip(b'\x00').decode(): {66 "size": s.SizeOfRawData,67 "entropy": s.get_entropy(),68 "characteristics": s.Characteristics,69 } for s in suspect_pe.sections}7071 # Find new or modified sections72 for name, props in suspect_sections.items():73 if name not in legit_sections:74 report["suspicious_sections"].append({75 "name": name, "reason": "New section not in legitimate version",76 "size": props["size"], "entropy": round(props["entropy"], 2),77 })78 elif abs(props["size"] - legit_sections[name]["size"]) > 1024:79 report["suspicious_sections"].append({80 "name": name, "reason": "Section size significantly changed",81 "legit_size": legit_sections[name]["size"],82 "suspect_size": props["size"],83 })8485 # Compare imports86 legit_imports = set()87 if hasattr(legit_pe, 'DIRECTORY_ENTRY_IMPORT'):88 for entry in legit_pe.DIRECTORY_ENTRY_IMPORT:89 for imp in entry.imports:90 if imp.name:91 legit_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")9293 suspect_imports = set()94 if hasattr(suspect_pe, 'DIRECTORY_ENTRY_IMPORT'):95 for entry in suspect_pe.DIRECTORY_ENTRY_IMPORT:96 for imp in entry.imports:97 if imp.name:98 suspect_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")99100 new_imports = suspect_imports - legit_imports101 if new_imports:102 report["import_changes"] = list(new_imports)103104 # Check code signing105 report["legit_signed"] = bool(legit_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)106 report["suspect_signed"] = bool(suspect_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)107108 return report109110111def hash_file(filepath):112 """Calculate multiple hashes for a file."""113 hashes = {}114 with open(filepath, 'rb') as f:115 data = f.read()116 for algo in ['md5', 'sha1', 'sha256']:117 h = hashlib.new(algo)118 h.update(data)119 hashes[algo] = h.hexdigest()120 return hashes121122123if __name__ == "__main__":124 if len(sys.argv) < 3:125 print(f"Usage: {sys.argv[0]} <legitimate_binary> <suspect_binary>")126 sys.exit(1)127 report = compare_pe_files(sys.argv[1], sys.argv[2])128 print(json.dumps(report, indent=2))129```130131## Validation Criteria132133- Trojanized components identified through binary diffing134- Injected code isolated and analyzed separately135- Code signing anomalies documented136- Infection timeline reconstructed from build artifacts137- Downstream impact scope assessed across affected systems138- IOCs extracted for detection and blocking139140## References141142- [ReversingLabs - 3CX Supply Chain Analysis](https://www.reversinglabs.com/blog/what-went-wrong-with-the-3cx-software-supply-chain-attack-and-how-it-could-have-been-prevented)143- [Fortinet - SolarWinds Supply Chain Attack](https://www.fortinet.com/resources/cyberglossary/solarwinds-cyber-attack)144- [Picus - 3CX SmoothOperator Analysis](https://www.picussecurity.com/resource/blog/smoothoperator-analysis-of-3cxdesktopapp-supply-chain-attack)145- [MITRE ATT&CK T1195 - Supply Chain Compromise](https://attack.mitre.org/techniques/T1195/)