Files
litterbox/app/static/js/results.js
T
2025-01-26 12:56:03 -08:00

1872 lines
104 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// app/static/js/resutls.js
// UI Components and Constants
const UI = {
icons: {
running: `
<svg class="w-6 h-6 text-red-500 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>`,
complete: `
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>`,
error: `
<svg class="w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>`
}
};
// Tab Manager
class TabManager {
constructor() {
this.tabs = document.querySelectorAll('.tab-button');
this.tabContents = document.querySelectorAll('.tab-content');
this.setupTabs();
}
setupTabs() {
this.tabs.forEach(tab => {
tab.addEventListener('click', () => this.switchTab(tab));
});
// Activate first tab by default
if (this.tabs.length > 0) {
this.tabs[0].click();
}
}
switchTab(selectedTab) {
const target = selectedTab.dataset.tab;
// Hide all tab content and deactivate tabs
this.tabContents.forEach(content => content.classList.add('hidden'));
this.tabs.forEach(tab => tab.classList.remove('border-red-500', 'text-white'));
// Show target content and activate tab
document.getElementById(target).classList.remove('hidden');
selectedTab.classList.add('border-red-500', 'text-white');
}
}
// Analysis Type Handler
class AnalysisTypeHandler {
constructor() {
this.setupAnalysisType();
}
isNumeric(str) {
return /^\d+$/.test(str);
}
setupAnalysisType() {
const pathSegments = window.location.pathname.split('/').filter(segment => segment.length > 0);
const identifier = pathSegments[pathSegments.length - 1];
// If PID, hide static analysis button
if (this.isNumeric(identifier)) {
const staticButton = document.getElementById('staticAnalysisButton');
if (staticButton) {
staticButton.style.display = 'none';
}
}
}
}
// Analysis Core Logic
class AnalysisCore {
constructor() {
this.elements = {
analysisStatus: document.getElementById('analysisStatus'),
statusIcon: document.getElementById('statusIcon'),
analysisTimer: document.getElementById('analysisTimer'),
stageLine: document.getElementById('stageLine'),
analysisStage: document.getElementById('analysisStage')
};
this.startTime = Date.now();
this.timerInterval = null;
const pathParts = window.location.pathname.split('/');
this.analysisType = pathParts[2];
this.fileHash = pathParts[3];
}
updateTimer() {
const elapsed = Date.now() - this.startTime;
const minutes = Math.floor(elapsed / 60000);
const seconds = Math.floor((elapsed % 60000) / 1000);
const milliseconds = elapsed % 1000;
this.elements.analysisTimer.textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(3, '0')}`;
// Update summary scan duration
document.getElementById('scanDuration').textContent = this.elements.analysisTimer.textContent;
}
startTimer() {
this.timerInterval = setInterval(() => this.updateTimer(), 1000);
}
stopTimer() {
clearInterval(this.timerInterval);
}
updateStatusIcon(status) {
this.elements.statusIcon.innerHTML = UI.icons[status] || '';
}
updateStageToComplete() {
this.elements.stageLine.classList.remove('bg-gray-800');
this.elements.stageLine.classList.add('bg-green-500/20');
this.elements.analysisStage.innerHTML = `
<div class="w-10 h-10 rounded-full bg-green-500/10 border-2 border-green-500 flex items-center justify-center">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<span class="text-gray-400">Analysis</span>`;
}
async startAnalysis() {
this.updateStatusIcon('running');
this.elements.analysisStatus.textContent = 'Running analysis...';
this.startTimer();
try {
// Retrieve the arguments from localStorage
const savedArgs = localStorage.getItem('analysisArgs');
const args = savedArgs ? JSON.parse(savedArgs) : []; // Default to an empty array if no args are saved
const response = await fetch(`/analyze/${this.analysisType}/${this.fileHash}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
args // Dynamically include the arguments retrieved from storage
})
});
const data = await response.json();
// Handle early termination
if (data.status === 'early_termination') {
this.updateTimer();
this.stopTimer();
this.updateStatusIcon('error');
this.elements.analysisStatus.textContent = data.error || 'Process terminated early';
// Create a minimal results object for summary
const results = {
status: 'early_termination',
analysis_metadata: {
early_termination: true,
total_duration: data.details?.termination_time || 0
}
};
if (tools.summary) {
tools.summary.render(results);
}
return;
}
// Normal completion flow
this.updateTimer();
this.stopTimer();
this.updateStatusIcon('complete');
this.elements.analysisStatus.textContent = 'Analysis completed';
this.updateStageToComplete();
// First update the summary with all results
if (tools.summary && data.results) {
tools.summary.render(data.results);
}
// Then process individual tool results
Object.entries(data.results || {}).forEach(([toolKey, results]) => {
if (results && tools[toolKey] && toolKey !== 'summary') {
tools[toolKey].render(results);
}
});
} catch (error) {
this.stopTimer();
this.updateStatusIcon('error');
this.elements.analysisStatus.textContent = `Error: ${error.message}`;
}
}
}
// Modal Handler
class ModalHandler {
constructor() {
this.modal = document.getElementById('dynamicWarningModal');
this.dialog = this.modal.querySelector('.bg-gray-900');
this.setupListeners();
}
setupListeners() {
this.modal.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.hide();
}
});
}
show() {
this.modal.classList.remove('hidden');
setTimeout(() => {
this.dialog.classList.remove('scale-95', 'opacity-0');
}, 50);
}
hide() {
this.dialog.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
this.modal.classList.add('hidden');
}, 300);
}
}
function getEventTypeColor(type) {
const colors = {
'Process Start': 'bg-green-500',
'Child Process': 'bg-yellow-500',
'DLL Load': 'bg-blue-500',
'Image Load': 'bg-purple-500',
'Thread Start': 'bg-pink-500'
};
return colors[type] || 'bg-gray-500';
}
function formatBytes(bytes) {
if (!bytes) return 'N/A';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
}
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: {
element: document.getElementById('yaraResults'),
statsElement: document.getElementById('yaraStats'),
render: (results) => {
if (results.status === 'error') {
tools.yara.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;
}
// Ensure matches is always an array
const matches = Array.isArray(results.matches) ? results.matches : [];
const matchCount = matches.length;
const isClean = matchCount === 0;
// Stats Section
const highestSeverity = Math.max(...matches.map(m => parseInt(m.metadata?.severity || 0)));
tools.yara.statsElement.innerHTML = `
<div class="grid grid-cols-3 gap-4 mb-4">
<div class="px-4 py-3 bg-gray-900/50 rounded-lg border ${isClean ? 'border-green-500/30' : 'border-red-500/30'}">
<div class="text-sm text-gray-500">Rule Matches</div>
<div class="text-2xl font-semibold ${isClean ? 'text-green-500' : 'text-red-500'}">${matchCount}</div>
</div>
<div class="px-4 py-3 bg-gray-900/50 rounded-lg border border-gray-800">
<div class="text-sm text-gray-500">Total Strings</div>
<div class="text-2xl font-semibold text-gray-400">
${matches.reduce((acc, match) => acc + (Array.isArray(match.strings) ? match.strings.length : 0), 0)}
</div>
</div>
<div class="px-4 py-3 bg-gray-900/50 rounded-lg border ${isClean ? 'border-green-500/30' : highestSeverity > 50 ? 'border-red-500/30' : 'border-yellow-500/30'}">
<div class="text-sm text-gray-500">Status</div>
<div class="text-2xl font-semibold ${isClean ? 'text-green-500' : highestSeverity > 50 ? 'text-red-500' : 'text-yellow-500'}">
${isClean ? 'Clean' : `Severity ${highestSeverity}`}
</div>
</div>
</div>`;
// Start building HTML content
let html = '';
// Scan Info Section
if (results.scan_info && results.scan_info.target) {
html += `
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4 mb-4">
<div class="text-sm text-gray-400">
<div class="font-medium text-gray-300 mb-1">Target Information</div>
<div class="font-mono break-all">${results.scan_info.target}</div>
${results.scan_info.rules_file ?
`<div class="mt-1 text-gray-500">Rules: ${results.scan_info.rules_file}</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">All YARA rules passed successfully</span>
</div>`;
tools.yara.element.innerHTML = html;
return;
}
// Sort matches by severity for threat cases
const sortedMatches = [...matches].sort((a, b) =>
(parseInt(b.metadata?.severity) || 0) - (parseInt(a.metadata?.severity) || 0)
);
// Format metadata labels
const formatMetadataLabel = (key) => {
const labels = {
'threat_name': 'Threat',
'rule_filepath': 'Rule File',
'creation_date': 'Created',
'id': 'Rule ID'
};
return labels[key] || key;
};
// Matches Section
html += sortedMatches.map((match, index) => {
const strings = Array.isArray(match.strings) ? match.strings : [];
const severity = parseInt(match.metadata?.severity || 0);
const metadataOrder = ['threat_name', 'rule_filepath', 'creation_date', 'id'];
return `
<div class="bg-gray-900/30 rounded-lg border ${severity > 50 ? 'border-red-500/20' : 'border-yellow-500/20'}
hover:${severity > 50 ? 'border-red-500/30' : 'border-yellow-500/30'} transition-colors mb-4">
<div class="p-4">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
<div class="w-6 h-6 flex items-center justify-center ${severity > 50 ? 'bg-red-500/10' : 'bg-yellow-500/10'} rounded">
<span class="${severity > 50 ? 'text-red-500' : 'text-yellow-500'} text-xs">#${index + 1}</span>
</div>
<span class="${severity > 50 ? 'text-red-500' : 'text-yellow-500'} text-sm">${match.rule}</span>
</div>
<div class="flex items-center space-x-2">
<span class="px-1.5 py-0.5 text-xs ${severity > 50 ? 'bg-red-500/10 text-red-500' : 'bg-yellow-500/10 text-yellow-500'} rounded">
Severity: ${severity}
</span>
</div>
</div>
<!-- Metadata Section -->
${Object.keys(match.metadata || {}).length > 0 ? `
<div class="mb-6 pl-11">
<div class="p-4 bg-gray-900/50 rounded-lg border border-gray-800">
<div class="grid grid-cols-2 gap-2">
${metadataOrder
.filter(key => match.metadata[key])
.map(key => `
<div class="bg-gray-900/30 rounded p-1.5">
<div class="flex justify-between items-center">
<span class="text-gray-500 text-xs">${formatMetadataLabel(key)}:</span>
<span class="text-gray-300 ml-2 text-xs font-mono">${match.metadata[key]}</span>
</div>
</div>
`).join('')}
</div>
</div>
</div>
` : ''}
<!-- Strings Section -->
${strings.length > 0 ? `
<div class="pl-11">
<div class="text-sm font-medium text-gray-400 mb-3">String Matches</div>
<div class="space-y-3">
${strings.map(str => `
<div class="bg-gray-900/30 rounded p-3">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
<span class="text-xs text-gray-500 font-mono">${str.offset}</span>
${str.identifier ?
`<span class="text-xs px-2 py-0.5 bg-gray-800 rounded text-gray-400">${str.identifier}</span>` : ''}
${str.data_type ?
`<span class="text-xs px-2 py-0.5 bg-gray-800 rounded text-gray-400">${str.data_type}</span>` : ''}
</div>
<button
onclick="navigator.clipboard.writeText('${str.data.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '&quot;')}')"
class="px-2 py-0.5 text-xs text-gray-500 hover:text-gray-300 transition-colors">
Copy
</button>
</div>
<pre class="text-sm text-gray-300 font-mono whitespace-pre-wrap break-all max-h-32 overflow-y-auto">${str.data}</pre>
</div>
`).join('')}
</div>
</div>
` : ''}
</div>
</div>`;
}).join('');
tools.yara.element.innerHTML = html;
},
},
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) => {
if (results.status === 'error') {
tools.pe_sieve.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 isClean = findings.total_suspicious === 0;
// Stats Section with Clean State
const statsHtml = `
<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-2xl font-semibold ${isClean ? 'text-green-500' : 'text-red-500'}">
${isClean ? 'Clean' : 'Suspicious'}
</div>
</div>
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="text-sm text-gray-500">Total Scanned</div>
<div class="text-2xl font-semibold text-gray-300">${findings.total_scanned}</div>
</div>
<div class="bg-gray-900/30 rounded-lg border ${isClean ? 'border-gray-800' : 'border-red-500/30'} p-4">
<div class="text-sm text-gray-500">Suspicious</div>
<div class="text-2xl font-semibold ${isClean ? 'text-gray-300' : 'text-red-500'}">
${findings.total_suspicious}
</div>
</div>
</div>`;
// Clean State Message
if (isClean) {
tools.pe_sieve.element.innerHTML = statsHtml + `
<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 suspicious activities detected - Process is clean</span>
<span class="text-green-400 text-sm mt-1">PE analysis completed successfully</span>
</div>`;
return;
}
// Detailed Findings Section (only shown when suspicious activities are detected)
const detailsHtml = `
<div class="grid grid-cols-3 gap-4">
${[
{ label: 'Hooked', value: findings.hooked },
{ label: 'Replaced', value: findings.replaced },
{ label: 'Headers Modified', value: findings.hdrs_modified },
{ label: 'IAT Hooks', value: findings.iat_hooks },
{ label: 'Implanted', value: findings.implanted },
{ label: 'Implanted PE', value: findings.implanted_pe },
{ label: 'Implanted shc', value: findings.implanted_shc },
{ label: 'Unreachable', value: findings.unreachable },
{ label: 'Other', value: findings.other },
]
.map(item => `
<div class="bg-gray-900/30 rounded-lg border ${item.value > 0 ? 'border-red-500/30' : 'border-red-900/10'} p-4">
<div class="text-sm text-gray-500">${item.label}</div>
<div class="text-xl font-semibold ${item.value > 0 ? 'text-red-500' : 'text-gray-300'}">${item.value}</div>
</div>`)
.join('')}
</div>`;
tools.pe_sieve.element.innerHTML = statsHtml + detailsHtml;
// Raw Output Section (only shown when suspicious activities are detected)
if (findings.raw_output) {
tools.pe_sieve.element.innerHTML += `
<div class="mt-6 bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-gray-300">Raw Analysis Output</span>
<button
onclick="navigator.clipboard.writeText(this.parentElement.nextElementSibling.textContent).then(() => { this.textContent = 'Copied!'; setTimeout(() => { this.textContent = 'Copy'; }, 2000); })"
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-xs font-mono text-gray-400 whitespace-pre-wrap overflow-x-auto">${findings.raw_output}</pre>
</div>`;
}
},
},
moneta: {
element: document.getElementById('monetaResults'),
render: (results) => {
if (results.status === 'error') {
tools.moneta.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 isClean = (!findings.total_private_rx && !findings.total_private_rwx &&
!findings.total_modified_code && !findings.total_inconsistent_x &&
!findings.total_heap_executable && !findings.total_modified_pe_header &&
!findings.total_missing_peb && !findings.total_mismatching_peb) ||
(findings.total_unsigned_modules > 0 &&
!findings.total_private_rx && !findings.total_private_rwx &&
!findings.total_modified_code && !findings.total_inconsistent_x &&
!findings.total_heap_executable && !findings.total_modified_pe_header &&
!findings.total_missing_peb && !findings.total_mismatching_peb);
let html = '';
// Process Info Section
if (findings.process_info) {
html += `
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4 mb-4">
<div class="flex items-center justify-between mb-2">
<div class="text-sm text-gray-500">Process Information</div>
${findings.scan_duration ?
`<div class="text-sm text-gray-500">Scan Duration: ${findings.scan_duration.toFixed(2)}s</div>` : ''}
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<div class="text-sm text-gray-500">Process Name</div>
<div class="text-base text-gray-300">${findings.process_info.name}</div>
</div>
<div>
<div class="text-sm text-gray-500">Process ID</div>
<div class="text-base text-gray-300">${findings.process_info.pid}</div>
</div>
<div>
<div class="text-sm text-gray-500">Architecture</div>
<div class="text-base text-gray-300">${findings.process_info.arch}</div>
</div>
<div>
<div class="text-sm text-gray-500">Path</div>
<div class="text-base text-gray-300 truncate" title="${findings.process_info.path}">${findings.process_info.path}</div>
</div>
</div>
</div>`;
}
// Stats Overview with Status
html += `
<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-2xl font-semibold ${isClean ? 'text-green-500' : 'text-red-500'}">
${isClean ? 'Clean' : 'Suspicious'}
</div>
</div>
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="text-sm text-gray-500">Total Regions</div>
<div class="text-2xl font-semibold text-gray-300">${findings.total_regions}</div>
</div>
<div class="bg-gray-900/30 rounded-lg border ${findings.threads.length > 0 ? 'border-blue-500/30' : 'border-gray-800'} p-4">
<div class="text-sm text-gray-500">Threads</div>
<div class="text-2xl font-semibold text-blue-500">${findings.threads.length}</div>
</div>
</div>`;
// For clean scans, show success message and return
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 suspicious activities detected - Process is clean</span>
<span class="text-green-400 text-sm mt-1">
${findings.total_unsigned_modules > 0 ?
`Note: ${findings.total_unsigned_modules} unsigned module(s) found, but no suspicious behavior detected` :
'Memory analysis completed successfully'}
</span>
</div>`;
tools.moneta.element.innerHTML = html;
return;
}
// Detailed Findings - Only shown for suspicious scans
html += `
<div class="grid grid-cols-3 gap-4">
${[
{ label: 'Private RWX', value: findings.total_private_rwx },
{ label: 'Private RX', value: findings.total_private_rx },
{ label: 'Modified Code', value: findings.total_modified_code },
{ label: 'Heap Executable', value: findings.total_heap_executable },
{ label: 'Modified PE Header', value: findings.total_modified_pe_header },
{ label: 'Inconsistent X', value: findings.total_inconsistent_x },
{ label: 'Missing PEB', value: findings.total_missing_peb },
{ label: 'Mismatching PEB', value: findings.total_mismatching_peb },
{ label: 'Unsigned Modules', value: findings.total_unsigned_modules },
]
.map(item => `
<div class="bg-gray-900/30 rounded-lg border ${item.value > 0 ? 'border-red-500/30' : 'border-red-900/10'} p-4">
<div class="text-sm text-gray-500">${item.label}</div>
<div class="text-xl font-semibold ${item.value > 0 ? 'text-red-500' : 'text-gray-300'}">${item.value}</div>
</div>`)
.join('')}
</div>`;
// Suspicious Findings Summary
html += `
<div class="mt-6 bg-red-500/10 rounded-lg border border-red-900/20 p-4">
<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">Suspicious Activity Detected</span>
</div>
<div class="space-y-2 text-sm text-gray-400">
${[
{ condition: findings.total_private_rwx > 0, message: `Critical: Found ${findings.total_private_rwx} private RWX region(s)`, type: 'critical' },
{ condition: findings.total_heap_executable > 0, message: `Critical: Found ${findings.total_heap_executable} executable heap region(s)`, type: 'critical' },
{ condition: findings.total_modified_code > 0, message: `Critical: Detected ${findings.total_modified_code} modified code region(s)`, type: 'critical' },
{ condition: findings.total_modified_pe_header > 0, message: `Critical: Found ${findings.total_modified_pe_header} modified PE header(s)`, type: 'critical' },
{ condition: findings.total_private_rx > 0, message: `Warning: Found ${findings.total_private_rx} private RX region(s)`, type: 'warning' },
{ condition: findings.total_inconsistent_x > 0, message: `Warning: Found ${findings.total_inconsistent_x} region(s) with inconsistent executable permissions`, type: 'warning' },
{ condition: findings.total_missing_peb > 0, message: `Warning: Found ${findings.total_missing_peb} missing PEB module(s)`, type: 'warning' },
{ condition: findings.total_mismatching_peb > 0, message: `Warning: Found ${findings.total_mismatching_peb} mismatching PEB module(s)`, type: 'warning' }
]
.filter(item => item.condition)
.map(item => `
<div class="flex items-center space-x-2">
<svg class="w-4 h-4 ${item.type === 'critical' ? 'text-red-500' : 'text-yellow-500'}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>${item.message}</span>
</div>`)
.join('')}
</div>
</div>`;
// Add raw output for suspicious scans
if (!isClean && findings.raw_output) {
html += `
<div class="mt-6 bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-gray-300">Raw Analysis Output</span>
<button
onclick="navigator.clipboard.writeText(this.parentElement.nextElementSibling.textContent).then(() => { this.textContent = 'Copied!'; setTimeout(() => { this.textContent = 'Copy'; }, 2000); })"
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-xs font-mono text-gray-400 whitespace-pre-wrap overflow-x-auto">${findings.raw_output}</pre>
</div>`;
}
tools.moneta.element.innerHTML = html;
}
},
patriot: {
element: document.getElementById('patriotResults'),
statsElement: document.getElementById('patriotStats'),
render: (results) => {
if (results.status === 'error') {
return tools.patriot.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>`;
}
const data = results.findings;
const processInfo = data.process_info || {};
const memoryStats = data.memory_stats || {};
const scanSummary = data.scan_summary || {};
const detailedFindings = data.findings || [];
const isClean = detailedFindings.length === 0;
tools.patriot.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-2xl font-semibold ${isClean ? 'text-green-500' : 'text-red-500'}">
${isClean ? 'Clean' : 'Suspicious'}
</div>
</div>
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="text-sm text-gray-500">Memory Regions</div>
<div class="text-2xl font-semibold text-gray-300">${memoryStats.total_regions || 0}</div>
</div>
<div class="bg-gray-900/30 rounded-lg border ${isClean ? 'border-gray-800' : 'border-red-500/30'} p-4">
<div class="text-sm text-gray-500">Total Findings</div>
<div class="text-2xl font-semibold ${isClean ? 'text-gray-300' : 'text-red-500'}">${scanSummary.total_findings || 0}</div>
</div>
</div>`;
let 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">Process Information</div>
<div class="grid grid-cols-2 gap-4">
<div>
<div class="text-sm text-gray-500">PID</div>
<div class="text-sm text-gray-300">${processInfo.pid || 'N/A'}</div>
</div>
<div>
<div class="text-sm text-gray-500">Process Name</div>
<div class="text-sm text-gray-300">${processInfo.process_name || 'N/A'}</div>
</div>
<div>
<div class="text-sm text-gray-500">Elevation Status</div>
<div class="text-sm text-gray-300">${processInfo.elevation_status || 'N/A'}</div>
</div>
<div>
<div class="text-sm text-gray-500">Memory Usage</div>
<div class="text-sm text-gray-300">
Private: ${memoryStats.private_memory} MB |
Executable: ${memoryStats.executable_memory} MB
</div>
</div>
</div>
</div>`;
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 - Process is clean</span>
<span class="text-green-400 text-sm mt-1">Scan completed in ${scanSummary.duration || 0} seconds</span>
</div>`;
} else {
// Findings Type Summary
const findingTypes = scanSummary.findings_by_type || {};
html += `
<div class="mb-6">
<div class="grid grid-cols-3 gap-4">
${Object.entries(findingTypes).map(([type, count]) => `
<div class="bg-gray-900/30 rounded-lg border border-red-500/30 p-4">
<div class="text-sm text-gray-500">${type}</div>
<div class="text-xl font-semibold text-red-500">${count}</div>
</div>
`).join('')}
</div>
</div>
<div class="space-y-4">
${detailedFindings.map(finding => `
<div class="bg-gray-900/30 rounded-lg border border-red-500/20 hover:border-red-500/30 transition-colors">
<div class="p-4">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
<div class="w-6 h-6 flex items-center justify-center bg-red-500/10 rounded">
<span class="text-red-500 text-xs">#${finding.finding_number}</span>
</div>
<span class="text-red-500 text-sm">${finding.type}</span>
</div>
<span class="text-xs text-gray-500">${finding.timestamp}</span>
</div>
<div class="pl-8 space-y-2">
<div class="flex items-center space-x-2 text-sm">
<span class="text-gray-500">Process:</span>
<span class="text-gray-300">${finding.process_name} (PID: ${finding.pid})</span>
</div>
<div class="flex items-center space-x-2 text-sm">
<span class="text-gray-500">Level:</span>
<span class="text-gray-300">${finding.level}</span>
</div>
<div class="text-sm">
<div class="text-gray-500 mb-1">Details:</div>
<div class="text-gray-300 font-mono">${finding.details}</div>
</div>
${finding.parsed_details ? `
<div class="text-sm">
<div class="text-gray-500 mb-1">Parsed Details:</div>
<div class="grid grid-cols-2 gap-2">
${Object.entries(finding.parsed_details).map(([key, value]) => `
<div class="bg-gray-900/50 rounded p-2">
<span class="text-gray-500">${key}:</span>
<span class="text-gray-300 font-mono ml-2">${value}</span>
</div>
`).join('')}
</div>
</div>
` : ''}
${finding.module_information ? `
<div class="mt-4 bg-gray-900/50 rounded p-3">
<div class="text-sm text-gray-500 mb-2">Module Information</div>
${Object.entries(finding.module_information).map(([key, value]) => `
<div class="flex items-center space-x-2 text-sm">
<span class="text-gray-500">${key}:</span>
<span class="text-gray-300 font-mono">${value}</span>
</div>
`).join('')}
</div>
` : ''}
</div>
</div>
</div>
`).join('')}
</div>`;
}
tools.patriot.element.innerHTML = html;
}
},
hsb: {
element: document.getElementById('hsbResults'),
statsElement: document.getElementById('hsbStats'),
render: (results) => {
// 1) Handle errors
if (results.status === 'error') {
return tools.hsb.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>`;
}
// 2) Pull parsed data from the Python output
const data = results.findings || {};
const summary = data.summary || {};
// Use the first detection (if any) to show process_name and PID in UI
const firstDetection = (data.detections && data.detections.length > 0)
? data.detections[0]
: null;
// Safely check if there's any process or any findings
const hasFindings = !!(
firstDetection &&
firstDetection.findings &&
firstDetection.findings.length > 0
);
// Extract process_name and pid if we do have a detection
const processName = firstDetection?.process_name ?? 'N/A';
const processPid = firstDetection?.pid ?? 'N/A';
// 3) Severity styling & icons
const severityConfig = {
'CRITICAL': {
color: 'text-red-600 border-red-600/30 bg-red-500/10',
icon: `<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="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>`
},
'HIGH': {
color: 'text-red-500 border-red-500/30 bg-red-400/10',
icon: `<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="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>`
},
'MID': {
color: 'text-yellow-500 border-yellow-500/30 bg-yellow-500/10',
icon: `<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="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>`
},
'LOW': {
color: 'text-blue-500 border-blue-500/30 bg-blue-500/10',
icon: `<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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>`
}
};
// 4) Render stats section
// We'll display (a) total findings, (b) threads, (c) optional PID, etc.
tools.hsb.statsElement.innerHTML = `
<div class="grid grid-cols-5 gap-4 mb-6">
<!-- Status tile -->
<div class="bg-gray-900/30 rounded-lg border
${hasFindings ? 'border-red-500/30' : 'border-green-500/30'} p-4">
<div class="text-sm text-gray-500">Status</div>
<div class="text-2xl font-semibold ${hasFindings ? 'text-red-500' : 'text-green-500'}">
${hasFindings ? 'Suspicious' : 'Clean'}
</div>
</div>
<!-- Total findings -->
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="text-sm text-gray-500">Findings</div>
<div class="text-2xl font-semibold text-gray-300">${summary.total_findings || 0}</div>
</div>
<!-- Threads analyzed -->
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="text-sm text-gray-500">Threads Analyzed</div>
<div class="text-2xl font-semibold text-gray-300">${summary.scanned_threads || 0}</div>
</div>
<!-- PID from the first detection (or N/A) -->
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4">
<div class="text-sm text-gray-500">PID</div>
<div class="text-2xl font-semibold text-gray-300">${processPid}</div>
</div>
<!-- Scan Duration -->
<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-2xl font-semibold text-gray-300">
${(summary.duration || 0).toFixed(3)}s
</div>
</div>
</div>
`;
// 5) Render the findings section
let html = '';
if (!hasFindings) {
// No suspicious findings => show a "clean" message
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 suspicious behavior detected</span>
<span class="text-green-400 text-sm mt-1">
Process ${processName} (PID: ${processPid}) is clean
</span>
</div>`;
} else {
// If we do have findings, build them out
const findingsByThread = {};
firstDetection.findings.forEach(finding => {
const threadId = finding.thread_id || 'process';
if (!findingsByThread[threadId]) {
findingsByThread[threadId] = [];
}
findingsByThread[threadId].push(finding);
});
html = `
<div class="space-y-4">
<div class="flex items-center space-x-2 mb-4">
<span class="text-red-500 font-medium text-lg">${processName}</span>
<span class="text-sm text-gray-500">(PID: ${processPid})</span>
</div>
${Object.entries(findingsByThread).map(([threadId, findings]) => `
<div class="bg-gray-900/30 rounded-lg border border-red-500/20">
<div class="p-3 border-b border-gray-800">
<span class="text-gray-300 font-medium">
${threadId === 'process' ? 'Process-wide Findings' : `Thread ${threadId}`}
</span>
</div>
<div class="p-4 space-y-3">
${findings.map(finding => {
const severity = severityConfig[finding.severity] || severityConfig.LOW;
return `
<div class="bg-gray-900/50 rounded border ${severity.color.split(' ')[1]} p-4">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
${severity.icon}
<span class="font-medium ${severity.color.split(' ')[0]}">
${finding.type}
</span>
</div>
<span class="px-2 py-0.5 text-xs rounded-full ${severity.color}">
${finding.severity}
</span>
</div>
${finding.description ? `
<div class="text-sm text-gray-300 mt-2">${finding.description}</div>
` : ''}
${finding.details && Object.keys(finding.details).length > 0 ? `
<div class="mt-2 pt-2 border-t border-gray-800">
${Object.entries(finding.details)
.filter(([key]) => key !== 'issue' && key !== 'condition')
.map(([key, value]) => `
<div class="text-sm">
<span class="text-gray-500">
${key.replace(/_/g, ' ')}:
</span>
<span class="text-gray-300 font-mono ml-2">
${value}
</span>
</div>
`).join('')}
</div>
` : ''}
</div>
`;
}).join('')}
</div>
</div>
`).join('')}
</div>
`;
}
tools.hsb.element.innerHTML = html;
}
},
rededr: {
element: document.getElementById('redEdrResults'),
statsElement: document.getElementById('redEdrStats'),
render: (results) => {
if (results.status === 'error') {
return tools.rededr.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>
${results.error_details ? `<pre class="mt-2 text-xs">${JSON.stringify(results.error_details, null, 2)}</pre>` : ''}
</div>
</div>`;
}
const findings = results.findings || {};
const isEmpty = !findings.process_info?.pid &&
(!findings.loaded_dlls?.length) &&
(!findings.threads?.length) &&
(!findings.image_loads?.length);
// Check if this is a PID-based scan by looking at the URL
const isPidScan = window.location.pathname.match(/\/analyze\/dynamic\/\d+$/);
// Handle empty results based on scan type
if (isEmpty) {
let messageHtml = '';
if (isPidScan) {
messageHtml = `
<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">RedEdr Not Initialized</span>
<span class="text-green-400 text-sm mt-1">No data available - RedEdr did not initialize for this PID-based scan.</span>
</div>`;
} else {
messageHtml = `
<div class="flex flex-col items-center justify-center py-8 bg-red-500/10 rounded-lg border border-red-500/20">
<svg class="w-12 h-12 text-red-500 mb-3" 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 class="text-red-500 font-medium">No Analysis Data Available</span>
<span class="text-red-400 text-sm mt-1">Please refresh the page to initiate a new scan.</span>
</div>`;
}
tools.rededr.statsElement.innerHTML = '';
tools.rededr.element.innerHTML = messageHtml;
return;
}
const processInfo = findings.process_info || {};
const loadedDlls = findings.loaded_dlls || [];
const childProcesses = findings.child_processes || [];
const threads = findings.threads || [];
const imageLoads = findings.image_loads || [];
const imageUnloads = findings.image_unloads || [];
const cpuChanges = findings.cpu_priority_changes || [];
const timeline = findings.timeline || [];
const summary = findings.summary || {};
const severity = findings.severity || {};
const alerts = findings.alerts || [];
// Enhanced stats with complete summary data
const statsHtml = `
<div class="grid grid-cols-1 gap-4 mb-6">
<!-- Summary Overview -->
<div class="bg-gray-900/30 rounded-lg border border-gray-700 p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-200">Analysis Summary</h2>
</div>
<!-- Stats Grid -->
<div class="grid grid-cols-4 gap-4">
${[
{label: 'Total Events', value: summary.total_events, icon: 'M13 10V3L4 14h7v7l9-11h-7z'},
{label: 'DLLs Loaded', value: summary.total_dlls, icon: 'M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z'},
{label: 'Child Processes', value: summary.total_child_processes, icon: 'M12 4v16m8-8H4'},
{label: 'Active Threads', value: summary.total_threads, icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z'}
].map(stat => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="flex items-center space-x-2">
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${stat.icon}"></path>
</svg>
<div>
<div class="text-sm text-gray-400">${stat.label}</div>
<div class="text-lg font-semibold text-gray-200">${stat.value || 0}</div>
</div>
</div>
</div>
`).join('')}
</div>
</div>
</div>`;
tools.rededr.statsElement.innerHTML = statsHtml;
// Main content with complete process information
let html = `
<!-- Process Information -->
<div class="bg-gray-900/30 rounded-lg border border-gray-700 p-4 mb-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-2">
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
</svg>
<h3 class="text-lg font-semibold text-gray-200">Process Details</h3>
</div>
<div class="flex items-center space-x-2">
<span class="px-2 py-1 text-xs rounded-full ${processInfo.is_protected_process ? 'bg-green-500/20 text-green-400' : 'bg-blue-500/20 text-blue-400'}">
${processInfo.is_protected_process ? 'Protected Process' : 'Standard Process'}
</span>
${processInfo.is_debugged ? `
<span class="px-2 py-1 text-xs rounded-full bg-yellow-500/20 text-yellow-400">
Debugged
</span>
` : ''}
</div>
</div>
<div class="grid grid-cols-2 gap-4">
${[
['Command Line', processInfo.commandline],
['Working Directory', processInfo.working_dir],
['Process ID', processInfo.pid],
['Image Path', processInfo.image_path],
].map(([label, value]) => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="text-sm text-gray-400 mb-1">${label}</div>
<div class="text-sm text-gray-200 font-mono break-all">${value || 'N/A'}</div>
</div>
`).join('')}
</div>
</div>
<!-- Timeline Section -->
<div class="bg-gray-900/30 rounded-lg border border-gray-700 p-4 mb-6">
<div class="flex items-center space-x-2 mb-4">
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h3 class="text-lg font-semibold text-gray-200">Event Timeline</h3>
</div>
<div class="relative pl-8 space-y-4">
${timeline.map((event, index) => `
<div class="relative">
<div class="absolute left-0 top-0 -ml-6 mt-2 w-2 h-2 rounded-full ${getEventTypeColor(event.type)}"></div>
${index !== timeline.length - 1 ? `
<div class="absolute left-0 top-0 -ml-5 mt-4 w-px h-full bg-gray-700"></div>
` : ''}
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-gray-200">${event.type}</span>
<span class="text-xs text-gray-400">${event.time || 'N/A'}</span>
</div>
<p class="text-sm text-gray-400">${event.details}</p>
</div>
</div>
`).join('')}
</div>
</div>
<!-- CPU Priority Changes -->
${cpuChanges.length > 0 ? `
<div class="bg-gray-900/30 rounded-lg border border-gray-700 p-4 mb-6">
<div class="flex items-center space-x-2 mb-4">
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<h3 class="text-lg font-semibold text-gray-200">CPU Priority Changes</h3>
</div>
<div class="space-y-2">
${cpuChanges.map(change => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="grid grid-cols-4 gap-4">
<div class="text-sm">
<span class="text-gray-400">Thread ID:</span>
<span class="text-gray-200 ml-2">${change.thread_id}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">Old Priority:</span>
<span class="text-gray-200 ml-2">${change.old_priority}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">New Priority:</span>
<span class="text-gray-200 ml-2">${change.new_priority}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">Time:</span>
<span class="text-gray-200 ml-2">${change.time || 'N/A'}</span>
</div>
</div>
</div>
`).join('')}
</div>
</div>
` : ''}
<!-- Tabs for Detail Sections -->
<div class="bg-gray-900/30 rounded-lg border border-gray-700 p-4">
<div class="border-b border-gray-700 mb-4">
<div class="flex space-x-4" role="tablist">
<button onclick="switchInnerTab('dlls')" class="tab-button active px-4 py-2 text-sm font-medium text-gray-400 hover:text-gray-200 border-b-2 border-transparent hover:border-blue-500">DLLs</button>
<button onclick="switchInnerTab('images')" class="tab-button px-4 py-2 text-sm font-medium text-gray-400 hover:text-gray-200 border-b-2 border-transparent hover:border-blue-500">Images</button>
<button onclick="switchInnerTab('threads')" class="tab-button px-4 py-2 text-sm font-medium text-gray-400 hover:text-gray-200 border-b-2 border-transparent hover:border-blue-500">Threads</button>
</div>
</div>
<!-- DLLs Tab -->
<div id="dlls-tab" class="tab-content">
<div class="space-y-2">
${loadedDlls.map(dll => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="flex items-center justify-between">
<span class="text-gray-200 font-mono">${dll.name || 'Unknown DLL'}</span>
<span class="text-gray-400 text-sm">Base: ${dll.addr ? '0x' + dll.addr.toString(16) : 'N/A'}</span>
</div>
${dll.size ? `
<div class="text-xs text-gray-400 mt-1">
Size: ${formatBytes(dll.size)}
</div>
` : ''}
</div>
`).join('')}
</div>
</div>
<!-- Images Tab -->
<div id="images-tab" class="tab-content hidden">
<div class="flex justify-between mb-4">
<div class="space-x-2">
<button onclick="filterImages('loads')" class="px-3 py-1 text-sm rounded-full bg-blue-500/20 text-blue-400">Loads (${imageLoads.length})</button>
<button onclick="filterImages('unloads')" class="px-3 py-1 text-sm rounded-full bg-red-500/20 text-red-400">Unloads (${imageUnloads.length})</button>
</div>
</div>
<div id="image-loads" class="space-y-2">
${imageLoads.map(img => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="flex items-center justify-between mb-2">
<span class="text-gray-200 font-mono">${img.image_name?.split('\\').pop() || 'Unknown'}</span>
<span class="text-gray-400 text-sm">${formatBytes(img.size)}</span>
</div>
<div class="grid grid-cols-2 gap-2 text-xs text-gray-400">
<div>Base: ${img.base ? '0x' + img.base.toString(16) : 'N/A'}</div>
</div>
</div>
`).join('')}
</div>
<div id="image-unloads" class="space-y-2 hidden">
${imageUnloads.map(img => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="flex items-center justify-between mb-2">
<span class="text-gray-200 font-mono">${img.image_name?.split('\\').pop() || 'Unknown'}</span>
<span class="text-gray-400 text-sm">${formatBytes(img.size)}</span>
</div>
<div class="grid grid-cols-2 gap-2 text-xs text-gray-400">
<div>Base: ${img.base ? '0x' + img.base.toString(16) : 'N/A'}</div>
</div>
</div>
`).join('')}
</div>
</div>
<!-- Threads Tab -->
<div id="threads-tab" class="tab-content hidden">
<div class="space-y-2">
${threads.map(thread => `
<div class="bg-gray-800/50 rounded-lg p-3">
<div class="flex items-center justify-between mb-2">
<span class="text-gray-200 font-mono">Thread ID: ${thread.thread_id}</span>
<span class="text-gray-400 text-sm">Process ID: ${thread.process_id}</span>
</div>
<div class="grid grid-cols-2 gap-2 text-xs text-gray-400">
<div>Start Address: ${thread.start_addr ? '0x' + thread.start_addr.toString(16) : 'N/A'}</div>
<div>Stack Base: ${thread.stack_base ? '0x' + thread.stack_base.toString(16) : 'N/A'}</div>
</div>
</div>
`).join('')}
</div>
</div>
</div>`;
tools.rededr.element.innerHTML = html;
// Initialize tab functionality if not already done
// Initialize tab functionality if not already done
if (!window.switchInnerTab) {
window.switchInnerTab = function(tabName) {
// Hide all tab contents within RedEdr
const tabContents = document.querySelectorAll('#redEdrTab .tab-content');
tabContents.forEach(content => {
content.classList.add('hidden');
});
// Show selected tab content
const selectedTab = document.getElementById(`${tabName}-tab`);
if (selectedTab) {
selectedTab.classList.remove('hidden');
}
// Update tab button styles
const tabButtons = document.querySelectorAll('#redEdrTab .tab-button');
tabButtons.forEach(button => {
button.classList.remove('active', 'text-gray-200', 'border-blue-500');
button.classList.add('text-gray-400');
});
const activeButton = document.querySelector(`[onclick="switchInnerTab('${tabName}')"]`);
if (activeButton) {
activeButton.classList.add('active', 'text-gray-200', 'border-blue-500');
activeButton.classList.remove('text-gray-400');
}
};
}
// Also update the image filter function to not conflict with tab visibility
if (!window.filterImages) {
window.filterImages = function(type) {
const loads = document.getElementById('image-loads');
const unloads = document.getElementById('image-unloads');
if (!loads || !unloads) return;
if (type === 'loads') {
loads.classList.remove('hidden');
unloads.classList.add('hidden');
} else {
loads.classList.add('hidden');
unloads.classList.remove('hidden');
}
};
}
}
},
summary: {
element: document.getElementById('summaryWrapper'),
statsElement: document.getElementById('scannerResultsBody'),
render: (results) => {
// First check if this is an early termination case
if (results.status === 'early_termination') {
// Update stats for early termination
document.getElementById('totalDetections').textContent = '-';
document.getElementById('overallStatus').textContent = 'Analysis Failed';
document.getElementById('overallStatus').className = 'text-2xl font-semibold text-red-500';
// Show early termination in results table
tools.summary.statsElement.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-4">
<div class="flex flex-col items-center justify-center text-center">
<span class="text-red-500 font-medium mb-1">Process terminated before analysis could complete</span>
<span class="text-sm text-gray-400">
${results.error || 'Process terminated early'}
${results.analysis_metadata?.total_duration ?
`(Terminated after ${results.analysis_metadata.total_duration} seconds)` :
''}
</span>
</div>
</td>
</tr>`;
// Update target details for early termination
const targetDetailsEl = document.getElementById('targetDetails');
if (targetDetailsEl) {
targetDetailsEl.innerHTML = `
<div class="bg-red-500/10 rounded-lg border border-red-900/20 p-4 mb-4">
<h4 class="text-base font-medium text-red-500 mb-2">Analysis Failed</h4>
<p class="text-gray-300">
Process terminated before analysis could complete. No results available.
</p>
</div>`;
}
return;
}
// Rest of the existing summary render code for normal cases...
const targetDetailsEl = document.getElementById('targetDetails');
const filePath = results.checkplz?.findings?.scan_results?.file_path ||
'No file path available';
targetDetailsEl.innerHTML = `
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4 mb-4">
<h4 class="text-base font-medium text-gray-100 mb-2">Target File</h4>
<p class="text-gray-300">
<span class="font-semibold">File Path:</span>
${filePath}
</p>
</div>`;
// or if we have process info (Monetas approach)
if (results.moneta?.findings?.process_info) {
const info = results.moneta.findings.process_info;
targetDetailsEl.innerHTML = `
<div class="bg-gray-900/30 rounded-lg border border-gray-800 p-4 mb-4">
<h4 class="text-base font-medium text-gray-100 mb-2">Target Process</h4>
<p class="text-gray-300">
<span class="font-semibold">Name:</span> ${info.name}<br />
<span class="font-semibold">PID:</span> ${info.pid}<br />
<span class="font-semibold">Path:</span> <span class="text-gray-400">${info.path}</span>
</p>
</div>
`;
}
// 4) Now proceed with existing logic to build the table rows
let totalDetections = 0;
const rows = [];
// Example for YARA results
if (results.yara) {
const matches = Array.isArray(results.yara.matches) ? results.yara.matches : [];
totalDetections += matches.length;
rows.push(`
<tr>
<td class="px-6 py-4 text-base text-gray-300">YARA</td> <!-- Updated font size -->
<td class="px-6 py-4">
<span class="px-2 py-1 text-base rounded ${matches.length > 0 ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}">
${matches.length > 0 ? 'Suspicious' : 'Clean'}
</span>
</td>
<td class="px-6 py-4 text-base ${matches.length > 0 ? 'text-red-500' : 'text-gray-400'}">${matches.length}</td> <!-- Updated font size -->
<td class="px-6 py-4 text-base text-gray-400"> <!-- Updated font size -->
${matches.length > 0 ? `${matches.length} rule matches found` : 'No threats detected'}
</td>
</tr>
`);
}
// PE-sieve results
if (results.pe_sieve) {
const findings = results.pe_sieve.findings || {};
const suspicious = findings.total_suspicious || 0;
totalDetections += suspicious;
rows.push(`
<tr>
<td class="px-6 py-4 text-base text-gray-300">PE-sieve</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-base rounded ${suspicious > 0 ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}">
${suspicious > 0 ? 'Suspicious' : 'Clean'}
</span>
</td>
<td class="px-6 py-4 text-base ${suspicious > 0 ? 'text-red-500' : 'text-gray-400'}">${suspicious}</td>
<td class="px-6 py-4 text-base text-gray-400">
${suspicious > 0 ? `${suspicious} suspicious modifications found` : 'No modifications detected'}
</td>
</tr>
`);
}
// Moneta results
if (results.moneta) {
const findings = results.moneta.findings || {};
// Count all possible suspicious indicators
const suspicious = (findings.total_private_rwx || 0) +
(findings.total_private_rx || 0) +
(findings.total_modified_code || 0) +
(findings.total_heap_executable || 0) +
(findings.total_modified_pe_header || 0) +
(findings.total_inconsistent_x || 0) +
(findings.total_missing_peb || 0) +
(findings.total_mismatching_peb || 0);
// Check if scan is clean (same logic as moneta.render)
const isClean = (!findings.total_private_rx && !findings.total_private_rwx &&
!findings.total_modified_code && !findings.total_inconsistent_x &&
!findings.total_heap_executable && !findings.total_modified_pe_header &&
!findings.total_missing_peb && !findings.total_mismatching_peb) ||
(findings.total_unsigned_modules > 0 &&
!findings.total_private_rx && !findings.total_private_rwx &&
!findings.total_modified_code && !findings.total_inconsistent_x &&
!findings.total_heap_executable && !findings.total_modified_pe_header &&
!findings.total_missing_peb && !findings.total_mismatching_peb);
totalDetections += suspicious;
rows.push(`
<tr>
<td class="px-6 py-4 text-v text-gray-300">Moneta</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-base rounded ${!isClean ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}">
${!isClean ? 'Suspicious' : 'Clean'}
</span>
</td>
<td class="px-6 py-4 text-base ${!isClean ? 'text-red-500' : 'text-gray-400'}">${suspicious}</td>
<td class="px-6 py-4 text-base text-gray-400">
${!isClean ? `Memory anomalies found` : 'No anomalies detected'}
</td>
</tr>
`);
}
// ThreatCheck results
if (results.checkplz) {
const findings = results.checkplz.findings || {};
const hasDetection = findings.scan_results?.detection_offset;
if (hasDetection) totalDetections++;
rows.push(`
<tr>
<td class="px-6 py-4 text-base text-gray-300">CheckPlz</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-base rounded ${hasDetection ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}">
${hasDetection ? 'Suspicious' : 'Clean'}
</span>
</td>
<td class="px-6 py-4 text-base ${hasDetection ? 'text-red-500' : 'text-gray-400'}">${hasDetection ? '1' : '0'}</td>
<td class="px-6 py-4 text-base text-gray-400">
${hasDetection ? findings.initial_threat || 'Threat detected' : 'No threats detected'}
</td>
</tr>
`);
}
// Patriot results
if (results.patriot) {
const findings = results.patriot.findings || {};
const totalFindings = findings.findings?.length || 0;
totalDetections += totalFindings;
rows.push(`
<tr>
<td class="px-6 py-4 text-base text-gray-300">Patriot</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-base rounded ${totalFindings > 0 ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}">
${totalFindings > 0 ? 'Suspicious' : 'Clean'}
</span>
</td>
<td class="px-6 py-4 text-base ${totalFindings > 0 ? 'text-red-500' : 'text-gray-400'}">${totalFindings}</td>
<td class="px-6 py-4 text-base text-gray-400">
${totalFindings > 0 ? `${totalFindings} suspicious activities found` : 'No suspicious activities'}
</td>
</tr>
`);
}
// HSB results
if (results.hsb) {
const findings = results.hsb.findings || {};
const totalFindings = findings.summary?.total_findings || 0;
totalDetections += totalFindings;
rows.push(`
<tr>
<td class="px-6 py-4 text-base text-gray-300">Hunt-Sleeping-Beacons</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-base rounded ${totalFindings > 0 ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}">
${totalFindings > 0 ? 'Suspicious' : 'Clean'}
</span>
</td>
<td class="px-6 py-4 text-base ${totalFindings > 0 ? 'text-red-500' : 'text-gray-400'}">${totalFindings}</td>
<td class="px-6 py-4 text-base text-gray-400">
${totalFindings > 0 ? 'Suspicious behavior detected' : 'No suspicious behavior'}
</td>
</tr>
`);
}
// Update stats
document.getElementById('totalDetections').textContent = totalDetections;
document.getElementById('overallStatus').textContent = totalDetections > 0 ? 'Threats Detected' : 'Clean';
document.getElementById('overallStatus').className = totalDetections > 0 ? 'text-2xl font-semibold text-red-500' : 'text-2xl font-semibold text-green-500';
// Set table content
tools.summary.statsElement.innerHTML = rows.join('');
document.getElementById('scanDuration').textContent = document.getElementById('analysisTimer').textContent;
}
},
};
// Initialize Everything
document.addEventListener('DOMContentLoaded', function() {
const modal = new ModalHandler();
const analysis = new AnalysisCore();
// Make modal functions globally available
window.showDynamicWarning = () => modal.show();
window.hideDynamicWarning = () => modal.hide();
// Start analysis if parameters exist
if (analysis.analysisType && analysis.fileHash) {
analysis.startAnalysis();
}
});