LitterBox v1.6.0
This commit is contained in:
+8
-1
@@ -2,6 +2,13 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.6.0] - 2025-01-26
|
||||
### Added
|
||||
- New Static analyzer Stringnalyzer implementation
|
||||
|
||||
### Fixed
|
||||
- Refactoring health check implementation to use configuration file settings
|
||||
|
||||
## [v1.5.1] - 2025-01-25
|
||||
### Added
|
||||
- Support for executing payloads with custom command-line arguments
|
||||
@@ -15,7 +22,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.5.0] - 2025-01-11
|
||||
### Added
|
||||
- LitterBox RedEdr Scanner implementation
|
||||
- New Dynamic analyzer RedEdr Scanner implementation
|
||||
- Added LICENSE file
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -36,6 +36,12 @@ analysis:
|
||||
command: "{tool_path} -m -r -f {file_path}"
|
||||
timeout: 120
|
||||
|
||||
stringnalyzer:
|
||||
enabled: true
|
||||
tool_path: ".\\Scanners\\Stringnalyzer\\Stringnalyzer.exe "
|
||||
command: "{tool_path} --wide -f {file_path}"
|
||||
timeout: 120
|
||||
|
||||
dynamic:
|
||||
yara:
|
||||
enabled: true
|
||||
|
||||
@@ -58,20 +58,20 @@ Features include:
|
||||
- Injection technique analysis
|
||||
- Sleep pattern monitoring
|
||||
- Collect Windows telemetry via ETW
|
||||
- PE integrity verification
|
||||
|
||||
## Integrated Tools
|
||||
|
||||
### Static Analysis Suite
|
||||
- [YARA](https://github.com/elastic/protections-artifacts/tree/main/yara) - Pattern matching and signature detection
|
||||
- [CheckPlz](https://github.com/BlackSnufkin/CheckPlz) - AV detection testing
|
||||
- [Stringnalyzer](https://github.com/BlackSnufkin/Rusty-Playground/Stringnalyzer) - Payload Strings analyzer (Note: May produce false positives)
|
||||
|
||||
### Dynamic Analysis Suite
|
||||
- [YARA](https://github.com/elastic/protections-artifacts/tree/main/yara) (memory scanning) - Runtime pattern detection
|
||||
- [PE-Sieve](https://github.com/hasherezade/pe-sieve) - Detecting and dumping in-memory malware implants and advanced process injection techniques
|
||||
- [Moneta](https://github.com/forrest-orr/moneta) - Usermode memory analysis tool to detect malware IOCs
|
||||
- [Patriot](https://github.com/BlackSnufkin/patriot) - Detecting various kinds of in-memory stealth techniques
|
||||
- [RedEdr](https://github.com/dobin/RedEdr) - Collect Windows telemetry via ETW providers
|
||||
- [RedEdr](https://github.com/dobin/RedEdr) - Collect Windows telemetry via ETW providers (Note: See issue #6)
|
||||
- [Hunt-Sleeping-Beacons](https://github.com/thefLink/Hunt-Sleeping-Beacons) - Beacon behavior analysis
|
||||
|
||||
## Web Endpoint Reference
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,5 @@
|
||||
# app/analyzers/manager.py
|
||||
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
@@ -12,6 +11,7 @@ from abc import ABC, abstractmethod
|
||||
# Import analyzers
|
||||
from .static.yara_analyzer import YaraStaticAnalyzer
|
||||
from .static.checkplz_analyzer import CheckPlzAnalyzer
|
||||
from .static.stringnalyzer_analyzer import StringsAnalyzer
|
||||
from .dynamic.yara_analyzer import YaraDynamicAnalyzer
|
||||
from .dynamic.pe_sieve_analyzer import PESieveAnalyzer
|
||||
from .dynamic.moneta_analyzer import MonetaAnalyzer
|
||||
@@ -32,7 +32,8 @@ class AnalysisManager:
|
||||
# Define analyzer mappings
|
||||
STATIC_ANALYZERS = {
|
||||
'yara': YaraStaticAnalyzer,
|
||||
'checkplz': CheckPlzAnalyzer
|
||||
'checkplz': CheckPlzAnalyzer,
|
||||
'stringnalyzer': StringsAnalyzer
|
||||
}
|
||||
|
||||
DYNAMIC_ANALYZERS = {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
from .base import StaticAnalyzer
|
||||
|
||||
class StringsAnalyzer(StaticAnalyzer):
|
||||
def analyze(self, file_path):
|
||||
"""
|
||||
Analyzes a file using strings analysis tool specified in the config.
|
||||
"""
|
||||
try:
|
||||
tool_config = self.config['analysis']['static']['stringnalyzer']
|
||||
command = tool_config['command'].format(
|
||||
tool_path=os.path.abspath(tool_config['tool_path']),
|
||||
file_path=os.path.abspath(file_path)
|
||||
)
|
||||
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
cwd=os.path.dirname(os.path.abspath(tool_config['tool_path']))
|
||||
)
|
||||
|
||||
stdout, stderr = process.communicate(timeout=tool_config.get('timeout', 300))
|
||||
|
||||
# Parse the JSON output
|
||||
results = self._parse_output(stdout)
|
||||
|
||||
self.results = {
|
||||
'status': 'completed' if process.returncode == 0 else 'failed',
|
||||
'scan_info': {
|
||||
'target': file_path,
|
||||
'tool': 'Stringnalyzer'
|
||||
},
|
||||
'findings': results,
|
||||
'errors': stderr if stderr else None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.results = {
|
||||
'status': 'error',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _parse_output(self, output):
|
||||
"""
|
||||
Parse the strings tool JSON output into structured data.
|
||||
Returns a dictionary containing the parsed results.
|
||||
"""
|
||||
try:
|
||||
# Try to parse the JSON output
|
||||
results = json.loads(output)
|
||||
|
||||
# Ensure all expected fields are present, initialize if missing
|
||||
default_fields = {
|
||||
'file_path': None,
|
||||
'total_strings': 0,
|
||||
'all_strings': [],
|
||||
'found_error_messages': [],
|
||||
'found_functions': [],
|
||||
'found_url': [],
|
||||
'found_dll': [],
|
||||
'found_ip': [],
|
||||
'found_path': [],
|
||||
'found_file': [],
|
||||
'found_commands': [],
|
||||
'found_suspicious_strings': [],
|
||||
'found_suspicious_functions': [],
|
||||
'found_network_indicators': [],
|
||||
'found_registry_keys': [],
|
||||
'found_interesting_strings': [],
|
||||
'found_file_operations': [],
|
||||
'found_emails': [],
|
||||
'found_domains': []
|
||||
}
|
||||
|
||||
# Update default fields with actual results
|
||||
for key in default_fields:
|
||||
if key not in results:
|
||||
results[key] = default_fields[key]
|
||||
|
||||
return results
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return {
|
||||
'error': f'Failed to parse JSON output: {str(e)}',
|
||||
**default_fields
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'error': f'Unexpected error parsing output: {str(e)}',
|
||||
**default_fields
|
||||
}
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Cleanup any temporary files or processes.
|
||||
"""
|
||||
pass
|
||||
+26
-13
@@ -724,7 +724,7 @@ def register_routes(app):
|
||||
analysis_config = config.get('analysis', {})
|
||||
issues = []
|
||||
|
||||
# Check upload folder accessibility
|
||||
# Simple upload folder check
|
||||
upload_folder = upload_config.get('upload_folder')
|
||||
if not upload_folder:
|
||||
app.logger.warning("Upload folder path is not configured.")
|
||||
@@ -732,9 +732,6 @@ def register_routes(app):
|
||||
elif not os.path.isdir(upload_folder):
|
||||
app.logger.warning(f"Upload folder does not exist: {upload_folder}")
|
||||
issues.append(f"Upload folder does not exist: {upload_folder}")
|
||||
elif not os.access(upload_folder, os.W_OK):
|
||||
app.logger.warning(f"Upload folder is not writable: {upload_folder}")
|
||||
issues.append(f"Upload folder is not writable: {upload_folder}")
|
||||
|
||||
# Check static and dynamic analysis tools
|
||||
def check_analysis_tool(section, tool_name):
|
||||
@@ -746,17 +743,33 @@ def register_routes(app):
|
||||
issues.append(f"{tool_name}: tool path not configured")
|
||||
elif not os.path.isfile(tool_path):
|
||||
issues.append(f"{tool_name}: tool not found at {tool_path}")
|
||||
elif not os.access(tool_path, os.X_OK):
|
||||
issues.append(f"{tool_name}: tool not executable at {tool_path}")
|
||||
|
||||
rules_path = tool_config.get('rules_path')
|
||||
if rules_path and not os.path.isfile(rules_path):
|
||||
issues.append(f"{tool_name}: rules not found at {rules_path}")
|
||||
|
||||
for tool in ['yara', 'checkplz']:
|
||||
check_analysis_tool(analysis_config.get('static', {}), tool)
|
||||
# Get tools from config instead of hardcoding
|
||||
static_section = analysis_config.get('static', {})
|
||||
dynamic_section = analysis_config.get('dynamic', {})
|
||||
|
||||
for tool in ['yara', 'pe_sieve', 'moneta', 'patriot', 'hsb', 'rededr']:
|
||||
check_analysis_tool(analysis_config.get('dynamic', {}), tool)
|
||||
# Check all configured static tools
|
||||
for tool_name in static_section.keys():
|
||||
check_analysis_tool(static_section, tool_name)
|
||||
|
||||
# Check all configured dynamic tools
|
||||
for tool_name in dynamic_section.keys():
|
||||
check_analysis_tool(dynamic_section, tool_name)
|
||||
|
||||
# Get all enabled tools for configuration response
|
||||
static_tools = {
|
||||
tool: static_section.get(tool, {}).get('enabled', False)
|
||||
for tool in static_section.keys()
|
||||
}
|
||||
|
||||
dynamic_tools = {
|
||||
tool: dynamic_section.get(tool, {}).get('enabled', False)
|
||||
for tool in dynamic_section.keys()
|
||||
}
|
||||
|
||||
status = 'ok' if not issues else 'degraded'
|
||||
app.logger.debug(f"Health check completed. Status: {status}")
|
||||
@@ -764,11 +777,11 @@ def register_routes(app):
|
||||
return jsonify({
|
||||
'status': status,
|
||||
'timestamp': datetime.datetime.now().isoformat(),
|
||||
'upload_folder_accessible': os.path.isdir(upload_folder) and os.access(upload_folder, os.W_OK) if upload_folder else False,
|
||||
'upload_folder_accessible': os.path.isdir(upload_folder) if upload_folder else False,
|
||||
'issues': issues,
|
||||
'configuration': {
|
||||
'static_analysis': {tool: analysis_config.get('static', {}).get(tool, {}).get('enabled', False) for tool in ['yara', 'threatcheck']},
|
||||
'dynamic_analysis': {tool: analysis_config.get('dynamic', {}).get(tool, {}).get('enabled', False) for tool in ['yara', 'pe_sieve', 'moneta']}
|
||||
'static_analysis': static_tools,
|
||||
'dynamic_analysis': dynamic_tools
|
||||
}
|
||||
}), 200 if status == 'ok' else 503
|
||||
|
||||
|
||||
+234
-126
@@ -250,6 +250,32 @@ function formatBytes(bytes) {
|
||||
}
|
||||
|
||||
|
||||
function renderSection(title, items) {
|
||||
if (!items || items.length === 0) return '';
|
||||
|
||||
const displayItems = items.slice(0, 25);
|
||||
const remainingCount = items.length - 25;
|
||||
|
||||
return `
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm font-medium text-gray-300 mb-3">
|
||||
${title} (${items.length})
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
${displayItems.map(item => `
|
||||
<div class="text-sm text-gray-400 font-mono break-all bg-gray-900/50 p-2 rounded">
|
||||
${item}
|
||||
</div>
|
||||
`).join('')}
|
||||
${remainingCount > 0 ? `
|
||||
<div class="text-sm text-gray-500 mt-2 p-2">
|
||||
... and ${remainingCount} more items
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Tools Registry Object (keeping reference to tools)
|
||||
const tools = {
|
||||
yara: {
|
||||
@@ -421,6 +447,214 @@ const tools = {
|
||||
},
|
||||
},
|
||||
|
||||
checkplz: {
|
||||
element: document.getElementById('threatCheckResults'),
|
||||
statsElement: document.getElementById('threatCheckStats'),
|
||||
render: (results) => {
|
||||
if (results.status === 'error') {
|
||||
tools.checkplz.element.innerHTML = `
|
||||
<div class="bg-red-500/10 border border-red-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center space-x-2 text-red-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${results.error}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const findings = results.findings || {};
|
||||
const scanResults = findings.scan_results || {};
|
||||
const isClean = !findings.initial_threat && !scanResults.detection_offset;
|
||||
|
||||
// Stats Section
|
||||
tools.checkplz.statsElement.innerHTML = `
|
||||
<div class="grid grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-gray-900/30 rounded-lg border ${isClean ? 'border-green-500/30' : 'border-red-500/30'} p-4">
|
||||
<div class="text-sm text-gray-500">Status</div>
|
||||
<div class="text-base font-semibold ${isClean ? 'text-green-500' : 'text-red-500'}">
|
||||
${isClean ? 'Clean' : (findings.initial_threat || 'Unknown Threat')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm text-gray-500">Scan Duration</div>
|
||||
<div class="text-xl font-semibold text-gray-400">
|
||||
${typeof scanResults.scan_duration === 'number' ? scanResults.scan_duration.toFixed(3) + 's' : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm text-gray-500">Search Iterations</div>
|
||||
<div class="text-xl font-semibold text-gray-400">
|
||||
${scanResults.search_iterations || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let html = '';
|
||||
|
||||
// File Information Section
|
||||
html += `
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4 mb-6">
|
||||
<div class="text-sm font-medium text-gray-300 mb-3">File Information</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">File Path</div>
|
||||
<div class="text-sm text-gray-300 font-mono break-all">
|
||||
${scanResults.file_path || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">File Size</div>
|
||||
<div class="text-sm text-gray-300">
|
||||
${scanResults.file_size || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Clean State Message
|
||||
if (isClean) {
|
||||
html += `
|
||||
<div class="flex flex-col items-center justify-center py-8 bg-green-500/10 rounded-lg border border-green-500/20">
|
||||
<svg class="w-12 h-12 text-green-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span class="text-green-500 font-medium">No threats detected - File is clean</span>
|
||||
<span class="text-green-400 text-sm mt-1">Security scan completed successfully</span>
|
||||
</div>`;
|
||||
} else {
|
||||
// Detection Details Section
|
||||
if (scanResults.detection_offset) {
|
||||
html += `
|
||||
<div class="bg-red-500/10 rounded-lg border border-red-900/20 p-4 mb-6">
|
||||
<div class="flex items-center space-x-2 mb-3">
|
||||
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-red-500">Threat Detection Details</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">Detection Offset</div>
|
||||
<div class="text-sm text-red-500 font-mono">${scanResults.detection_offset}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">Relative Location</div>
|
||||
<div class="text-sm text-red-500">${scanResults.relative_location}</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-sm text-gray-500">Final Threat Detection</div>
|
||||
<div class="text-sm text-red-500">${scanResults.final_threat_detection}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Hex Dump Section (only shown when threats are detected)
|
||||
if (scanResults.hex_dump) {
|
||||
html += `
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="text-sm font-medium text-gray-300">Showing ±128 bytes around detection point</span>
|
||||
<button
|
||||
onclick="navigator.clipboard.writeText(this.parentElement.nextElementSibling.textContent)"
|
||||
class="px-2 py-1 text-xs text-gray-400 hover:text-white border border-gray-700 rounded hover:border-gray-600 transition-colors">
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre class="text-base font-mono text-gray-400 whitespace-pre-wrap overflow-x-auto p-4 bg-gray-900/50 rounded-lg leading-relaxed">${scanResults.hex_dump}</pre>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
tools.checkplz.element.innerHTML = html;
|
||||
}
|
||||
},
|
||||
|
||||
stringnalyzer: {
|
||||
element: document.getElementById('StringnalyzerResults'),
|
||||
statsElement: document.getElementById('StringnalyzerStats'),
|
||||
render: (results) => {
|
||||
if (results.status === 'error') {
|
||||
tools.stringnalyzer.element.innerHTML = `
|
||||
<div class="bg-red-500/10 border border-red-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center space-x-2 text-red-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${results.error}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const findings = results.findings || {};
|
||||
|
||||
// Stats Section with Download Button
|
||||
tools.stringnalyzer.statsElement.innerHTML = `
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm text-gray-500">File Path</div>
|
||||
<div class="text-sm text-gray-300 font-mono break-all">
|
||||
${findings.file_path || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm text-gray-500">Total Strings</div>
|
||||
<div class="text-xl font-semibold text-gray-400">
|
||||
${findings.total_strings || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Attach click event to the Download Button
|
||||
const downloadButton = document.getElementById('downloadResultsBtn');
|
||||
if (downloadButton) {
|
||||
downloadButton.addEventListener('click', () => {
|
||||
const filePath = results.findings.file_path || 'unknown';
|
||||
const fullFileName = filePath.split('\\').pop().split('/').pop(); // Get file name from path
|
||||
const actualFileName = fullFileName.split('_').pop(); // Get everything after last underscore
|
||||
const downloadName = `stringnalyzer_${actualFileName.replace('.exe', '')}.json`;
|
||||
|
||||
const blob = new Blob([JSON.stringify(results, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = downloadName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
// All Findings Sections
|
||||
html += `
|
||||
<div class="space-y-4">
|
||||
${renderSection('Suspicious Strings', findings.found_suspicious_strings)}
|
||||
${renderSection('Functions Referenced', findings.found_suspicious_functions)}
|
||||
${renderSection('URLs Found', findings.found_url)}
|
||||
${renderSection('DLLs Referenced', findings.found_dll)}
|
||||
${renderSection('IP Addresses', findings.found_ip)}
|
||||
${renderSection('Paths Found', findings.found_path)}
|
||||
${renderSection('Files Referenced', findings.found_file)}
|
||||
${renderSection('Commands Found', findings.found_commands)}
|
||||
${renderSection('Functions', findings.found_functions)}
|
||||
${renderSection('Error Messages', findings.found_error_messages)}
|
||||
${renderSection('Network Indicators', findings.found_network_indicators)}
|
||||
${renderSection('Registry Keys', findings.found_registry_keys)}
|
||||
${renderSection('File Operations', findings.found_file_operations)}
|
||||
${renderSection('Email Addresses', findings.found_emails)}
|
||||
${renderSection('Domains', findings.found_domains)}
|
||||
${renderSection('Interesting Strings', findings.found_interesting_strings)}
|
||||
</div>`;
|
||||
|
||||
tools.stringnalyzer.element.innerHTML = html;
|
||||
}
|
||||
},
|
||||
|
||||
pe_sieve: {
|
||||
element: document.getElementById('peSieveResults'),
|
||||
render: (results) => {
|
||||
@@ -686,132 +920,6 @@ const tools = {
|
||||
}
|
||||
},
|
||||
|
||||
checkplz: {
|
||||
element: document.getElementById('threatCheckResults'),
|
||||
statsElement: document.getElementById('threatCheckStats'),
|
||||
render: (results) => {
|
||||
if (results.status === 'error') {
|
||||
tools.checkplz.element.innerHTML = `
|
||||
<div class="bg-red-500/10 border border-red-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center space-x-2 text-red-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${results.error}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const findings = results.findings || {};
|
||||
const scanResults = findings.scan_results || {};
|
||||
const isClean = !findings.initial_threat && !scanResults.detection_offset;
|
||||
|
||||
// Stats Section
|
||||
tools.checkplz.statsElement.innerHTML = `
|
||||
<div class="grid grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-gray-900/30 rounded-lg border ${isClean ? 'border-green-500/30' : 'border-red-500/30'} p-4">
|
||||
<div class="text-sm text-gray-500">Status</div>
|
||||
<div class="text-base font-semibold ${isClean ? 'text-green-500' : 'text-red-500'}">
|
||||
${isClean ? 'Clean' : (findings.initial_threat || 'Unknown Threat')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm text-gray-500">Scan Duration</div>
|
||||
<div class="text-xl font-semibold text-gray-400">
|
||||
${typeof scanResults.scan_duration === 'number' ? scanResults.scan_duration.toFixed(3) + 's' : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="text-sm text-gray-500">Search Iterations</div>
|
||||
<div class="text-xl font-semibold text-gray-400">
|
||||
${scanResults.search_iterations || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let html = '';
|
||||
|
||||
// File Information Section
|
||||
html += `
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4 mb-6">
|
||||
<div class="text-sm font-medium text-gray-300 mb-3">File Information</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">File Path</div>
|
||||
<div class="text-sm text-gray-300 font-mono break-all">
|
||||
${scanResults.file_path || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">File Size</div>
|
||||
<div class="text-sm text-gray-300">
|
||||
${scanResults.file_size || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Clean State Message
|
||||
if (isClean) {
|
||||
html += `
|
||||
<div class="flex flex-col items-center justify-center py-8 bg-green-500/10 rounded-lg border border-green-500/20">
|
||||
<svg class="w-12 h-12 text-green-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span class="text-green-500 font-medium">No threats detected - File is clean</span>
|
||||
<span class="text-green-400 text-sm mt-1">Security scan completed successfully</span>
|
||||
</div>`;
|
||||
} else {
|
||||
// Detection Details Section
|
||||
if (scanResults.detection_offset) {
|
||||
html += `
|
||||
<div class="bg-red-500/10 rounded-lg border border-red-900/20 p-4 mb-6">
|
||||
<div class="flex items-center space-x-2 mb-3">
|
||||
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-red-500">Threat Detection Details</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">Detection Offset</div>
|
||||
<div class="text-sm text-red-500 font-mono">${scanResults.detection_offset}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">Relative Location</div>
|
||||
<div class="text-sm text-red-500">${scanResults.relative_location}</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-sm text-gray-500">Final Threat Detection</div>
|
||||
<div class="text-sm text-red-500">${scanResults.final_threat_detection}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Hex Dump Section (only shown when threats are detected)
|
||||
if (scanResults.hex_dump) {
|
||||
html += `
|
||||
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="text-sm font-medium text-gray-300">Showing ±128 bytes around detection point</span>
|
||||
<button
|
||||
onclick="navigator.clipboard.writeText(this.parentElement.nextElementSibling.textContent)"
|
||||
class="px-2 py-1 text-xs text-gray-400 hover:text-white border border-gray-700 rounded hover:border-gray-600 transition-colors">
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre class="text-base font-mono text-gray-400 whitespace-pre-wrap overflow-x-auto p-4 bg-gray-900/50 rounded-lg leading-relaxed">${scanResults.hex_dump}</pre>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
tools.checkplz.element.innerHTML = html;
|
||||
}
|
||||
},
|
||||
|
||||
patriot: {
|
||||
element: document.getElementById('patriotResults'),
|
||||
statsElement: document.getElementById('patriotStats'),
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
<!-- Results Section -->
|
||||
<div class="bg-black/60 backdrop-blur-sm rounded-xl border border-gray-800 shadow-lg">
|
||||
<div class="p-6">
|
||||
@@ -137,6 +136,9 @@
|
||||
<button class="tab-button text-base px-4 py-2 text-gray-300 hover:text-white border-b-2" data-tab="threatCheckResultsTab">
|
||||
CheckPlz
|
||||
</button>
|
||||
<button class="tab-button text-base px-4 py-2 text-gray-300 hover:text-white border-b-2" data-tab="StringnalyzerResultsTab">
|
||||
Stringnalyzer
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="tab-button text-base px-4 py-2 text-gray-300 hover:text-white border-b-2" data-tab="summaryTab">
|
||||
Summary
|
||||
@@ -220,6 +222,20 @@
|
||||
<div id="threatCheckStats" class="flex space-x-4 mb-6"></div>
|
||||
<div id="threatCheckResults" class="space-y-4"></div>
|
||||
</div>
|
||||
<div id="StringnalyzerResultsTab" class="tab-content">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xl font-medium text-gray-100">Stringnalyzer Scan Results</h3>
|
||||
<button id="downloadResultsBtn" class="px-3 py-1 text-sm text-gray-300 hover:text-white border border-gray-700 rounded hover:border-gray-600 transition-colors flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||
</svg>
|
||||
<span>Download Results</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-base text-gray-500 mb-6">Payload String Analyzer</p>
|
||||
<div id="StringnalyzerStats" class="flex space-x-4 mb-6"></div>
|
||||
<div id="StringnalyzerResults" class="space-y-4"></div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Summary Tab for Dynamic Analysis -->
|
||||
<div id="summaryTab" class="tab-content">
|
||||
|
||||
Reference in New Issue
Block a user