mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-30 07:56:52 +03:00
Add 5 new cybersecurity skills: AFL++ fuzzing, Sysmon scheduled task detection, ModSecurity WAF logging, MITRE Navigator TTP analysis, PowerShell script block hunting
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Mahipal
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: hunting-for-anomalous-powershell-execution
|
||||
description: >
|
||||
Hunt for malicious PowerShell activity by analyzing Script Block Logging (Event 4104),
|
||||
Module Logging (Event 4103), and process creation events. The analyst parses Windows
|
||||
Event Log EVTX files to detect obfuscated commands, AMSI bypass attempts, encoded
|
||||
payloads, credential dumping keywords, and suspicious download cradles. Activates for
|
||||
requests involving PowerShell threat hunting, script block analysis, encoded command
|
||||
detection, or AMSI bypass identification.
|
||||
domain: cybersecurity
|
||||
subdomain: threat-hunting
|
||||
tags: [powershell, script-block-logging, event-4104, amsi, threat-hunting, evtx, obfuscation]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
# Hunting for Anomalous PowerShell Execution
|
||||
|
||||
## Overview
|
||||
|
||||
PowerShell Script Block Logging (Event ID 4104) records the full deobfuscated script text
|
||||
executed on a Windows endpoint, making it the primary data source for hunting malicious
|
||||
PowerShell. Combined with Module Logging (4103) and process creation events, analysts can
|
||||
detect encoded commands, AMSI bypass patterns, download cradles, credential theft tools,
|
||||
and fileless attack techniques even when the attacker uses obfuscation layers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Windows Event Log exports (.evtx) from Microsoft-Windows-PowerShell/Operational
|
||||
- Python 3.8+ with python-evtx and lxml libraries
|
||||
- Script Block Logging enabled via Group Policy
|
||||
- Understanding of common PowerShell attack techniques
|
||||
|
||||
## Steps
|
||||
|
||||
1. Parse EVTX files extracting Event 4104 script block text and metadata
|
||||
2. Reassemble multi-part script blocks using ScriptBlock ID correlation
|
||||
3. Scan script text for AMSI bypass indicators and obfuscation patterns
|
||||
4. Detect encoded command execution and base64 payloads
|
||||
5. Identify download cradles, credential dumping, and lateral movement commands
|
||||
6. Score and prioritize findings by threat severity
|
||||
|
||||
## Expected Output
|
||||
|
||||
```json
|
||||
{
|
||||
"total_events": 1247,
|
||||
"suspicious_events": 23,
|
||||
"amsi_bypass_attempts": 2,
|
||||
"encoded_commands": 8,
|
||||
"download_cradles": 5,
|
||||
"credential_access": 3
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,106 @@
|
||||
# Hunting for Anomalous PowerShell Execution — API Reference
|
||||
|
||||
## Windows Event Log IDs
|
||||
|
||||
| Event ID | Log Source | Description |
|
||||
|----------|-----------|-------------|
|
||||
| 4104 | Microsoft-Windows-PowerShell/Operational | Script Block Logging — full deobfuscated script text |
|
||||
| 4103 | Microsoft-Windows-PowerShell/Operational | Module Logging — pipeline execution details |
|
||||
| 4688 | Security | Process Creation with command line auditing |
|
||||
| 800 | Windows PowerShell | Pipeline execution (classic log) |
|
||||
|
||||
## Event 4104 XML Fields
|
||||
|
||||
| Field | Path | Description |
|
||||
|-------|------|-------------|
|
||||
| ScriptBlockText | EventData/Data[@Name='ScriptBlockText'] | Full script block content |
|
||||
| ScriptBlockId | EventData/Data[@Name='ScriptBlockId'] | GUID linking multi-part blocks |
|
||||
| MessageNumber | EventData/Data[@Name='MessageNumber'] | Part number for split blocks |
|
||||
| MessageTotal | EventData/Data[@Name='MessageTotal'] | Total parts in split block |
|
||||
| Path | EventData/Data[@Name='Path'] | Script file path (if applicable) |
|
||||
|
||||
## AMSI Bypass Indicators
|
||||
|
||||
| Indicator | Context |
|
||||
|-----------|---------|
|
||||
| `System.Management.Automation.AmsiUtils` | Reflection access to AMSI internals |
|
||||
| `amsiInitFailed` | Setting AMSI init flag to bypass scanning |
|
||||
| `AmsiScanBuffer` | Patching the scan buffer function |
|
||||
| `amsi.dll` | Direct DLL manipulation |
|
||||
| `VirtualProtect` | Memory protection change for AMSI patching |
|
||||
| `Marshal::Copy` | Overwriting AMSI function bytes in memory |
|
||||
|
||||
## Suspicious PowerShell Keywords
|
||||
|
||||
| Keyword | Category |
|
||||
|---------|----------|
|
||||
| `Invoke-Mimikatz` | Credential Dumping |
|
||||
| `Invoke-Kerberoast` | Credential Access |
|
||||
| `Invoke-ShellCode` | Code Injection |
|
||||
| `Invoke-ReflectivePEInjection` | Process Injection |
|
||||
| `PowerView` | Active Directory Enumeration |
|
||||
| `SharpHound` / `BloodHound` | AD Attack Path Mapping |
|
||||
| `Rubeus` | Kerberos Ticket Manipulation |
|
||||
| `Out-Minidump` | LSASS Memory Dumping |
|
||||
|
||||
## Download Cradle Patterns
|
||||
|
||||
| Pattern | Example |
|
||||
|---------|---------|
|
||||
| `Net.WebClient` | `(New-Object Net.WebClient).DownloadString(...)` |
|
||||
| `Invoke-WebRequest` | `IWR -Uri http://... -OutFile ...` |
|
||||
| `DownloadString` | `$wc.DownloadString('http://...')` |
|
||||
| `Start-BitsTransfer` | `Start-BitsTransfer -Source http://...` |
|
||||
| `Invoke-RestMethod` | `IRM http://... \| IEX` |
|
||||
|
||||
## Obfuscation Indicators
|
||||
|
||||
| Pattern | Description |
|
||||
|---------|-------------|
|
||||
| `-EncodedCommand` / `-enc` | Base64-encoded PowerShell command |
|
||||
| `IEX` / `Invoke-Expression` | Dynamic execution of string content |
|
||||
| `[Convert]::FromBase64String` | Base64 decoding in script |
|
||||
| `-join [char[]]` | Character array concatenation obfuscation |
|
||||
| `.Replace()` chaining | String substitution for keyword evasion |
|
||||
|
||||
## python-evtx Library Usage
|
||||
|
||||
```python
|
||||
import Evtx.Evtx as evtx
|
||||
from lxml import etree
|
||||
|
||||
with evtx.Evtx("PowerShell-Operational.evtx") as log:
|
||||
for record in log.records():
|
||||
xml = record.xml()
|
||||
root = etree.fromstring(xml.encode("utf-8"))
|
||||
# Extract EventID, EventData fields
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
# Hunt for suspicious PowerShell in EVTX file
|
||||
python agent.py --evtx /path/to/PowerShell-Operational.evtx
|
||||
|
||||
# Limit events parsed
|
||||
python agent.py --evtx logs.evtx --max-events 5000
|
||||
|
||||
# Save report to JSON
|
||||
python agent.py --evtx logs.evtx --output hunt_report.json
|
||||
```
|
||||
|
||||
## Group Policy Settings for Script Block Logging
|
||||
|
||||
```
|
||||
Computer Configuration > Administrative Templates > Windows Components
|
||||
> Windows PowerShell > Turn on PowerShell Script Block Logging
|
||||
-> Enabled
|
||||
-> Log script block invocation start / stop events: Checked
|
||||
```
|
||||
|
||||
## External References
|
||||
|
||||
- [Splunk: Hunting for Malicious PowerShell using Script Block Logging](https://www.splunk.com/en_us/blog/security/hunting-for-malicious-powershell-using-script-block-logging.html)
|
||||
- [block-parser: PowerShell Script Block Log Parser](https://github.com/matthewdunwoody/block-parser)
|
||||
- [Windows Forensic Artifacts: EVTX 4104](https://github.com/Psmths/windows-forensic-artifacts/blob/main/execution/evtx-4104-script-block-logging.md)
|
||||
- [Elastic: AMSI Bypass via PowerShell Detection Rule](https://www.elastic.co/docs/reference/security/prebuilt-rules/rules/windows/defense_evasion_amsi_bypass_powershell)
|
||||
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PowerShell Script Block Logging threat hunting agent."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
import base64
|
||||
import re
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import Evtx.Evtx as evtx
|
||||
from lxml import etree
|
||||
except ImportError:
|
||||
print("Install: pip install python-evtx lxml")
|
||||
sys.exit(1)
|
||||
|
||||
NS = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"}
|
||||
|
||||
AMSI_INDICATORS = [
|
||||
"amsiutils", "amsiinitfailed", "amsicontext", "amsisession",
|
||||
"amsiinitialize", "amsi.dll", "amsiScanBuffer",
|
||||
"System.Management.Automation.AmsiUtils",
|
||||
]
|
||||
|
||||
SUSPICIOUS_KEYWORDS = [
|
||||
"Invoke-Mimikatz", "Invoke-Kerberoast", "Invoke-ShellCode",
|
||||
"Invoke-ReflectivePEInjection", "Invoke-TokenManipulation",
|
||||
"Get-GPPPassword", "Get-Keystrokes", "Get-TimedScreenshot",
|
||||
"Out-Minidump", "Invoke-NinjaCopy", "Invoke-CredentialInjection",
|
||||
"Invoke-DllInjection", "Invoke-WMICommand", "PowerSploit",
|
||||
"Empire", "BloodHound", "Rubeus", "SharpHound",
|
||||
"Invoke-PSInject", "Invoke-RunAs", "PowerView",
|
||||
]
|
||||
|
||||
DOWNLOAD_PATTERNS = [
|
||||
r"Net\.WebClient", r"Invoke-WebRequest", r"wget\s", r"curl\s",
|
||||
r"DownloadString", r"DownloadFile", r"DownloadData",
|
||||
r"Start-BitsTransfer", r"Invoke-RestMethod",
|
||||
r"New-Object\s+IO\.MemoryStream",
|
||||
]
|
||||
|
||||
OBFUSCATION_PATTERNS = [
|
||||
r"-[Ee]nc(?:oded)?[Cc]ommand",
|
||||
r"\-e\s+[A-Za-z0-9+/=]{20,}",
|
||||
r"IEX\s*\(",
|
||||
r"Invoke-Expression",
|
||||
r"\[Convert\]::FromBase64String",
|
||||
r"\[System\.Text\.Encoding\]::",
|
||||
r"\.Replace\(['\"][^'\"]+['\"],\s*['\"][^'\"]+['\"]\)",
|
||||
r"-join\s*\[char\[\]\]",
|
||||
r"\$env:comspec",
|
||||
]
|
||||
|
||||
|
||||
def parse_evtx_4104(evtx_path, max_events=10000):
|
||||
"""Parse Event 4104 script block logging entries from EVTX."""
|
||||
events = []
|
||||
count = 0
|
||||
with evtx.Evtx(evtx_path) as log:
|
||||
for record in log.records():
|
||||
if count >= max_events:
|
||||
break
|
||||
xml = record.xml()
|
||||
root = etree.fromstring(xml.encode("utf-8"))
|
||||
event_id_el = root.find(".//e:System/e:EventID", NS)
|
||||
if event_id_el is None or event_id_el.text != "4104":
|
||||
continue
|
||||
count += 1
|
||||
time_el = root.find(".//e:System/e:TimeCreated", NS)
|
||||
timestamp = time_el.get("SystemTime", "") if time_el is not None else ""
|
||||
data = {}
|
||||
for el in root.findall(".//e:EventData/e:Data", NS):
|
||||
name = el.get("Name", "")
|
||||
data[name] = el.text or ""
|
||||
events.append({
|
||||
"timestamp": timestamp,
|
||||
"script_block_id": data.get("ScriptBlockId", ""),
|
||||
"script_block_text": data.get("ScriptBlockText", ""),
|
||||
"message_number": data.get("MessageNumber", "1"),
|
||||
"message_total": data.get("MessageTotal", "1"),
|
||||
"path": data.get("Path", ""),
|
||||
})
|
||||
return events
|
||||
|
||||
|
||||
def reassemble_script_blocks(events):
|
||||
"""Reassemble multi-part script blocks by ScriptBlockId."""
|
||||
blocks = defaultdict(list)
|
||||
for ev in events:
|
||||
sb_id = ev.get("script_block_id", "")
|
||||
if sb_id:
|
||||
blocks[sb_id].append(ev)
|
||||
assembled = []
|
||||
for sb_id, parts in blocks.items():
|
||||
parts.sort(key=lambda x: int(x.get("message_number", "1")))
|
||||
full_text = "".join(p.get("script_block_text", "") for p in parts)
|
||||
assembled.append({
|
||||
"script_block_id": sb_id,
|
||||
"timestamp": parts[0].get("timestamp", ""),
|
||||
"path": parts[0].get("path", ""),
|
||||
"parts": len(parts),
|
||||
"full_text": full_text,
|
||||
})
|
||||
return assembled
|
||||
|
||||
|
||||
def detect_amsi_bypass(script_text):
|
||||
"""Check script text for AMSI bypass indicators."""
|
||||
findings = []
|
||||
lower = script_text.lower()
|
||||
for indicator in AMSI_INDICATORS:
|
||||
if indicator.lower() in lower:
|
||||
findings.append({"type": "amsi_bypass", "indicator": indicator})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_suspicious_keywords(script_text):
|
||||
"""Check for known offensive tool keywords."""
|
||||
findings = []
|
||||
for kw in SUSPICIOUS_KEYWORDS:
|
||||
if kw.lower() in script_text.lower():
|
||||
findings.append({"type": "credential_or_offensive_tool", "keyword": kw})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_download_cradles(script_text):
|
||||
"""Detect download cradle patterns in script text."""
|
||||
findings = []
|
||||
for pattern in DOWNLOAD_PATTERNS:
|
||||
if re.search(pattern, script_text, re.IGNORECASE):
|
||||
findings.append({"type": "download_cradle", "pattern": pattern})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_obfuscation(script_text):
|
||||
"""Detect obfuscation and encoded command patterns."""
|
||||
findings = []
|
||||
for pattern in OBFUSCATION_PATTERNS:
|
||||
if re.search(pattern, script_text, re.IGNORECASE):
|
||||
findings.append({"type": "obfuscation", "pattern": pattern})
|
||||
b64_match = re.search(r"[A-Za-z0-9+/=]{40,}", script_text)
|
||||
if b64_match:
|
||||
try:
|
||||
decoded = base64.b64decode(b64_match.group()).decode("utf-16-le", errors="ignore")
|
||||
if any(c.isalpha() for c in decoded[:20]):
|
||||
findings.append({
|
||||
"type": "encoded_payload",
|
||||
"decoded_preview": decoded[:200],
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return findings
|
||||
|
||||
|
||||
def hunt_scripts(assembled_blocks):
|
||||
"""Run all detection checks on assembled script blocks."""
|
||||
results = []
|
||||
for block in assembled_blocks:
|
||||
text = block.get("full_text", "")
|
||||
if not text.strip():
|
||||
continue
|
||||
findings = []
|
||||
findings.extend(detect_amsi_bypass(text))
|
||||
findings.extend(detect_suspicious_keywords(text))
|
||||
findings.extend(detect_download_cradles(text))
|
||||
findings.extend(detect_obfuscation(text))
|
||||
if findings:
|
||||
results.append({
|
||||
"script_block_id": block["script_block_id"],
|
||||
"timestamp": block["timestamp"],
|
||||
"path": block["path"],
|
||||
"text_preview": text[:300],
|
||||
"findings": findings,
|
||||
"severity": "high" if any(
|
||||
f["type"] in ("amsi_bypass", "credential_or_offensive_tool")
|
||||
for f in findings
|
||||
) else "medium",
|
||||
})
|
||||
return results
|
||||
|
||||
|
||||
def run_audit(args):
|
||||
"""Execute PowerShell script block hunting."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f" POWERSHELL SCRIPT BLOCK HUNTING")
|
||||
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
report = {}
|
||||
events = parse_evtx_4104(args.evtx, args.max_events)
|
||||
report["total_4104_events"] = len(events)
|
||||
print(f"Parsed {len(events)} Event 4104 records\n")
|
||||
|
||||
blocks = reassemble_script_blocks(events)
|
||||
report["unique_script_blocks"] = len(blocks)
|
||||
print(f"Reassembled {len(blocks)} unique script blocks\n")
|
||||
|
||||
results = hunt_scripts(blocks)
|
||||
report["suspicious_blocks"] = len(results)
|
||||
report["findings"] = results
|
||||
|
||||
amsi = sum(1 for r in results if any(f["type"] == "amsi_bypass" for f in r["findings"]))
|
||||
cred = sum(1 for r in results if any(f["type"] == "credential_or_offensive_tool" for f in r["findings"]))
|
||||
dl = sum(1 for r in results if any(f["type"] == "download_cradle" for f in r["findings"]))
|
||||
obf = sum(1 for r in results if any(f["type"] == "obfuscation" for f in r["findings"]))
|
||||
report["summary"] = {
|
||||
"amsi_bypass_attempts": amsi,
|
||||
"credential_access": cred,
|
||||
"download_cradles": dl,
|
||||
"obfuscation_detected": obf,
|
||||
}
|
||||
|
||||
print(f"--- HUNT RESULTS ---")
|
||||
print(f" AMSI bypass attempts: {amsi}")
|
||||
print(f" Credential/offensive tools: {cred}")
|
||||
print(f" Download cradles: {dl}")
|
||||
print(f" Obfuscation detected: {obf}")
|
||||
print(f"\n--- HIGH SEVERITY ---")
|
||||
for r in results[:15]:
|
||||
if r["severity"] == "high":
|
||||
print(f" [{r['timestamp']}] {r['script_block_id']}")
|
||||
for f in r["findings"]:
|
||||
print(f" {f['type']}: {f.get('keyword', f.get('indicator', ''))}")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="PowerShell Script Block Hunting Agent")
|
||||
parser.add_argument("--evtx", required=True,
|
||||
help="Path to PowerShell Operational .evtx file")
|
||||
parser.add_argument("--max-events", type=int, default=10000,
|
||||
help="Max events to parse (default: 10000)")
|
||||
parser.add_argument("--output", help="Save report to JSON file")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = run_audit(args)
|
||||
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()
|
||||
Reference in New Issue
Block a user