# Code Similarity Methods for Malware Analysis A guide to the similarity comparison techniques used in malware family classification. ## Overview Code similarity analysis helps analysts answer key questions: - Is this sample related to a known malware family? - Are these samples from the same author or campaign? - What code components are shared between variants? - How has a malware family evolved over time? ## Fuzzy Hashing ### ssdeep (Context-Triggered Piecewise Hashing) **How it works:** ssdeep divides a file into variable-length blocks using a rolling hash (based on the Adler-32 algorithm). The trigger points depend on file content, so similar files produce similar block boundaries. Each block is hashed to produce a fixed output, and the sequence of block hashes forms the fuzzy hash. **Strengths:** - Detects similarity even when portions of code change - Fast computation and comparison - Well-established in the malware analysis community **Limitations:** - Requires minimum file size (~4KB) for meaningful results - Sensitive to insertion/deletion of large blocks - Easily defeated by adding/removing padding or reordering sections - Score is not transitive: A~B and B~C does not guarantee A~C **Scoring:** - 0: No similarity - 1-30: Low similarity, possibly coincidental - 31-70: Moderate similarity, likely related - 71-100: High similarity, very likely same family or variant ### TLSH (Trend Micro Locality Sensitive Hash) **How it works:** TLSH computes a locality-sensitive hash by: 1. Sliding a 5-byte window across the file 2. Computing Pearson hash triplets from each window 3. Building a 128-bucket histogram of hash values 4. Encoding quartile boundaries and file length into the hash **Strengths:** - More robust than ssdeep against small modifications - Better at handling code reordering - Produces distance metric (not just similarity score) - Works well for clustering large sample sets **Limitations:** - Requires minimum ~50 bytes of data - Requires sufficient byte diversity (fails on highly repetitive data) - Distance score requires calibration per use case **Scoring (distance):** - 0-30: Very similar, likely same family - 31-100: Moderately similar, investigate further - 101-200: Low similarity, may share some components - 200+: Likely unrelated ## Import Table Analysis ### Jaccard Similarity on Import Sets **How it works:** Extract the set of imported functions (DLL:function pairs) from each PE file. Compute the Jaccard similarity coefficient: |A intersect B| / |A union B|. **Strengths:** - Captures functional similarity (what APIs the malware uses) - Robust against code-level modifications - Unaffected by compiler optimizations or recompilation - Useful for identifying shared toolkits or builders **Limitations:** - Only works with PE files that have intact import tables - Packed/obfuscated samples hide true imports - Dynamic API resolution (GetProcAddress) is not captured - Common API usage (kernel32, user32 basics) creates noise **Interpretation:** - Jaccard > 0.8: Very similar functionality, likely same family - Jaccard 0.5-0.8: Significant overlap, possible shared toolkit - Jaccard 0.3-0.5: Some shared functionality, investigate further - Jaccard < 0.3: Different functional profiles ### Import Hash (imphash) **How it works:** Concatenate all imported DLL:function pairs in order, compute MD5 hash. Two files with identical import tables produce the same imphash. **Strengths:** - Exact matching is very fast (simple hash comparison) - High specificity: matching imphash strongly indicates same family/builder - Widely supported (pefile, VirusTotal, YARA) **Limitations:** - Any change in imports produces a different hash (no partial matching) - Import order matters, so different compilers may produce different hashes - Does not capture similarity, only exact matches ## String Comparison ### Jaccard Similarity on String Sets **How it works:** Extract printable strings from each binary, compute set overlap. **What to compare:** - Full string sets (broad comparison) - Filtered for interesting strings only (URLs, paths, mutexes) - N-gram analysis of strings for partial matching **High-value strings for comparison:** | String Type | Example | Significance | |-------------|---------|-------------| | PDB paths | `C:\Users\dev\malware\Release\payload.pdb` | Shared build environment | | Mutex names | `Global\{GUID}` | Execution markers | | Registry keys | `HKCU\Software\MalwareConfig` | Shared configuration | | C2 URLs | `http://c2.example.com/gate.php` | Same campaign | | User-Agents | `Mozilla/5.0 (Bot; v2.1)` | Shared C2 protocol | | Error messages | Custom debug strings | Shared source code | | Encryption keys | Hardcoded keys/IVs | Shared crypto implementation | ## Function-Level Comparison ### Basic Block Hashing **How it works:** Disassemble each function, normalize instructions (remove addresses, register names), and hash the sequence of instruction opcodes. **Tools:** - BinDiff (commercial, Ghidra/IDA plugin) - Diaphora (open-source IDA plugin) - radare2 (open-source, `zign` commands) - FLOSS/capa (function-level capability matching) ### Control Flow Graph Matching **How it works:** Extract the control flow graph (CFG) of each function, then compare graphs using subgraph isomorphism or graph edit distance. **Strengths:** - Resilient to instruction-level changes - Captures algorithmic similarity - Can match across different compilers or architectures **Limitations:** - Computationally expensive for large binaries - Obfuscation can transform CFGs significantly - Requires disassembly (fails on packed/encrypted code) ## Composite Scoring No single metric is sufficient for reliable classification. Best practice is to combine multiple metrics: ``` Overall Score = w1 * ssdeep_norm + w2 * tlsh_norm + w3 * imports_jaccard + w4 * strings_jaccard Suggested weights: w1 (ssdeep) = 0.20 w2 (tlsh) = 0.25 w3 (imports) = 0.35 w4 (strings) = 0.20 ``` Import similarity is weighted highest because it best captures functional intent and is least affected by simple obfuscation techniques. ## Practical Workflow 1. **Quick screen**: Compute imphash and ssdeep for all samples; exact matches are instant 2. **Broad comparison**: Run TLSH and import Jaccard on remaining samples 3. **Deep dive**: For borderline cases, compare strings and function-level code 4. **Clustering**: Apply hierarchical clustering with average linkage at threshold 0.6 5. **Validation**: Manually review cluster assignments, merge or split as needed ## References - ssdeep: https://ssdeep-project.github.io/ssdeep/ - TLSH: https://github.com/trendmicro/tlsh - BinDiff: https://www.zynamics.com/bindiff.html - Diaphora: https://github.com/joxeankoret/diaphora - imphash: Mandiant (FireEye), "Tracking Malware with Import Hashing"