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.
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)
Practical Steps
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.4domain: cybersecurity5subdomain: malware-analysis6tags: [supply-chain, malware-analysis, trojanized-software, solarwinds, 3cx, dependency-confusion, software-integrity]7version: "1.0"8author: mahipal9license: Apache-2.010---1112# Analyzing Supply Chain Malware Artifacts1314## Overview1516Supply 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.1718## Prerequisites1920- Python 3.9+ with `pefile`, `ssdeep`, `hashlib`21- Binary diff tools (BinDiff, Diaphora)22- Code signing verification tools (sigcheck, codesign)23- Software composition analysis (SCA) tools24- Access to legitimate software versions for comparison25- Package repository monitoring (npm, PyPI, NuGet)2627## Practical Steps2829### Step 1: Binary Comparison Analysis3031```python32#!/usr/bin/env python333"""Compare trojanized binary against legitimate version."""34import hashlib35import pefile36import sys37import json383940def compare_pe_files(legitimate_path, suspect_path):41 """Compare PE file structures between legitimate and suspect versions."""42 legit_pe = pefile.PE(legitimate_path)43 suspect_pe = pefile.PE(suspect_path)4445 report = {"differences": [], "suspicious_sections": [], "import_changes": []}4647 # Compare sections48 legit_sections = {s.Name.rstrip(b'\x00').decode(): {49 "size": s.SizeOfRawData,50 "entropy": s.get_entropy(),51 "characteristics": s.Characteristics,52 } for s in legit_pe.sections}5354 suspect_sections = {s.Name.rstrip(b'\x00').decode(): {55 "size": s.SizeOfRawData,56 "entropy": s.get_entropy(),57 "characteristics": s.Characteristics,58 } for s in suspect_pe.sections}5960 # Find new or modified sections61 for name, props in suspect_sections.items():62 if name not in legit_sections:63 report["suspicious_sections"].append({64 "name": name, "reason": "New section not in legitimate version",65 "size": props["size"], "entropy": round(props["entropy"], 2),66 })67 elif abs(props["size"] - legit_sections[name]["size"]) > 1024:68 report["suspicious_sections"].append({69 "name": name, "reason": "Section size significantly changed",70 "legit_size": legit_sections[name]["size"],71 "suspect_size": props["size"],72 })7374 # Compare imports75 legit_imports = set()76 if hasattr(legit_pe, 'DIRECTORY_ENTRY_IMPORT'):77 for entry in legit_pe.DIRECTORY_ENTRY_IMPORT:78 for imp in entry.imports:79 if imp.name:80 legit_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")8182 suspect_imports = set()83 if hasattr(suspect_pe, 'DIRECTORY_ENTRY_IMPORT'):84 for entry in suspect_pe.DIRECTORY_ENTRY_IMPORT:85 for imp in entry.imports:86 if imp.name:87 suspect_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")8889 new_imports = suspect_imports - legit_imports90 if new_imports:91 report["import_changes"] = list(new_imports)9293 # Check code signing94 report["legit_signed"] = bool(legit_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)95 report["suspect_signed"] = bool(suspect_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)9697 return report9899100def hash_file(filepath):101 """Calculate multiple hashes for a file."""102 hashes = {}103 with open(filepath, 'rb') as f:104 data = f.read()105 for algo in ['md5', 'sha1', 'sha256']:106 h = hashlib.new(algo)107 h.update(data)108 hashes[algo] = h.hexdigest()109 return hashes110111112if __name__ == "__main__":113 if len(sys.argv) < 3:114 print(f"Usage: {sys.argv[0]} <legitimate_binary> <suspect_binary>")115 sys.exit(1)116 report = compare_pe_files(sys.argv[1], sys.argv[2])117 print(json.dumps(report, indent=2))118```119120## Validation Criteria121122- Trojanized components identified through binary diffing123- Injected code isolated and analyzed separately124- Code signing anomalies documented125- Infection timeline reconstructed from build artifacts126- Downstream impact scope assessed across affected systems127- IOCs extracted for detection and blocking128129## References130131- [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)132- [Fortinet - SolarWinds Supply Chain Attack](https://www.fortinet.com/resources/cyberglossary/solarwinds-cyber-attack)133- [Picus - 3CX SmoothOperator Analysis](https://www.picussecurity.com/resource/blog/smoothoperator-analysis-of-3cxdesktopapp-supply-chain-attack)134- [MITRE ATT&CK T1195 - Supply Chain Compromise](https://attack.mitre.org/techniques/T1195/)