reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,381 @@
|
||||
---
|
||||
name: performing-malware-triage-with-yara
|
||||
description: 'Performs rapid malware triage and classification using YARA rules that
|
||||
match file patterns, strings, byte sequences, and structural characteristics against
|
||||
known malware families and suspicious indicators, covering rule writing, scanning,
|
||||
and integration into analysis pipelines. Use when classifying a batch of malware
|
||||
samples against known family signatures, writing detection rules for a newly
|
||||
analyzed malware family, or performing signature-based malware triage.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- YARA
|
||||
- triage
|
||||
- classification
|
||||
- pattern-matching
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1055
|
||||
- T1140
|
||||
- T1497
|
||||
- T0816
|
||||
---
|
||||
|
||||
# Performing Malware Triage with YARA
|
||||
|
||||
## When to Use
|
||||
|
||||
- Rapidly classifying a large batch of malware samples against known family signatures
|
||||
- Writing detection rules for a newly analyzed malware family based on unique byte patterns
|
||||
- Scanning file shares, endpoints, or memory dumps for indicators of a specific threat
|
||||
- Building automated triage pipelines that classify samples before manual analysis
|
||||
- Hunting for variants of a known threat across an enterprise using YARA scans
|
||||
|
||||
**Do not use** as the sole analysis method; YARA triage identifies known patterns but does not reveal new or unknown malware behaviors.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- YARA 4.x installed (`apt install yara` or `pip install yara-python`)
|
||||
- YARA rule repositories (YARA-Rules, awesome-yara, Malpedia rules, Florian Roth's signature-base)
|
||||
- Python 3.8+ with `yara-python` for scripted scanning
|
||||
- Sample collection organized in a directory structure for batch scanning
|
||||
- Understanding of PE file format, hex patterns, and regular expressions for rule writing
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Scan Samples with Existing Rule Sets
|
||||
|
||||
Apply community and commercial YARA rules to classify samples:
|
||||
|
||||
```bash
|
||||
# Scan a single file
|
||||
yara -s malware_rules.yar suspect.exe
|
||||
|
||||
# Scan a directory of samples
|
||||
yara -r malware_rules.yar /path/to/samples/
|
||||
|
||||
# Scan with multiple rule files
|
||||
yara -r rules/apt_rules.yar rules/ransomware_rules.yar rules/trojan_rules.yar suspect.exe
|
||||
|
||||
# Scan with timeout (prevent hanging on large files)
|
||||
yara -t 30 malware_rules.yar suspect.exe
|
||||
|
||||
# Scan and show matching strings
|
||||
yara -s -r malware_rules.yar suspect.exe
|
||||
|
||||
# Scan with compiled rules (faster for repeated scans)
|
||||
yarac malware_rules.yar compiled_rules.yarc
|
||||
yara compiled_rules.yarc suspect.exe
|
||||
```
|
||||
|
||||
```bash
|
||||
# Download community rule sets
|
||||
git clone https://github.com/Yara-Rules/rules.git yara-community-rules
|
||||
git clone https://github.com/Neo23x0/signature-base.git signature-base
|
||||
|
||||
# Scan with signature-base
|
||||
yara -r signature-base/yara/*.yar suspect.exe
|
||||
```
|
||||
|
||||
### Step 2: Write Rules for Unique String Patterns
|
||||
|
||||
Create YARA rules based on strings extracted during malware analysis:
|
||||
|
||||
```
|
||||
rule MalwareX_Strings {
|
||||
meta:
|
||||
description = "Detects MalwareX based on unique strings"
|
||||
author = "analyst"
|
||||
date = "2025-09-15"
|
||||
reference = "Internal Analysis Report #1547"
|
||||
hash = "e3b0c44298fc1c149afbf4c8996fb924"
|
||||
tlp = "WHITE"
|
||||
|
||||
strings:
|
||||
// C2 URL pattern
|
||||
$url1 = "/gate.php?id=" ascii
|
||||
$url2 = "/panel/connect.php" ascii
|
||||
|
||||
// Unique mutex name
|
||||
$mutex = "Global\\CryptLocker_2025" ascii wide
|
||||
|
||||
// User-Agent string
|
||||
$ua = "Mozilla/5.0 (compatible; MSIE 10.0)" ascii
|
||||
|
||||
// Registry persistence path
|
||||
$reg = "Software\\Microsoft\\Windows\\CurrentVersion\\Run\\WindowsUpdate" ascii
|
||||
|
||||
// Campaign identifier
|
||||
$campaign = "campaign_2025_q3" ascii
|
||||
|
||||
condition:
|
||||
uint16(0) == 0x5A4D and // PE file (MZ header)
|
||||
filesize < 500KB and // Size constraint
|
||||
($url1 or $url2) and // At least one C2 URL
|
||||
($mutex or $campaign) and // Campaign identifier
|
||||
$ua // Specific User-Agent
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Write Rules for Byte Patterns
|
||||
|
||||
Create rules matching specific code sequences:
|
||||
|
||||
```
|
||||
rule MalwareX_Decryptor {
|
||||
meta:
|
||||
description = "Detects MalwareX XOR decryption routine"
|
||||
author = "analyst"
|
||||
date = "2025-09-15"
|
||||
|
||||
strings:
|
||||
// XOR decryption loop (x86 assembly)
|
||||
// mov al, [esi+ecx]
|
||||
// xor al, [edi+ecx]
|
||||
// mov [esi+ecx], al
|
||||
// inc ecx
|
||||
// cmp ecx, edx
|
||||
// jl loop
|
||||
$xor_loop = { 8A 04 0E 32 04 0F 88 04 0E 41 3B CA 7C F3 }
|
||||
|
||||
// RC4 KSA initialization (256-byte loop)
|
||||
$rc4_ksa = { 33 C0 88 04 ?8 40 3D 00 01 00 00 7? }
|
||||
|
||||
// Embedded RSA public key marker
|
||||
$rsa_key = { 06 02 00 00 00 A4 00 00 52 53 41 31 } // PUBLICKEYBLOB
|
||||
|
||||
condition:
|
||||
uint16(0) == 0x5A4D and
|
||||
($xor_loop or $rc4_ksa) and
|
||||
$rsa_key
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Write Rules with PE Module
|
||||
|
||||
Leverage YARA's PE module for structural detection:
|
||||
|
||||
```
|
||||
import "pe"
|
||||
import "hash"
|
||||
import "math"
|
||||
|
||||
rule MalwareX_PE_Characteristics {
|
||||
meta:
|
||||
description = "Detects MalwareX by PE structure and imports"
|
||||
author = "analyst"
|
||||
|
||||
condition:
|
||||
pe.is_pe and
|
||||
|
||||
// Compiled within specific timeframe
|
||||
pe.timestamp > 1693526400 and // After 2023-09-01
|
||||
pe.timestamp < 1727740800 and // Before 2024-10-01
|
||||
|
||||
// Specific import hash
|
||||
pe.imphash() == "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" or
|
||||
|
||||
// Suspicious import combination
|
||||
(
|
||||
pe.imports("kernel32.dll", "VirtualAllocEx") and
|
||||
pe.imports("kernel32.dll", "WriteProcessMemory") and
|
||||
pe.imports("kernel32.dll", "CreateRemoteThread") and
|
||||
pe.imports("wininet.dll", "InternetOpenA")
|
||||
) or
|
||||
|
||||
// High entropy .text section (packed)
|
||||
(
|
||||
for any section in pe.sections : (
|
||||
section.name == ".text" and
|
||||
math.entropy(section.raw_data_offset, section.raw_data_size) > 7.0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
rule MalwareX_Rich_Header {
|
||||
meta:
|
||||
description = "Detects MalwareX by Rich header hash"
|
||||
|
||||
condition:
|
||||
pe.is_pe and
|
||||
hash.md5(pe.rich_signature.clear_data) == "abc123def456abc123def456abc123de"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Batch Triage with Python
|
||||
|
||||
Automate scanning of sample collections:
|
||||
|
||||
```python
|
||||
import yara
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
# Compile all rule files
|
||||
rule_files = {
|
||||
"apt": "rules/apt_rules.yar",
|
||||
"ransomware": "rules/ransomware_rules.yar",
|
||||
"trojan": "rules/trojan_rules.yar",
|
||||
"custom": "rules/custom_rules.yar",
|
||||
}
|
||||
rules = yara.compile(filepaths=rule_files)
|
||||
|
||||
# Scan sample directory
|
||||
results = []
|
||||
sample_dir = "/path/to/samples"
|
||||
|
||||
for filename in os.listdir(sample_dir):
|
||||
filepath = os.path.join(sample_dir, filename)
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
sha256 = hashlib.sha256(data).hexdigest()
|
||||
|
||||
matches = rules.match(filepath)
|
||||
|
||||
result = {
|
||||
"filename": filename,
|
||||
"sha256": sha256,
|
||||
"size": len(data),
|
||||
"matches": [],
|
||||
"classification": "UNKNOWN",
|
||||
}
|
||||
|
||||
for match in matches:
|
||||
result["matches"].append({
|
||||
"rule": match.rule,
|
||||
"namespace": match.namespace,
|
||||
"tags": match.tags,
|
||||
"strings": [(hex(s[0]), s[1], s[2].decode("utf-8", errors="replace")[:100])
|
||||
for s in match.strings] if match.strings else []
|
||||
})
|
||||
|
||||
if result["matches"]:
|
||||
result["classification"] = result["matches"][0]["namespace"].upper()
|
||||
|
||||
results.append(result)
|
||||
|
||||
# Summary
|
||||
classified = sum(1 for r in results if r["classification"] != "UNKNOWN")
|
||||
print(f"Scanned: {len(results)} samples")
|
||||
print(f"Classified: {classified} ({classified/len(results)*100:.1f}%)")
|
||||
print(f"Unknown: {len(results)-classified}")
|
||||
|
||||
# Export results
|
||||
with open("triage_results.json", "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
```
|
||||
|
||||
### Step 6: Validate and Optimize Rules
|
||||
|
||||
Test rules for false positives and performance:
|
||||
|
||||
```bash
|
||||
# Test rule syntax
|
||||
yara -C custom_rules.yar
|
||||
|
||||
# Scan known-clean directory to check false positives
|
||||
yara -r custom_rules.yar /path/to/clean_files/ > false_positives.txt
|
||||
wc -l false_positives.txt
|
||||
|
||||
# Benchmark rule performance
|
||||
time yara -r custom_rules.yar /path/to/large_sample_collection/
|
||||
|
||||
# Profile individual rule performance
|
||||
yara -p custom_rules.yar suspect.exe
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **YARA Rule** | Pattern matching rule defining strings, byte sequences, and conditions that identify a specific file or malware family |
|
||||
| **Condition** | Boolean expression combining string matches, file properties, and module functions to determine if a rule matches |
|
||||
| **Hex String** | Byte pattern with optional wildcards (??) and jumps ([N-M]) for matching machine code or binary data |
|
||||
| **PE Module** | YARA module providing access to PE file properties (imports, sections, timestamps, resources) for structural matching |
|
||||
| **Imphash** | MD5 hash of a PE file's import table; samples from the same family often share import hashes |
|
||||
| **Rich Header** | Undocumented PE structure containing compiler/linker metadata; consistent within malware build environments |
|
||||
| **YARA-C** | Compiled YARA rule format enabling faster scanning by pre-compiling rules for repeated use |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **YARA**: Pattern matching engine for identifying and classifying malware based on text, hex, and structural patterns
|
||||
- **yara-python**: Python bindings for YARA enabling scripted scanning, rule compilation, and integration with analysis pipelines
|
||||
- **yarGen**: Automatic YARA rule generator that creates rules from malware samples by identifying unique strings and opcodes
|
||||
- **YARA-Rules (GitHub)**: Community-maintained repository of YARA rules covering malware families, exploits, and suspicious indicators
|
||||
- **Malpedia YARA**: Curated YARA rules from the Malpedia malware encyclopedia with high-quality family-specific rules
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Creating Detection Rules for a New Malware Family
|
||||
|
||||
**Context**: Reverse engineering of a new malware sample has identified unique strings, byte patterns, and PE characteristics. YARA rules are needed for enterprise-wide hunting and ongoing detection.
|
||||
|
||||
**Approach**:
|
||||
1. Extract unique strings from the unpacked binary (C2 URLs, mutex names, registry paths)
|
||||
2. Identify unique byte sequences from the encryption routine or C2 protocol (from Ghidra analysis)
|
||||
3. Record PE characteristics (imphash, Rich header hash, section names, compilation timestamp range)
|
||||
4. Write a YARA rule combining string, byte pattern, and PE module conditions
|
||||
5. Test against the known malware samples to confirm true positive detection
|
||||
6. Test against a clean file corpus (Windows system files, common applications) to verify zero false positives
|
||||
7. Deploy to enterprise scanning infrastructure and threat intelligence platform
|
||||
|
||||
**Pitfalls**:
|
||||
- Writing rules too specific to a single sample (will not detect variants with minor changes)
|
||||
- Writing rules too generic (matching legitimate software, causing false positives)
|
||||
- Using strings that appear in common libraries or frameworks (e.g., OpenSSL strings)
|
||||
- Not testing on a sufficiently large clean corpus before deployment
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
YARA TRIAGE RESULTS
|
||||
=====================
|
||||
Scan Date: 2025-09-15
|
||||
Rule Sets: apt_rules (847 rules), ransomware_rules (312 rules),
|
||||
trojan_rules (1,204 rules), custom_rules (45 rules)
|
||||
Samples Scanned: 2,500
|
||||
Processing Time: 47 seconds
|
||||
|
||||
CLASSIFICATION SUMMARY
|
||||
APT: 12 samples (0.5%)
|
||||
Ransomware: 187 samples (7.5%)
|
||||
Trojan: 423 samples (16.9%)
|
||||
Unknown: 1,878 samples (75.1%)
|
||||
|
||||
TOP MATCHING RULES
|
||||
Rule Matches Family
|
||||
MalwareX_C2_Beacon 45 MalwareX
|
||||
LockBit3_Ransom_Note 38 LockBit 3.0
|
||||
Emotet_Epoch5_Loader 32 Emotet
|
||||
CobaltStrike_Beacon_Config 28 Cobalt Strike
|
||||
QakBot_DLL_Loader 25 QakBot
|
||||
|
||||
SAMPLE DETAIL
|
||||
File: suspect.exe
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
Matches:
|
||||
[1] MalwareX_Strings (custom)
|
||||
- $url1 at 0x4A20: "/gate.php?id="
|
||||
- $mutex at 0x5100: "Global\\CryptLocker_2025"
|
||||
[2] MalwareX_Decryptor (custom)
|
||||
- $xor_loop at 0x401200: { 8A 04 0E 32 04 0F ... }
|
||||
[3] MalwareX_PE_Characteristics (custom)
|
||||
- PE import combination matched
|
||||
Classification: MALWAREX (HIGH CONFIDENCE)
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
# API Reference: Malware Triage with YARA
|
||||
|
||||
## yara-python API
|
||||
|
||||
```python
|
||||
import yara
|
||||
|
||||
# Compile from files
|
||||
rules = yara.compile(filepaths={"ns1": "rules.yar"})
|
||||
|
||||
# Compile from string
|
||||
rules = yara.compile(source='rule test { condition: true }')
|
||||
|
||||
# Scan file
|
||||
matches = rules.match("/path/to/sample")
|
||||
|
||||
# Scan data
|
||||
matches = rules.match(data=open("sample", "rb").read())
|
||||
```
|
||||
|
||||
## Match Object Attributes
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `match.rule` | str | Name of the matching rule |
|
||||
| `match.namespace` | str | Rule file namespace |
|
||||
| `match.tags` | list | Tags from the rule definition |
|
||||
| `match.meta` | dict | Meta fields (author, description, hash) |
|
||||
| `match.strings` | list | Matched strings: (offset, identifier, data) |
|
||||
|
||||
## YARA CLI
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `yara rules.yar sample.exe` | Scan file against rules |
|
||||
| `yara -r rules.yar /dir/` | Recursive directory scan |
|
||||
| `yara -s rules.yar sample.exe` | Show matching strings |
|
||||
| `yarac rules.yar compiled.yarc` | Compile rules for faster loading |
|
||||
| `yara -C rules.yar` | Check rule syntax |
|
||||
|
||||
## Python Libraries
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `yara-python` | >=4.3 | YARA rule compilation and scanning |
|
||||
| `hashlib` | stdlib | Sample hashing (SHA-256, MD5) |
|
||||
|
||||
## References
|
||||
|
||||
- YARA documentation: https://yara.readthedocs.io/
|
||||
- yara-python: https://github.com/VirusTotal/yara-python
|
||||
- YARA-Rules community: https://github.com/Yara-Rules/rules
|
||||
- Signature-base: https://github.com/Neo23x0/signature-base
|
||||
- yarGen rule generator: https://github.com/Neo23x0/yarGen
|
||||
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for performing malware triage with YARA.
|
||||
|
||||
Compiles and applies YARA rules to classify malware samples,
|
||||
perform batch scanning, and generate triage reports.
|
||||
"""
|
||||
|
||||
import yara
|
||||
import sys
|
||||
import json
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class YaraTriageAgent:
|
||||
"""Batch malware triage and classification using YARA rules."""
|
||||
|
||||
def __init__(self, output_dir):
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.rules = None
|
||||
self.results = []
|
||||
|
||||
def compile_rules(self, rule_paths):
|
||||
"""Compile YARA rules from file paths or directories."""
|
||||
filepaths = {}
|
||||
for path in rule_paths:
|
||||
p = Path(path)
|
||||
if p.is_file() and p.suffix in (".yar", ".yara"):
|
||||
filepaths[p.stem] = str(p)
|
||||
elif p.is_dir():
|
||||
for rule_file in p.rglob("*.yar"):
|
||||
filepaths[rule_file.stem] = str(rule_file)
|
||||
for rule_file in p.rglob("*.yara"):
|
||||
filepaths[rule_file.stem] = str(rule_file)
|
||||
if not filepaths:
|
||||
raise ValueError(f"No YARA rule files found in: {rule_paths}")
|
||||
self.rules = yara.compile(filepaths=filepaths)
|
||||
return len(filepaths)
|
||||
|
||||
def scan_file(self, filepath):
|
||||
"""Scan a single file against compiled YARA rules."""
|
||||
filepath = Path(filepath)
|
||||
if not filepath.is_file():
|
||||
return None
|
||||
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
sha256 = hashlib.sha256(data).hexdigest()
|
||||
md5 = hashlib.md5(data).hexdigest()
|
||||
matches = self.rules.match(data=data)
|
||||
|
||||
result = {
|
||||
"filename": filepath.name,
|
||||
"path": str(filepath),
|
||||
"sha256": sha256,
|
||||
"md5": md5,
|
||||
"size": len(data),
|
||||
"matches": [],
|
||||
"match_count": len(matches),
|
||||
"classification": "UNKNOWN",
|
||||
}
|
||||
|
||||
for match in matches:
|
||||
match_info = {
|
||||
"rule": match.rule,
|
||||
"namespace": match.namespace,
|
||||
"tags": match.tags,
|
||||
"meta": match.meta,
|
||||
"strings": [],
|
||||
}
|
||||
if match.strings:
|
||||
for string_match in match.strings[:10]:
|
||||
match_info["strings"].append({
|
||||
"identifier": string_match[1],
|
||||
"offset": hex(string_match[0]),
|
||||
"data": string_match[2].decode("utf-8", errors="replace")[:80],
|
||||
})
|
||||
result["matches"].append(match_info)
|
||||
|
||||
if result["matches"]:
|
||||
result["classification"] = result["matches"][0].get("namespace", "DETECTED").upper()
|
||||
|
||||
return result
|
||||
|
||||
def scan_directory(self, sample_dir, recursive=True):
|
||||
"""Scan all files in a directory."""
|
||||
sample_path = Path(sample_dir)
|
||||
glob_fn = sample_path.rglob if recursive else sample_path.glob
|
||||
|
||||
for filepath in glob_fn("*"):
|
||||
if filepath.is_file() and filepath.stat().st_size > 0:
|
||||
result = self.scan_file(filepath)
|
||||
if result:
|
||||
self.results.append(result)
|
||||
|
||||
return self.results
|
||||
|
||||
def get_classification_summary(self):
|
||||
"""Summarize scan results by classification."""
|
||||
summary = defaultdict(int)
|
||||
for result in self.results:
|
||||
summary[result["classification"]] += 1
|
||||
return dict(sorted(summary.items(), key=lambda x: x[1], reverse=True))
|
||||
|
||||
def get_top_rules(self, limit=20):
|
||||
"""Get most frequently matching rules."""
|
||||
rule_counts = defaultdict(int)
|
||||
for result in self.results:
|
||||
for match in result["matches"]:
|
||||
rule_counts[match["rule"]] += 1
|
||||
return dict(sorted(rule_counts.items(), key=lambda x: x[1], reverse=True)[:limit])
|
||||
|
||||
def generate_report(self):
|
||||
"""Generate comprehensive triage report."""
|
||||
classified = [r for r in self.results if r["classification"] != "UNKNOWN"]
|
||||
unknown = [r for r in self.results if r["classification"] == "UNKNOWN"]
|
||||
|
||||
report = {
|
||||
"scan_date": datetime.utcnow().isoformat(),
|
||||
"total_scanned": len(self.results),
|
||||
"classified": len(classified),
|
||||
"unknown": len(unknown),
|
||||
"classification_rate": round(
|
||||
len(classified) / max(len(self.results), 1) * 100, 1
|
||||
),
|
||||
"classification_summary": self.get_classification_summary(),
|
||||
"top_matching_rules": self.get_top_rules(),
|
||||
"detected_samples": [
|
||||
{
|
||||
"filename": r["filename"],
|
||||
"sha256": r["sha256"],
|
||||
"classification": r["classification"],
|
||||
"rules_matched": [m["rule"] for m in r["matches"]],
|
||||
}
|
||||
for r in classified
|
||||
],
|
||||
}
|
||||
|
||||
report_path = self.output_dir / "yara_triage_report.json"
|
||||
with open(report_path, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
print(f"YARA Triage Results")
|
||||
print(f"={'=' * 40}")
|
||||
print(f"Scanned: {report['total_scanned']}")
|
||||
print(f"Classified: {report['classified']} ({report['classification_rate']}%)")
|
||||
print(f"Unknown: {report['unknown']}")
|
||||
print(f"\nClassification Summary:")
|
||||
for cls, count in report["classification_summary"].items():
|
||||
print(f" {cls}: {count}")
|
||||
print(f"\nTop Rules:")
|
||||
for rule, count in list(report["top_matching_rules"].items())[:10]:
|
||||
print(f" {rule}: {count} matches")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: agent.py <rules_path> <samples_dir> [output_dir]")
|
||||
print(" rules_path: YARA rule file or directory of .yar files")
|
||||
print(" samples_dir: Directory of files to scan")
|
||||
sys.exit(1)
|
||||
|
||||
rules_path = sys.argv[1]
|
||||
samples_dir = sys.argv[2]
|
||||
output_dir = sys.argv[3] if len(sys.argv) > 3 else "./triage_output"
|
||||
|
||||
agent = YaraTriageAgent(output_dir)
|
||||
rule_count = agent.compile_rules([rules_path])
|
||||
print(f"Compiled {rule_count} rule files")
|
||||
|
||||
agent.scan_directory(samples_dir)
|
||||
agent.generate_report()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user