Shift UI terminology toward operator-flavored, observation-based language
Reframes verdict/score language across templates, JS renderers, MCP and GrumpyCats client docs so findings read as feedback on the operator's own payload rather than threat judgments. Risk Score becomes Detection Score, Risk Factors becomes Triggering Indicators, Suspicious Imports becomes Sensitive Imports, CheckPlz "Threat detected" becomes "Signature triggered", HolyGrail "Dangerous Imports" becomes "Critical Imports". Dynamic-analysis warning modal on the static results page now accepts command-line arguments (pre-populated from last run) and persists them via localStorage, matching the upload-page flow.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""LitterBox MCP server.
|
||||
|
||||
Exposes the LitterBox malware-analysis sandbox to MCP clients (Claude Desktop,
|
||||
Exposes the LitterBox payload-analysis sandbox to MCP clients (Claude Desktop,
|
||||
Cursor, etc.) so an LLM can drive uploads, analysis, result retrieval, and
|
||||
report generation, plus a small set of OPSEC-review prompts.
|
||||
|
||||
@@ -34,7 +34,7 @@ logger = logging.getLogger("litterbox-mcp")
|
||||
mcp = FastMCP(
|
||||
name="LitterBox",
|
||||
instructions=(
|
||||
"Tools for the LitterBox malware-analysis sandbox: upload payloads / drivers, "
|
||||
"Tools for the LitterBox payload-analysis sandbox: upload payloads / drivers, "
|
||||
"run static or dynamic analysis, retrieve results, and generate reports. "
|
||||
"Use the prompts for OPSEC review of analysis output. "
|
||||
"Tool exceptions are surfaced to the client by FastMCP automatically — "
|
||||
@@ -128,7 +128,7 @@ async def validate_pid(
|
||||
|
||||
@mcp.tool()
|
||||
async def get_file_info(file_hash: str) -> dict:
|
||||
"""File metadata: type, size, hashes, entropy, PE structure, suspicious imports."""
|
||||
"""File metadata: type, size, hashes, entropy, PE structure, sensitive imports."""
|
||||
return await _call(client.get_file_info, file_hash)
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ async def get_holygrail_results(file_hash: str) -> dict:
|
||||
|
||||
@mcp.tool()
|
||||
async def get_risk_assessment(target: str) -> dict:
|
||||
"""Computed risk: numerical score, level (Low / Medium / High / Critical), contributing factors."""
|
||||
"""Computed detection assessment: numerical score, level (Low / Medium / High / Critical), triggering indicators."""
|
||||
return await _call(client.get_risk_assessment, target)
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ async def create_fuzzy_database(
|
||||
|
||||
@mcp.tool()
|
||||
async def list_payloads() -> dict:
|
||||
"""List every analyzed payload, driver, and process in the sandbox with risk summary."""
|
||||
"""List every analyzed payload, driver, and process in the sandbox with detection summary."""
|
||||
return await _call(client.get_files_summary)
|
||||
|
||||
|
||||
@@ -261,9 +261,9 @@ def detection_summary(file_hash: str) -> str:
|
||||
return f"""Load `get_comprehensive_results("{file_hash}")` and summarize:
|
||||
|
||||
1. **YARA matches** — for each rule that fired, name it and the string / pattern that triggered it.
|
||||
2. **Memory anomalies** — for each PE-Sieve / Moneta / Patriot / HSB finding, identify the technique it maps to (e.g. private RWX → manual injection, modified PE header → unhooking).
|
||||
3. **Behavioral telemetry** — flag anything in the RedEdr timeline atypical for a benign process (suspicious DLL loads, child processes, IOCTL traffic).
|
||||
4. **Static red flags** — entropy, packing, suspicious imports, attribution-bearing strings.
|
||||
2. **Memory anomalies** — for each PE-Sieve / Moneta / Patriot / HSB indicator, identify the technique it maps to (e.g. private RWX → manual injection, modified PE header → unhooking).
|
||||
3. **Behavioral telemetry** — flag anything in the RedEdr timeline atypical for an unmodified process (notable DLL loads, child processes, IOCTL traffic).
|
||||
4. **Static signals** — entropy, packing, sensitive imports, attribution-bearing strings.
|
||||
|
||||
Be specific: cite rule names, region addresses, API names. Do not speculate beyond the data.
|
||||
"""
|
||||
|
||||
@@ -78,7 +78,7 @@ python grumpycat.py [global-options] <command> [command-options]
|
||||
|
||||
```bash
|
||||
# Upload and run static + dynamic
|
||||
grumpycat.py upload malware.exe --analysis static dynamic
|
||||
grumpycat.py upload payload.exe --analysis static dynamic
|
||||
|
||||
# Upload a kernel driver and immediately run BYOVD
|
||||
grumpycat.py upload-driver rootkit.sys --holygrail
|
||||
@@ -145,7 +145,7 @@ from grumpycat import LitterBoxClient
|
||||
|
||||
with LitterBoxClient(base_url="http://127.0.0.1:1337") as client:
|
||||
# Upload and run analysis
|
||||
result = client.upload_file("malware.exe")
|
||||
result = client.upload_file("payload.exe")
|
||||
file_hash = result["file_info"]["md5"]
|
||||
static_result = client.analyze_file(file_hash, "static")
|
||||
dynamic_result = client.analyze_file(file_hash, "dynamic")
|
||||
@@ -156,7 +156,7 @@ with LitterBoxClient(base_url="http://127.0.0.1:1337") as client:
|
||||
# Driver workflow
|
||||
driver_result = client.upload_and_analyze_driver("driver.sys", run_holygrail=True)
|
||||
|
||||
# Risk assessment endpoint
|
||||
# Detection assessment endpoint
|
||||
risk = client.get_risk_assessment(file_hash)
|
||||
# → {"risk_score": 32.5, "risk_level": "Medium", "risk_factors": [...]}
|
||||
|
||||
@@ -278,11 +278,11 @@ All 22 tools are async. Tool exceptions become MCP error responses automatically
|
||||
|
||||
| Tool | What It Does |
|
||||
|-------------------------------|-----------------------------------------------------------------------|
|
||||
| `get_file_info` | Metadata: type, size, hashes, entropy, PE structure, suspicious imports |
|
||||
| `get_file_info` | Metadata: type, size, hashes, entropy, PE structure, sensitive imports |
|
||||
| `get_static_results` | YARA + CheckPlz + Stringnalyzer findings |
|
||||
| `get_dynamic_results` | Memory scanners + behavioral telemetry + process output |
|
||||
| `get_holygrail_results` | LOLDrivers + block status + critical imports |
|
||||
| `get_risk_assessment` | `{score, level, factors}` for the target |
|
||||
| `get_risk_assessment` | Detection score + level + triggering indicators for the target |
|
||||
| `get_comprehensive_results` | All four results in one parallel call |
|
||||
| `get_report` | Full HTML report inline |
|
||||
| `download_report` | Save the HTML report to disk and return the path |
|
||||
@@ -300,7 +300,7 @@ All 22 tools are async. Tool exceptions become MCP error responses automatically
|
||||
|
||||
| Tool | What It Does |
|
||||
|---------------------|-----------------------------------------------------------------------|
|
||||
| `list_payloads` | List every analyzed payload + driver + process with risk summary |
|
||||
| `list_payloads` | List every analyzed payload + driver + process with detection summary |
|
||||
| `sandbox_status` | Health + tool readiness + fleet summary |
|
||||
| `cleanup_sandbox` | Wipe artifacts (destructive — confirm before calling) |
|
||||
| `delete_payload` | Delete one payload + its results (destructive) |
|
||||
|
||||
@@ -30,7 +30,7 @@ class LitterBoxAPIError(LitterBoxError):
|
||||
|
||||
|
||||
class LitterBoxClient:
|
||||
"""Optimized Python client for LitterBox malware analysis sandbox API."""
|
||||
"""Optimized Python client for LitterBox payload-analysis sandbox API."""
|
||||
|
||||
def __init__(self,
|
||||
base_url: str = "http://127.0.0.1:1337",
|
||||
@@ -340,7 +340,7 @@ class LitterBoxClient:
|
||||
return response.json()
|
||||
|
||||
def get_risk_assessment(self, target: str) -> Dict:
|
||||
"""Get the computed risk assessment (score, level, factors) for a target."""
|
||||
"""Get the computed detection assessment (score, level, triggering indicators) for a target."""
|
||||
response = self._make_request('GET', f'/api/results/{target}/risk')
|
||||
return response.json()
|
||||
|
||||
@@ -525,7 +525,7 @@ class LitterBoxClient:
|
||||
def create_enhanced_parser():
|
||||
"""Create enhanced argument parser with all available operations."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Enhanced LitterBox Malware Analysis Client",
|
||||
description="Enhanced LitterBox Payload-Analysis Client",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
|
||||
@@ -104,7 +104,7 @@ def api_byovd_info(target):
|
||||
@api_bp.route('/api/results/<target>/risk', methods=['GET'])
|
||||
@error_handler
|
||||
def api_risk_assessment(target):
|
||||
"""Return the computed risk assessment (score, level, factors) for a target."""
|
||||
"""Return the computed detection assessment (score, level, triggering indicators) for a target."""
|
||||
app = current_app
|
||||
deps = _deps()
|
||||
app.logger.debug(f"Fetching risk assessment for target: {target}")
|
||||
|
||||
@@ -133,7 +133,7 @@ def render_analysis_info(data, analysis_type, route_helpers):
|
||||
# route_helpers.get_detection_counts(data) returns dynamic counts whenever
|
||||
# a dynamic scan exists, which is wrong on the /static page (the row's
|
||||
# match list is static but the count is dynamic, so YARA can read
|
||||
# "Suspicious / 3" with "No threats detected" right next to it).
|
||||
# "Detected / 3" with "No rules matched" right next to it).
|
||||
detections = json_helpers.extract_detection_counts(analysis_results)
|
||||
|
||||
if analysis_type == 'static':
|
||||
|
||||
@@ -404,7 +404,7 @@ class ByovdApp {
|
||||
<span class="lb-tag medium">⚑ HolyGrail</span>
|
||||
<div style="flex: 1;">
|
||||
<div class="lb-strong" style="font-size: 14px;">The Holy Grail Found</div>
|
||||
<div class="lb-muted" style="font-size: 11px;">Dangerous imports detected · Not on LOLDrivers · Not blocked</div>
|
||||
<div class="lb-muted" style="font-size: 11px;">Critical imports observed · Not on LOLDrivers · Not blocked</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -421,11 +421,11 @@ class ByovdApp {
|
||||
severity = 'medium';
|
||||
} else if (data.hasDanger) {
|
||||
title = 'Potentially Interesting';
|
||||
description = 'Contains suspicious imports but may have limited exploitation potential';
|
||||
description = 'Contains critical imports but may have limited exploitation potential';
|
||||
severity = 'info';
|
||||
} else {
|
||||
title = 'Low Risk Driver';
|
||||
description = 'No obvious signs of exploitation potential detected';
|
||||
title = 'Low BYOVD Potential';
|
||||
description = 'No obvious signs of exploitation potential observed';
|
||||
severity = 'info';
|
||||
}
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<span class="lb-tag medium">⚑ HolyGrail</span>
|
||||
<div style="flex: 1;">
|
||||
<div class="lb-strong" style="font-size: 13px;">The Holy Grail Found</div>
|
||||
<div class="lb-muted" style="font-size: 11px;">Dangerous imports detected · Not on LOLDrivers · Not blocked</div>
|
||||
<div class="lb-muted" style="font-size: 11px;">Critical imports observed · Not on LOLDrivers · Not blocked</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -394,10 +394,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
description = 'Listed on Microsoft recommended driver block rules';
|
||||
} else if (hasDanger) {
|
||||
title = 'Potentially Interesting';
|
||||
description = 'Contains suspicious imports but may have limited exploitation potential';
|
||||
description = 'Contains critical imports but may have limited exploitation potential';
|
||||
} else {
|
||||
title = 'Low Risk Driver';
|
||||
description = 'No obvious signs of exploitation potential detected';
|
||||
title = 'Low BYOVD Potential';
|
||||
description = 'No obvious signs of exploitation potential observed';
|
||||
}
|
||||
|
||||
const severity = (isLol || win10 || win11) ? 'medium' : 'info';
|
||||
|
||||
@@ -255,8 +255,28 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
if (dynamicButton) dynamicButton.style.display = 'flex';
|
||||
}
|
||||
// Make modal functions globally accessible
|
||||
window.showDynamicWarning = () => modal.show();
|
||||
window.showDynamicWarning = () => {
|
||||
// Pre-populate the args input with whatever was last used so the user
|
||||
// can re-run with the same args or tweak them.
|
||||
const argsInput = document.getElementById('dynamicAnalysisArgs');
|
||||
if (argsInput) {
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem('analysisArgs') || '[]');
|
||||
argsInput.value = Array.isArray(saved) ? saved.join(' ') : '';
|
||||
} catch {
|
||||
argsInput.value = '';
|
||||
}
|
||||
}
|
||||
modal.show();
|
||||
};
|
||||
window.hideDynamicWarning = () => modal.hide();
|
||||
window.proceedWithDynamicAnalysis = (fileHash) => {
|
||||
const argsInput = document.getElementById('dynamicAnalysisArgs');
|
||||
const argsValue = argsInput ? argsInput.value : '';
|
||||
const args = argsValue.split(' ').filter(arg => arg.trim() !== '');
|
||||
localStorage.setItem('analysisArgs', JSON.stringify(args));
|
||||
window.location.href = `/analyze/dynamic/${fileHash}`;
|
||||
};
|
||||
|
||||
// Start analysis if parameters exist
|
||||
if (analysis.analysisType && analysis.fileHash) {
|
||||
|
||||
@@ -118,12 +118,12 @@ export function codeBlock(content, opts = {}) {
|
||||
}
|
||||
|
||||
/** Build a summary scanner row (used inside #scannerResultsBody). */
|
||||
export function summaryRow({ name, suspicious, count, detail }) {
|
||||
export function summaryRow({ name, triggered, count, detail }) {
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(name)}</td>
|
||||
<td>${tag(suspicious ? 'critical' : 'clean', suspicious ? 'Suspicious' : 'Clean')}</td>
|
||||
<td class="lb-mono" style="color: ${suspicious ? 'var(--lb-accent)' : 'var(--lb-text-mute)'};">${count}</td>
|
||||
<td>${tag(triggered ? 'critical' : 'clean', triggered ? 'Detected' : 'Clean')}</td>
|
||||
<td class="lb-mono" style="color: ${triggered ? 'var(--lb-accent)' : 'var(--lb-text-mute)'};">${count}</td>
|
||||
<td class="lb-muted" style="font-size: 11px;">${escapeHtml(detail)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
const isClean = !findings.initial_threat && !scan.detection_offset;
|
||||
|
||||
ctx.statsElement.innerHTML = statRow([
|
||||
{ label: 'Status', value: isClean ? 'Clean' : (findings.initial_threat || 'Threat'),
|
||||
{ label: 'Status', value: isClean ? 'Clean' : (findings.initial_threat || 'Triggered'),
|
||||
severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Scan Duration', value: typeof scan.scan_duration === 'number' ? scan.scan_duration.toFixed(3) + 's' : 'N/A',
|
||||
severity: 'info' },
|
||||
@@ -32,16 +32,16 @@ export default {
|
||||
], 2));
|
||||
|
||||
if (isClean) {
|
||||
html += cleanState('No threats detected', 'Security scan completed successfully.');
|
||||
html += cleanState('No signatures triggered', 'AV signature scan completed without matches.');
|
||||
} else {
|
||||
html += panel('Threat Detection', kvGrid([
|
||||
['Detection Offset', scan.detection_offset || '—'],
|
||||
html += panel('Signature Trigger', kvGrid([
|
||||
['Trigger Offset', scan.detection_offset || '—'],
|
||||
['Relative Location', scan.relative_location || '—', false],
|
||||
['Final Threat Detection', scan.final_threat_detection || '—', false],
|
||||
['Final Trigger Location', scan.final_threat_detection || '—', false],
|
||||
], 2), 'CRITICAL');
|
||||
|
||||
if (scan.hex_dump) {
|
||||
html += panel('Hex Dump (±128 bytes around detection)', codeBlock(scan.hex_dump, { label: 'Bytes' }));
|
||||
html += panel('Hex Dump (±128 bytes around trigger)', codeBlock(scan.hex_dump, { label: 'Bytes' }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
const processPid = firstDetection?.pid ?? 'N/A';
|
||||
|
||||
ctx.statsElement.innerHTML = statRow([
|
||||
{ label: 'Status', value: hasFindings ? 'Suspicious' : 'Clean', severity: hasFindings ? 'critical' : 'clean' },
|
||||
{ label: 'Status', value: hasFindings ? 'Detected' : 'Clean', severity: hasFindings ? 'critical' : 'clean' },
|
||||
{ label: 'Findings', value: summary.total_findings || 0, severity: hasFindings ? 'critical' : 'info' },
|
||||
{ label: 'Threads', value: summary.scanned_threads || 0, severity: 'info' },
|
||||
{ label: 'PID', value: processPid, severity: 'info' },
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
]);
|
||||
|
||||
if (!hasFindings) {
|
||||
ctx.element.innerHTML = cleanState('No suspicious behaviour detected', `Process ${processName} (PID ${processPid}) is clean.`);
|
||||
ctx.element.innerHTML = cleanState('No sleep-pattern indicators', `Process ${processName} (PID ${processPid}) shows no beacon-like sleep behaviour.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
html += Object.entries(findingsByThread).map(([tid, items]) => `
|
||||
<div class="lb-panel">
|
||||
<div class="lb-panel-hdr">
|
||||
<span class="lb-glyph">▸</span>${tid === 'process' ? 'Process-wide Findings' : `Thread ${tid}`}
|
||||
<span class="lb-glyph">▸</span>${tid === 'process' ? 'Process-wide Indicators' : `Thread ${tid}`}
|
||||
<span class="lb-panel-badge">${items.length}</span>
|
||||
</div>
|
||||
<div class="lb-panel-body">
|
||||
|
||||
@@ -31,16 +31,16 @@ export default {
|
||||
}
|
||||
|
||||
html += statRow([
|
||||
{ label: 'Status', value: isClean ? 'Clean' : 'Suspicious', severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Status', value: isClean ? 'Clean' : 'Detected', severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Total Regions', value: f.total_regions || 0, severity: 'info' },
|
||||
{ label: 'Threads', value: (f.threads || []).length, severity: 'info' },
|
||||
]);
|
||||
|
||||
if (isClean) {
|
||||
const note = f.total_unsigned_modules > 0
|
||||
? `Note: ${f.total_unsigned_modules} unsigned module(s) found, but no suspicious behaviour detected.`
|
||||
: 'Memory analysis completed successfully.';
|
||||
html += cleanState('No suspicious activities detected', note);
|
||||
? `Note: ${f.total_unsigned_modules} unsigned module(s) observed, but no anomalies in memory layout.`
|
||||
: 'Memory analysis completed without anomalies.';
|
||||
html += cleanState('No anomalies observed', note);
|
||||
ctx.element.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
@@ -70,19 +70,19 @@ export default {
|
||||
`);
|
||||
|
||||
const warnings = [
|
||||
{ cond: f.total_private_rwx > 0, msg: `Critical: Found ${f.total_private_rwx} private RWX region(s)`, sev: 'critical' },
|
||||
{ cond: f.total_heap_executable > 0, msg: `Critical: Found ${f.total_heap_executable} executable heap region(s)`, sev: 'critical' },
|
||||
{ cond: f.total_modified_code > 0, msg: `Critical: Detected ${f.total_modified_code} modified code region(s)`, sev: 'critical' },
|
||||
{ cond: f.total_modified_pe_header > 0, msg: `Critical: Found ${f.total_modified_pe_header} modified PE header(s)`, sev: 'critical' },
|
||||
{ cond: f.total_threads_non_image > 0, msg: `Critical: Found ${f.total_threads_non_image} thread(s) in non-image memory`, sev: 'critical' },
|
||||
{ cond: f.total_private_rx > 0, msg: `Warning: Found ${f.total_private_rx} private RX region(s)`, sev: 'medium' },
|
||||
{ cond: f.total_inconsistent_x > 0, msg: `Warning: ${f.total_inconsistent_x} region(s) with inconsistent X perms`, sev: 'medium' },
|
||||
{ cond: f.total_missing_peb > 0, msg: `Warning: ${f.total_missing_peb} missing PEB module(s)`, sev: 'medium' },
|
||||
{ cond: f.total_mismatching_peb > 0, msg: `Warning: ${f.total_mismatching_peb} mismatching PEB module(s)`, sev: 'medium' },
|
||||
{ cond: f.total_private_rwx > 0, msg: `Critical: ${f.total_private_rwx} private RWX region(s) observed`, sev: 'critical' },
|
||||
{ cond: f.total_heap_executable > 0, msg: `Critical: ${f.total_heap_executable} executable heap region(s) observed`, sev: 'critical' },
|
||||
{ cond: f.total_modified_code > 0, msg: `Critical: ${f.total_modified_code} modified code region(s) observed`, sev: 'critical' },
|
||||
{ cond: f.total_modified_pe_header > 0, msg: `Critical: ${f.total_modified_pe_header} modified PE header(s) observed`, sev: 'critical' },
|
||||
{ cond: f.total_threads_non_image > 0, msg: `Critical: ${f.total_threads_non_image} thread(s) in non-image memory`, sev: 'critical' },
|
||||
{ cond: f.total_private_rx > 0, msg: `Warning: ${f.total_private_rx} private RX region(s) observed`, sev: 'medium' },
|
||||
{ cond: f.total_inconsistent_x > 0, msg: `Warning: ${f.total_inconsistent_x} region(s) with inconsistent X perms`, sev: 'medium' },
|
||||
{ cond: f.total_missing_peb > 0, msg: `Warning: ${f.total_missing_peb} missing PEB module(s)`, sev: 'medium' },
|
||||
{ cond: f.total_mismatching_peb > 0, msg: `Warning: ${f.total_mismatching_peb} mismatching PEB module(s)`, sev: 'medium' },
|
||||
].filter(w => w.cond);
|
||||
|
||||
if (warnings.length) {
|
||||
html += panel('Suspicious Activity', `
|
||||
html += panel('Triggering Indicators', `
|
||||
<ul style="list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px;">
|
||||
${warnings.map(w => `
|
||||
<li style="display: flex; gap: 8px; padding: 6px 10px; background: var(--lb-bg); border-left: 2px solid ${w.sev === 'critical' ? 'var(--lb-accent)' : 'var(--lb-sev-medium)'}; font-size: 12px;">
|
||||
|
||||
@@ -20,7 +20,7 @@ export default {
|
||||
const isClean = findings.length === 0;
|
||||
|
||||
ctx.statsElement.innerHTML = statRow([
|
||||
{ label: 'Status', value: isClean ? 'Clean' : 'Suspicious', severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Status', value: isClean ? 'Clean' : 'Detected', severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Memory Regions', value: mem.total_regions || 0, severity: 'info' },
|
||||
{ label: 'Total Findings', value: sum.total_findings || 0, severity: isClean ? 'info' : 'critical' },
|
||||
]);
|
||||
@@ -33,11 +33,11 @@ export default {
|
||||
], 2));
|
||||
|
||||
if (isClean) {
|
||||
html += cleanState('No threats detected', `Scan completed in ${sum.duration || 0}s.`);
|
||||
html += cleanState('No indicators observed', `Scan completed in ${sum.duration || 0}s.`);
|
||||
} else {
|
||||
const findingsByType = sum.findings_by_type || {};
|
||||
if (Object.keys(findingsByType).length > 0) {
|
||||
html += panel('Findings By Type', `
|
||||
html += panel('Indicators By Type', `
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;">
|
||||
${Object.entries(findingsByType).map(([type, count]) => `
|
||||
<div style="padding: 10px; border: 1px solid rgba(239, 68, 68, 0.3);">
|
||||
|
||||
@@ -15,13 +15,13 @@ export default {
|
||||
const isClean = (f.total_suspicious || 0) === 0;
|
||||
|
||||
let html = statRow([
|
||||
{ label: 'Status', value: isClean ? 'Clean' : 'Suspicious', severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Status', value: isClean ? 'Clean' : 'Detected', severity: isClean ? 'clean' : 'critical' },
|
||||
{ label: 'Total Scanned', value: f.total_scanned || 0, severity: 'info' },
|
||||
{ label: 'Suspicious', value: f.total_suspicious || 0, severity: isClean ? 'info' : 'critical' },
|
||||
{ label: 'Modifications', value: f.total_suspicious || 0, severity: isClean ? 'info' : 'critical' },
|
||||
]);
|
||||
|
||||
if (isClean) {
|
||||
html += cleanState('No suspicious modifications detected', 'PE analysis completed successfully.');
|
||||
html += cleanState('No memory modifications observed', 'PE-Sieve scan completed without findings.');
|
||||
ctx.element.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export default {
|
||||
{ label: 'Other', value: f.other },
|
||||
];
|
||||
|
||||
html += panel('Detection Breakdown', `
|
||||
html += panel('Indicator Breakdown', `
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;">
|
||||
${breakdown.map(item => `
|
||||
<div style="padding: 10px; border: 1px solid ${item.value > 0 ? 'rgba(239, 68, 68, 0.3)' : 'var(--lb-border)'};">
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
ctx.statsElement.innerHTML = '';
|
||||
ctx.element.innerHTML = isPidScan
|
||||
? cleanState('RedEdr Not Initialised', 'No data available — RedEdr did not initialise for this PID-based scan.')
|
||||
: threatState('No Analysis Data Available', 'Refresh the page to initiate a new scan.');
|
||||
: threatState('No telemetry available', 'Refresh the page to initiate a new scan.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,14 +39,14 @@ export default {
|
||||
}
|
||||
|
||||
const sections = [
|
||||
['Suspicious Strings', findings.found_suspicious_strings],
|
||||
['Functions Referenced', findings.found_suspicious_functions],
|
||||
['URLs Found', findings.found_url],
|
||||
['Notable Strings', findings.found_suspicious_strings],
|
||||
['Notable Functions', findings.found_suspicious_functions],
|
||||
['URLs', findings.found_url],
|
||||
['DLLs Referenced', findings.found_dll],
|
||||
['IP Addresses', findings.found_ip],
|
||||
['Paths Found', findings.found_path],
|
||||
['Paths', findings.found_path],
|
||||
['Files Referenced', findings.found_file],
|
||||
['Commands Found', findings.found_commands],
|
||||
['Commands', findings.found_commands],
|
||||
['Functions', findings.found_functions],
|
||||
['Error Messages', findings.found_error_messages],
|
||||
['Network Indicators', findings.found_network_indicators],
|
||||
|
||||
@@ -76,9 +76,9 @@ export default {
|
||||
totalDetections += matches.length;
|
||||
rows.push(summaryRow({
|
||||
name: 'YARA',
|
||||
suspicious: matches.length > 0,
|
||||
triggered: matches.length > 0,
|
||||
count: matches.length,
|
||||
detail: matches.length > 0 ? `${matches.length} rule match${matches.length === 1 ? '' : 'es'} found` : 'No threats detected',
|
||||
detail: matches.length > 0 ? `${matches.length} rule match${matches.length === 1 ? '' : 'es'}` : 'No rules matched',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ export default {
|
||||
totalDetections += susp;
|
||||
rows.push(summaryRow({
|
||||
name: 'PE-sieve',
|
||||
suspicious: susp > 0,
|
||||
triggered: susp > 0,
|
||||
count: susp,
|
||||
detail: susp > 0 ? `${susp} suspicious modification${susp === 1 ? '' : 's'} found` : 'No modifications detected',
|
||||
detail: susp > 0 ? `${susp} memory modification${susp === 1 ? '' : 's'} observed` : 'No memory modifications observed',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -103,9 +103,9 @@ export default {
|
||||
totalDetections += susp;
|
||||
rows.push(summaryRow({
|
||||
name: 'Moneta',
|
||||
suspicious: !isClean,
|
||||
triggered: !isClean,
|
||||
count: susp,
|
||||
detail: isClean ? 'No anomalies detected' : 'Memory anomalies found',
|
||||
detail: isClean ? 'No anomalies observed' : 'Memory anomalies observed',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -115,9 +115,9 @@ export default {
|
||||
if (hasDetection) totalDetections++;
|
||||
rows.push(summaryRow({
|
||||
name: 'CheckPlz',
|
||||
suspicious: hasDetection,
|
||||
triggered: hasDetection,
|
||||
count: hasDetection ? 1 : 0,
|
||||
detail: hasDetection ? (f.initial_threat || 'Threat detected') : 'No threats detected',
|
||||
detail: hasDetection ? (f.initial_threat || 'Signature triggered') : 'No signatures triggered',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -126,9 +126,9 @@ export default {
|
||||
totalDetections += total;
|
||||
rows.push(summaryRow({
|
||||
name: 'Patriot',
|
||||
suspicious: total > 0,
|
||||
triggered: total > 0,
|
||||
count: total,
|
||||
detail: total > 0 ? `${total} suspicious activit${total === 1 ? 'y' : 'ies'} found` : 'No suspicious activities',
|
||||
detail: total > 0 ? `${total} indicator${total === 1 ? '' : 's'} observed` : 'No indicators observed',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -137,9 +137,9 @@ export default {
|
||||
totalDetections += total;
|
||||
rows.push(summaryRow({
|
||||
name: 'Hunt-Sleeping-Beacons',
|
||||
suspicious: total > 0,
|
||||
triggered: total > 0,
|
||||
count: total,
|
||||
detail: total > 0 ? 'Suspicious behaviour detected' : 'No suspicious behaviour',
|
||||
detail: total > 0 ? 'Sleep-pattern indicators observed' : 'No sleep-pattern indicators',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ export default {
|
||||
const overEl = document.getElementById('overallStatus');
|
||||
if (totalEl) totalEl.textContent = totalDetections;
|
||||
if (overEl) {
|
||||
overEl.textContent = totalDetections > 0 ? 'Threats Detected' : 'Clean';
|
||||
overEl.textContent = totalDetections > 0 ? 'Detections' : 'Clean';
|
||||
overEl.style.color = totalDetections > 0 ? 'var(--lb-accent)' : 'var(--lb-sev-low)';
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
}
|
||||
|
||||
if (isClean) {
|
||||
html += cleanState('No threats detected', 'All YARA rules passed successfully.');
|
||||
html += cleanState('No rules matched', 'All YARA rules passed without matching.');
|
||||
ctx.element.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
);
|
||||
|
||||
const labelMap = {
|
||||
threat_name: 'Threat',
|
||||
threat_name: 'Match',
|
||||
rule_filepath: 'Rule File',
|
||||
creation_date: 'Created',
|
||||
id: 'Rule ID',
|
||||
|
||||
@@ -105,13 +105,13 @@ function updateStats() {
|
||||
riskClass = 'bg-green-500 text-white';
|
||||
}
|
||||
|
||||
elements.averageRisk.textContent = `${riskText} Risk`;
|
||||
elements.averageRisk.textContent = `${riskText} Detection`;
|
||||
elements.averageRisk.className = 'px-2 py-1 text-sm rounded-lg inline-flex items-center justify-center font-medium ' + riskClass;
|
||||
elements.averageEntropy.textContent = `Risk Score: ${avgRiskScore.toFixed(1)}%`;
|
||||
elements.averageEntropy.textContent = `Detection Score: ${avgRiskScore.toFixed(1)}%`;
|
||||
} else {
|
||||
elements.averageRisk.textContent = '-';
|
||||
elements.averageRisk.className = 'px-2 py-1 text-sm rounded-lg inline-flex items-center justify-center font-medium bg-gray-500 text-white';
|
||||
elements.averageEntropy.textContent = 'Risk Score: -';
|
||||
elements.averageEntropy.textContent = 'Detection Score: -';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -399,9 +399,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
};
|
||||
|
||||
const defaultConfig = {
|
||||
title: 'Suspicious Imports Analysis',
|
||||
title: 'Sensitive Imports Analysis',
|
||||
badge: null,
|
||||
badgeLabel: 'Suspicious',
|
||||
badgeLabel: 'Sensitive',
|
||||
countClass: 'bg-red-500/10 text-red-500',
|
||||
dllColor: 'text-red-500',
|
||||
categoryBg: 'bg-red-500/20',
|
||||
@@ -477,24 +477,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function updateImportsSummary(buildWith, runtimeCount, suspiciousCount) {
|
||||
let summaryText = '';
|
||||
|
||||
|
||||
if (buildWith === 'go') {
|
||||
if (suspiciousCount > 0) {
|
||||
summaryText = `Go binary detected: ${runtimeCount} Go runtime imports (benign) and ${suspiciousCount} potentially suspicious imports found.`;
|
||||
summaryText = `Go binary detected: ${runtimeCount} standard Go runtime imports and ${suspiciousCount} sensitive imports observed.`;
|
||||
} else {
|
||||
summaryText = `Go binary detected: ${runtimeCount} Go runtime imports found - these are typically benign.`;
|
||||
summaryText = `Go binary detected: ${runtimeCount} standard Go runtime imports — typically not user logic.`;
|
||||
}
|
||||
} else if (buildWith === 'rust') {
|
||||
if (suspiciousCount > 0) {
|
||||
summaryText = `Rust binary detected: ${runtimeCount} Rust runtime imports (benign) and ${suspiciousCount} potentially suspicious imports found.`;
|
||||
summaryText = `Rust binary detected: ${runtimeCount} standard Rust runtime imports and ${suspiciousCount} sensitive imports observed.`;
|
||||
} else {
|
||||
summaryText = `Rust binary detected: ${runtimeCount} Rust runtime imports found - these are typically benign.`;
|
||||
summaryText = `Rust binary detected: ${runtimeCount} standard Rust runtime imports — typically not user logic.`;
|
||||
}
|
||||
} else {
|
||||
const totalImports = runtimeCount + suspiciousCount;
|
||||
summaryText = `Found ${totalImports} potentially suspicious imports that may indicate malicious capabilities.`;
|
||||
summaryText = `${totalImports} sensitive imports observed — these are APIs commonly watched by AV/EDR.`;
|
||||
}
|
||||
|
||||
|
||||
elements.suspiciousImportsSummary.textContent = summaryText;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>{{ config.application.name }} — Malware Analysis</title>
|
||||
<title>{{ config.application.name }} — Payload Analysis</title>
|
||||
<link rel="icon" type="image/x-icon" href="{{ favicon_url }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.min.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"/>
|
||||
|
||||
@@ -77,9 +77,9 @@
|
||||
{% if findings.implanted_shc > 0 %}<div>Implanted Shellcode: {{ findings.implanted_shc }}</div>{% endif %}
|
||||
{% if findings.unreachable > 0 %}<div>Unreachable: {{ findings.unreachable }}</div>{% endif %}
|
||||
{% if findings.other > 0 %}<div>Other: {{ findings.other }}</div>{% endif %}
|
||||
{% if findings.total_suspicious > 0 %}<div>Total Suspicious: {{ findings.total_suspicious }}</div>{% endif %}
|
||||
{% if findings.total_suspicious > 0 %}<div>Total Modifications: {{ findings.total_suspicious }}</div>{% endif %}
|
||||
{% else %}
|
||||
<span class="lb-muted">No modifications detected</span>
|
||||
<span class="lb-muted">No memory modifications observed</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -110,7 +110,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="lb-muted">No anomalies detected</span>
|
||||
<span class="lb-muted">No anomalies observed</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -126,7 +126,7 @@
|
||||
<div>{{ finding.type }} <span class="lb-muted">({{ finding.level }})</span></div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="lb-muted">No suspicious activities</span>
|
||||
<span class="lb-muted">No indicators observed</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -144,7 +144,7 @@
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="lb-muted">No suspicious behavior</span>
|
||||
<span class="lb-muted">No sleep-pattern indicators</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="lb-hero-meta lb-mono">MD5: {{ file_info.md5 }}</div>
|
||||
</div>
|
||||
<div class="lb-col">
|
||||
<div class="lb-hero-label">Risk</div>
|
||||
<div class="lb-hero-label">Detection</div>
|
||||
<div class="lb-hero-score">{{ file_info.risk_assessment.score }}<span class="unit">/100</span></div>
|
||||
<div class="lb-hero-meta {% if file_info.risk_assessment.level == 'Critical' or file_info.risk_assessment.level == 'High' %}crit{% elif file_info.risk_assessment.level == 'Medium' %}medium{% else %}low{% endif %}">
|
||||
{{ file_info.risk_assessment.level }}
|
||||
@@ -50,9 +50,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Risk score bar -->
|
||||
<!-- Detection score bar -->
|
||||
<div class="lb-panel">
|
||||
<div class="lb-panel-hdr"><span class="lb-glyph">▸</span>Risk Assessment</div>
|
||||
<div class="lb-panel-hdr"><span class="lb-glyph">▸</span>Detection Assessment</div>
|
||||
<div class="lb-panel-body">
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
||||
<span class="lb-tag {{ file_info.risk_assessment.level|lower }}">{{ file_info.risk_assessment.level }}</span>
|
||||
@@ -63,7 +63,7 @@
|
||||
</div>
|
||||
|
||||
{% if file_info.risk_assessment.factors %}
|
||||
<div class="lb-eyebrow" style="margin-bottom: 8px;">Risk Factors</div>
|
||||
<div class="lb-eyebrow" style="margin-bottom: 8px;">Triggering Indicators</div>
|
||||
<ul style="list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px;">
|
||||
{% for factor in file_info.risk_assessment.factors %}
|
||||
<li style="display: flex; gap: 8px; padding: 6px 10px; background: var(--lb-bg); border-left: 2px solid var(--lb-accent); font-size: 12px;">
|
||||
@@ -227,14 +227,14 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Suspicious imports -->
|
||||
<!-- Sensitive imports -->
|
||||
{% if file_info.pe_info.suspicious_imports %}
|
||||
{% set runtime_imports = file_info.pe_info.suspicious_imports | selectattr('is_runtime_import') | list %}
|
||||
{% set suspicious_imports = file_info.pe_info.suspicious_imports | rejectattr('is_runtime_import') | list %}
|
||||
{% set build_with = file_info.pe_info.build_with %}
|
||||
|
||||
<div class="lb-eyebrow" style="margin-bottom: 8px;">
|
||||
{% if build_with and not suspicious_imports %}API Imports ({{ build_with|capitalize }} Runtime){% else %}Suspicious Imports{% endif %}
|
||||
{% if build_with and not suspicious_imports %}API Imports ({{ build_with|capitalize }} Runtime){% else %}Sensitive Imports{% endif %}
|
||||
</div>
|
||||
|
||||
{% if build_with %}
|
||||
@@ -243,7 +243,7 @@
|
||||
<span class="lb-tag info">{{ build_with|upper }} Binary Detected</span>
|
||||
<span class="lb-muted">
|
||||
{% if suspicious_imports %}
|
||||
Found {{ runtime_imports|length }} runtime imports (benign) and {{ suspicious_imports|length }} potentially suspicious imports.
|
||||
{{ runtime_imports|length }} standard runtime imports and {{ suspicious_imports|length }} sensitive imports observed.
|
||||
{% else %}
|
||||
These {{ runtime_imports|length }} imports are typically part of the {{ build_with|capitalize }} runtime.
|
||||
{% endif %}
|
||||
|
||||
@@ -227,9 +227,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dangerous imports -->
|
||||
<!-- Critical imports -->
|
||||
<div class="lb-panel" id="importsCard">
|
||||
<div class="lb-panel-hdr"><span class="lb-glyph">▸</span>Dangerous Imports</div>
|
||||
<div class="lb-panel-hdr"><span class="lb-glyph">▸</span>Critical Imports</div>
|
||||
<div class="lb-panel-body">
|
||||
<div id="importsWrap" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px;"></div>
|
||||
<div class="lb-muted" style="font-size: 10px;">Heuristics identify privileged primitives commonly exploited in BYOVD attacks.</div>
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
{%- endmacro %}
|
||||
|
||||
|
||||
{# Renders the Suspicious / Clean status badge cell. #}
|
||||
{# Renders the Detected / Clean status badge cell. #}
|
||||
{% macro scanner_status_cell(detections) -%}
|
||||
<td>
|
||||
<span class="lb-tag {{ 'critical' if detections else 'clean' }}">
|
||||
{{ 'Suspicious' if detections else 'Clean' }}
|
||||
{{ 'Detected' if detections else 'Clean' }}
|
||||
</span>
|
||||
</td>
|
||||
{%- endmacro %}
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="lb-chip{% if total > 0 %} hit{% endif %}">
|
||||
<div class="lb-chip-name">Status</div>
|
||||
<div id="overallStatus" class="lb-chip-count" style="font-size: 14px; color: {{ 'var(--lb-accent)' if total > 0 else 'var(--lb-sev-low)' }};">
|
||||
{{ 'Threats Detected' if total > 0 else 'Clean' }}
|
||||
{{ 'Detections' if total > 0 else 'Clean' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lb-chip{% if total > 0 %} hit{% endif %}">
|
||||
@@ -82,7 +82,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="lb-muted">No threats detected</span>
|
||||
<span class="lb-muted">No rules matched</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+18
-18
@@ -567,7 +567,7 @@
|
||||
<div class="risk-bar-fill {{ byovd_class }}" style="width: {{ byovd_score if byovd_score is not none else 0 }}%;"></div>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="hero-eyebrow">Risk Assessment</span>
|
||||
<span class="hero-eyebrow">Detection Assessment</span>
|
||||
<div class="risk-row">
|
||||
<div>
|
||||
<span class="risk-score">{{ risk_score|round|int if risk_score is not none else 0 }}</span><span class="risk-score-suffix">/100</span>
|
||||
@@ -611,7 +611,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- HolyGrail (BYOVD) — driver samples only; promoted above File Info / Risk Factors. -->
|
||||
<!-- HolyGrail (BYOVD) — driver samples only; promoted above File Info / Triggering Indicators. -->
|
||||
{% if is_driver_report %}
|
||||
{% set bf = byovd_results.findings %}
|
||||
{% set bs = bf.summary or {} %}
|
||||
@@ -637,7 +637,7 @@
|
||||
{% elif has_danger %}
|
||||
<span class="pill medium">Potentially Interesting</span>
|
||||
{% else %}
|
||||
<span class="pill clean">Low Risk</span>
|
||||
<span class="pill clean">Low BYOVD Potential</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<p class="section-sub">Driver Imports · LoLDrivers list · Microsoft Driver Block Policy.</p>
|
||||
@@ -697,14 +697,14 @@
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<!-- Risk Factors -->
|
||||
<!-- Triggering Indicators -->
|
||||
{% if risk_factors %}
|
||||
<section class="card">
|
||||
<h2 class="section-title">
|
||||
Key Risk Factors
|
||||
<span class="pill {{ risk_class }}">{{ risk_factors|length }} factor{{ 's' if risk_factors|length != 1 else '' }}</span>
|
||||
Triggering Indicators
|
||||
<span class="pill {{ risk_class }}">{{ risk_factors|length }} indicator{{ 's' if risk_factors|length != 1 else '' }}</span>
|
||||
</h2>
|
||||
<p class="section-sub">Findings that contributed to the overall risk score.</p>
|
||||
<p class="section-sub">Indicators that contributed to the overall detection score.</p>
|
||||
<ul class="factor-list">
|
||||
{% for factor in risk_factors %}
|
||||
<li>{{ factor }}</li>
|
||||
@@ -777,7 +777,7 @@
|
||||
</dl>
|
||||
|
||||
{% if pe.suspicious_imports %}
|
||||
<h3 class="subsection-title">Suspicious Imports ({{ pe.suspicious_imports|length }})</h3>
|
||||
<h3 class="subsection-title">Sensitive Imports ({{ pe.suspicious_imports|length }})</h3>
|
||||
<table class="report-table">
|
||||
<thead><tr><th>DLL</th><th>Function</th><th>Category</th><th>Note</th></tr></thead>
|
||||
<tbody>
|
||||
@@ -871,13 +871,13 @@
|
||||
{% if cp and cp.findings and cp.findings.initial_threat %}
|
||||
<span class="threat-state">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1l7 14H1L8 1zm0 5v4m0 2v.5"/></svg>
|
||||
Threat detected
|
||||
Signature triggered
|
||||
</span>
|
||||
<span class="pill critical">{{ cp.findings.initial_threat }}</span>
|
||||
{% else %}
|
||||
<span class="clean-state">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg>
|
||||
No threat detected
|
||||
No signatures triggered
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -899,15 +899,15 @@
|
||||
{% if st and st.findings %}
|
||||
{% set sus_count = (st.findings.found_suspicious_strings or [])|length + (st.findings.found_suspicious_functions or [])|length %}
|
||||
{% if sus_count > 0 %}
|
||||
<span class="pill medium">{{ sus_count }} suspicious</span>
|
||||
<span class="pill medium">{{ sus_count }} notable</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if st and st.findings %}
|
||||
{% set f = st.findings %}
|
||||
{% set buckets = [
|
||||
('Suspicious strings', f.found_suspicious_strings),
|
||||
('Suspicious functions', f.found_suspicious_functions),
|
||||
('Notable strings', f.found_suspicious_strings),
|
||||
('Notable functions', f.found_suspicious_functions),
|
||||
('URLs', f.found_url),
|
||||
('IP addresses', f.found_ip),
|
||||
('Domains', f.found_domains),
|
||||
@@ -952,7 +952,7 @@
|
||||
{% else %}
|
||||
<div class="clean-state mt-12">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg>
|
||||
No suspicious strings detected
|
||||
No notable strings observed
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -1007,7 +1007,7 @@
|
||||
{% if sus > 0 %}
|
||||
<span class="threat-state">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1l7 14H1L8 1zm0 5v4m0 2v.5"/></svg>
|
||||
{{ sus }} suspicious indicator{{ 's' if sus != 1 else '' }}
|
||||
{{ sus }} memory modification{{ 's' if sus != 1 else '' }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="clean-state">
|
||||
@@ -1088,12 +1088,12 @@
|
||||
{% if pa_findings %}
|
||||
<span class="threat-state">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1l7 14H1L8 1zm0 5v4m0 2v.5"/></svg>
|
||||
{{ pa_findings|length }} finding{{ 's' if pa_findings|length != 1 else '' }}
|
||||
{{ pa_findings|length }} indicator{{ 's' if pa_findings|length != 1 else '' }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="clean-state">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg>
|
||||
No suspicious behaviour
|
||||
No indicators observed
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1197,7 +1197,7 @@
|
||||
|
||||
<footer class="report-footer">
|
||||
<span>Report generated {{ generated_on }}</span>
|
||||
<span class="branding">LitterBox · automated malware-analysis workbench</span>
|
||||
<span class="branding">LitterBox · automated payload-analysis workbench</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -108,11 +108,16 @@
|
||||
</div>
|
||||
<div class="lb-modal-body">
|
||||
<p style="margin-bottom: 10px;">About to execute the payload.</p>
|
||||
<div style="margin: 12px 0;">
|
||||
<label for="dynamicAnalysisArgs" class="lb-eyebrow" style="display: block; margin-bottom: 6px;">Command-line Arguments (optional)</label>
|
||||
<input type="text" id="dynamicAnalysisArgs" placeholder="Enter arguments separated by spaces" class="lb-input lb-mono"/>
|
||||
<p class="lb-muted" style="margin-top: 6px; font-size: 11px;">Arguments passed to the payload at execution time.</p>
|
||||
</div>
|
||||
<p style="color: var(--lb-sev-medium);">Are you sure you want to proceed?</p>
|
||||
</div>
|
||||
<div class="lb-modal-foot">
|
||||
<button onclick="hideDynamicWarning()" class="lb-btn lb-btn-ghost">Cancel</button>
|
||||
<button onclick="window.location.href='/analyze/dynamic/{{ file_hash }}'" class="lb-btn" style="color: var(--lb-sev-medium); border-color: var(--lb-sev-medium);">Proceed</button>
|
||||
<button onclick="proceedWithDynamicAnalysis('{{ file_hash }}')" class="lb-btn" style="color: var(--lb-sev-medium); border-color: var(--lb-sev-medium);">Proceed</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{{ scanner_status_cell(checkplz_detections) }}
|
||||
{{ scanner_count_cell(checkplz_detections) }}
|
||||
<td class="lb-muted" style="font-size: 11px;">
|
||||
{{ analysis_results.checkplz.findings.initial_threat if checkplz_detections else 'No threats detected' }}
|
||||
{{ analysis_results.checkplz.findings.initial_threat if checkplz_detections else 'No signatures triggered' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -78,7 +78,7 @@
|
||||
{% if findings.found_suspicious_strings and findings.found_suspicious_strings|length > 0 %}
|
||||
<div style="border: 1px solid var(--lb-accent); padding: 10px; margin-bottom: 10px;">
|
||||
<div class="lb-eyebrow" style="color: var(--lb-accent); margin-bottom: 6px;">
|
||||
Suspicious Strings <span class="lb-muted">({{ findings.found_suspicious_strings|length }})</span>
|
||||
Notable Strings <span class="lb-muted">({{ findings.found_suspicious_strings|length }})</span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 3px;">
|
||||
{% for item in findings.found_suspicious_strings[:5] %}
|
||||
@@ -93,8 +93,8 @@
|
||||
|
||||
<div class="lb-grid-2">
|
||||
<div>
|
||||
{{ render_findings_row('URLs Found', findings.found_url) }}
|
||||
{{ render_findings_row('Paths Found', findings.found_path) }}
|
||||
{{ render_findings_row('URLs', findings.found_url) }}
|
||||
{{ render_findings_row('Paths', findings.found_path) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ render_findings_row('IP Addresses', findings.found_ip) }}
|
||||
|
||||
+10
-10
@@ -51,7 +51,7 @@
|
||||
<div class="lb-chip-count lb-mono" id="storageUsed" style="font-size: 14px;">0 MB</div>
|
||||
</div>
|
||||
<div class="lb-chip">
|
||||
<div class="lb-chip-name">Average Risk</div>
|
||||
<div class="lb-chip-name">Average Detection</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; gap: 2px;">
|
||||
<span id="averageRisk" class="lb-tag muted">-</span>
|
||||
<span id="averageEntropy" class="lb-muted" style="font-size: 10px;">Entropy: -</span>
|
||||
@@ -63,7 +63,7 @@
|
||||
<!-- Search & filters -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap;">
|
||||
<div style="position: relative; flex: 1; min-width: 200px;">
|
||||
<input type="text" id="searchFiles" placeholder="Search files..." class="lb-input lb-mono" style="padding-right: 30px;"/>
|
||||
<input type="text" id="searchFiles" placeholder="Search payloads..." class="lb-input lb-mono" style="padding-right: 30px;"/>
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); color: var(--lb-text-mute); pointer-events: none;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
@@ -77,7 +77,7 @@
|
||||
<option value="lnk">.lnk</option>
|
||||
</select>
|
||||
<select id="filterRisk" class="lb-select" style="width: auto; min-width: 130px;">
|
||||
<option value="all">All Risks</option>
|
||||
<option value="all">All Levels</option>
|
||||
<option value="critical">Critical</option>
|
||||
<option value="high">High</option>
|
||||
<option value="medium">Medium</option>
|
||||
@@ -88,7 +88,7 @@
|
||||
<option value="oldest">Oldest First</option>
|
||||
<option value="name">Name</option>
|
||||
<option value="size">Size</option>
|
||||
<option value="risk">Risk Score</option>
|
||||
<option value="risk">Detection Score</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
<table class="lb-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Risk</th>
|
||||
<th>Payload Name</th>
|
||||
<th>Detection</th>
|
||||
<th>Size</th>
|
||||
<th>Upload Date</th>
|
||||
<th>Status</th>
|
||||
@@ -132,7 +132,7 @@
|
||||
<div class="lb-chip-count lb-mono" id="driverStorageUsed" style="font-size: 14px;">0 MB</div>
|
||||
</div>
|
||||
<div class="lb-chip">
|
||||
<div class="lb-chip-name">Average Risk</div>
|
||||
<div class="lb-chip-name">Average BYOVD</div>
|
||||
<span id="driverAverageRisk" class="lb-tag muted">-</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,11 +168,11 @@
|
||||
<div class="lb-chip-count" id="totalProcesses">0</div>
|
||||
</div>
|
||||
<div class="lb-chip">
|
||||
<div class="lb-chip-name">High Risk</div>
|
||||
<div class="lb-chip-name">High Detection</div>
|
||||
<div class="lb-chip-count" id="highRiskProcesses">0</div>
|
||||
</div>
|
||||
<div class="lb-chip">
|
||||
<div class="lb-chip-name">Average Risk</div>
|
||||
<div class="lb-chip-name">Average Detection</div>
|
||||
<span id="processAverageRisk" class="lb-tag muted">-</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -184,7 +184,7 @@
|
||||
<th>Process Name</th>
|
||||
<th>PID</th>
|
||||
<th>Arch</th>
|
||||
<th>Risk</th>
|
||||
<th>Detection</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -279,10 +279,10 @@
|
||||
<div id="checksumNotes" style="margin-top: 8px; font-size: 11px; color: var(--lb-text-dim);"></div>
|
||||
</div>
|
||||
|
||||
<!-- Suspicious imports -->
|
||||
<!-- Sensitive imports -->
|
||||
<div id="suspiciousImports" class="hidden" style="border: 1px solid var(--lb-border); padding: 12px; margin-bottom: 12px;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span id="suspiciousImportsTitle" class="lb-eyebrow">Suspicious Imports</span>
|
||||
<span id="suspiciousImportsTitle" class="lb-eyebrow">Sensitive Imports</span>
|
||||
<span id="suspiciousImportsCount" class="lb-tag critical"></span>
|
||||
</div>
|
||||
<div id="suspiciousImportsList" style="display: flex; flex-direction: column; gap: 8px;"></div>
|
||||
|
||||
+14
-14
@@ -55,7 +55,7 @@ class RiskCalculator:
|
||||
total_score += severity_score
|
||||
|
||||
risk_factors.append(
|
||||
f"Found {count} {severity.lower()} severity YARA match"
|
||||
f"{count} {severity.lower()} severity YARA match"
|
||||
f"{'es' if count > 1 else ''}"
|
||||
)
|
||||
|
||||
@@ -107,8 +107,8 @@ class RiskCalculator:
|
||||
pe_risk += min(critical_imports * 15 + high_risk_imports * 8, 30)
|
||||
if critical_imports > 0 or high_risk_imports > 0:
|
||||
risk_factors.append(
|
||||
f"Found {critical_imports} critical process manipulation and "
|
||||
f"{high_risk_imports} high-risk dynamic loading imports"
|
||||
f"{critical_imports} critical process manipulation and "
|
||||
f"{high_risk_imports} sensitive dynamic loading imports observed"
|
||||
)
|
||||
|
||||
if pe_info.get('checksum_info'):
|
||||
@@ -117,7 +117,7 @@ class RiskCalculator:
|
||||
build_with = checksum.get('build_with')
|
||||
if build_with not in ['go', 'rust']:
|
||||
pe_risk += 25
|
||||
risk_factors.append("PE checksum mismatch detected")
|
||||
risk_factors.append("PE checksum mismatch observed")
|
||||
|
||||
return pe_risk, risk_factors
|
||||
|
||||
@@ -224,16 +224,16 @@ def _calculate_byovd_risk(byovd_results):
|
||||
risk_score += 55
|
||||
danger_factors = []
|
||||
if has_dangerous_imports:
|
||||
danger_factors.append("dangerous imports detected")
|
||||
danger_factors.append("critical-import flag observed")
|
||||
if critical_imports and critical_imports.strip():
|
||||
danger_factors.append("critical imports detected")
|
||||
danger_factors.append("critical imports listed")
|
||||
if has_terminate_process:
|
||||
danger_factors.append("process termination capability")
|
||||
if has_communication:
|
||||
danger_factors.append("communication mechanisms")
|
||||
|
||||
if danger_factors:
|
||||
risk_factors.append(f"Dangerous capabilities: {', '.join(danger_factors)}")
|
||||
risk_factors.append(f"Critical capabilities: {', '.join(danger_factors)}")
|
||||
|
||||
if not win11_blocked:
|
||||
risk_score += 25
|
||||
@@ -282,13 +282,13 @@ def _calculate_static_risk(static_results):
|
||||
threat_score = 0
|
||||
if checkplz_findings.get('initial_threat'):
|
||||
threat_score += 50
|
||||
risk_factors.append("Critical: CheckPLZ detected initial threat indicators")
|
||||
risk_factors.append("Critical: CheckPLZ AV signature triggered")
|
||||
|
||||
indicators = checkplz_findings.get('threat_indicators', [])
|
||||
if indicators:
|
||||
indicator_score = min(len(indicators) * 15, 40)
|
||||
threat_score += indicator_score
|
||||
risk_factors.append(f"Found {len(indicators)} additional threat indicators")
|
||||
risk_factors.append(f"{len(indicators)} additional signature indicators observed")
|
||||
|
||||
static_risk += threat_score
|
||||
|
||||
@@ -323,7 +323,7 @@ def _calculate_dynamic_risk(dynamic_results, analysis_type):
|
||||
45 if analysis_type == 'file' else 30,
|
||||
)
|
||||
dynamic_risk += pe_sieve_score
|
||||
risk_factors.append(f"PE-Sieve found {pesieve_suspicious} suspicious indicators")
|
||||
risk_factors.append(f"PE-Sieve observed {pesieve_suspicious} memory modifications")
|
||||
|
||||
dynamic_risk += _calculate_memory_anomaly_risk(dynamic_results, analysis_type, risk_factors)
|
||||
dynamic_risk += _calculate_behavior_risk(dynamic_results, analysis_type, risk_factors)
|
||||
@@ -358,7 +358,7 @@ def _calculate_memory_anomaly_risk(dynamic_results, analysis_type, risk_factors)
|
||||
anomaly_count += count
|
||||
|
||||
if anomaly_count > 0:
|
||||
risk_factors.append(f"Found {anomaly_count} weighted memory anomalies")
|
||||
risk_factors.append(f"{anomaly_count} weighted memory anomalies observed")
|
||||
return min(total_score, 40 if analysis_type == 'file' else 30)
|
||||
|
||||
return 0
|
||||
@@ -387,7 +387,7 @@ def _calculate_behavior_risk(dynamic_results, analysis_type, risk_factors):
|
||||
severity = behavior.get('severity', 'low')
|
||||
behavior_score += severity_scores.get(severity, 5)
|
||||
|
||||
risk_factors.append(f"Found {behavior_count} weighted suspicious behaviors")
|
||||
risk_factors.append(f"{behavior_count} weighted runtime indicators observed")
|
||||
return min(behavior_score, 35)
|
||||
|
||||
|
||||
@@ -419,9 +419,9 @@ def _calculate_hsb_risk(dynamic_results, analysis_type, risk_factors):
|
||||
|
||||
severity_text = ["LOW", "MID", "HIGH"][min(severity, 2)]
|
||||
if severity >= 2:
|
||||
risk_factors.append(f"Critical: Found {count} high-severity memory operations")
|
||||
risk_factors.append(f"Critical: {count} high-severity memory operations observed")
|
||||
else:
|
||||
risk_factors.append(f"Found {count} {severity_text} severity memory operations")
|
||||
risk_factors.append(f"{count} {severity_text} severity memory operations observed")
|
||||
|
||||
return min(total_hsb_score, 45 if analysis_type == 'file' else 35)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user