// app/static/js/upload/core.js // DOMContentLoaded entry for the upload page (/). // Drag-drop, validation, POST /upload, file metadata rendering, // and analysis-button wiring. import { createLnkInfoSection, renderLnkInfo, calculateLnkRisk, getRiskLevelClass, getTargetCommandBorderClass, getTargetCommandTextClass, } from './lnk.js'; // app/static/js/upload_updated.js // Check if serverConfig exists and use a default if it doesn't const maxFileSize = (window.serverConfig && window.serverConfig.maxFileSize) || 100 * 1024 * 1024; const maxFileSizeMB = (window.serverConfig && window.serverConfig.maxFileSizeMB) || 100; // Upload configurations const UPLOAD_CONFIG = { maxFileSize: maxFileSize, toastDuration: 3000, transitionDelay: 300, fadeDelay: 50 }; // Global variables let currentFileHash = null; let currentFileExtension = null; let isDriverFile = false; document.addEventListener('DOMContentLoaded', function() { const elements = { dropZone: document.getElementById('dropZone'), fileInput: document.getElementById('fileInput'), uploadStatus: document.getElementById('uploadStatus'), uploadArea: document.getElementById('uploadArea'), fileAnalysisArea: document.getElementById('fileAnalysisArea'), fileName: document.getElementById('fileName'), fileSize: document.getElementById('fileSize'), fileType: document.getElementById('fileType'), fileFormat: document.getElementById('fileFormat'), fileCategory: document.getElementById('fileCategory'), fileEntropy: document.getElementById('fileEntropy'), uploadTime: document.getElementById('uploadTime'), md5Hash: document.getElementById('md5Hash'), sha256Hash: document.getElementById('sha256Hash'), fileSpecificInfo: document.getElementById('fileSpecificInfo'), step1Circle: document.getElementById('step1Circle'), step1Text: document.getElementById('step1Text'), step2Circle: document.getElementById('step2Circle'), step2Text: document.getElementById('step2Text'), progressLine: document.getElementById('progressLine'), toastContainer: document.getElementById('toastContainer'), entropyBar: document.getElementById('entropyBar'), entropyNotes: document.getElementById('entropyNotes'), detectionRisk: document.getElementById('detectionRisk'), peInfo: document.getElementById('peInfo'), sectionsList: document.getElementById('sectionsList'), detectionNotes: document.getElementById('detectionNotes'), officeInfo: document.getElementById('officeInfo'), macroStatus: document.getElementById('macroStatus'), macroDetectionNotes: document.getElementById('macroDetectionNotes'), htmlSmuggleInfo: document.getElementById('htmlSmuggleInfo'), smuggleStatus: document.getElementById('smuggleStatus'), smuggleDetectionNotes: document.getElementById('smuggleDetectionNotes'), smuggleInfo: document.getElementById('smuggleInfo'), checksumInfo: document.getElementById('checksumInfo'), checksumStatus: document.getElementById('checksumStatus'), storedChecksum: document.getElementById('storedChecksum'), calculatedChecksum: document.getElementById('calculatedChecksum'), checksumNotes: document.getElementById('checksumNotes'), suspiciousImports: document.getElementById('suspiciousImports'), suspiciousImportsList: document.getElementById('suspiciousImportsList'), suspiciousImportsCount: document.getElementById('suspiciousImportsCount'), suspiciousImportsSummary: document.getElementById('suspiciousImportsSummary'), suspiciousImportsTitle: document.getElementById('suspiciousImportsTitle'), }; let dragCounter = 0; // Event Listeners ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { elements.dropZone.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false); }); elements.dropZone.addEventListener('dragenter', () => { dragCounter++; if (dragCounter === 1) highlight(); }); elements.dropZone.addEventListener('dragleave', () => { dragCounter--; if (dragCounter === 0) unhighlight(); }); elements.dropZone.addEventListener('drop', (e) => { dragCounter = 0; unhighlight(); handleDrop(e); }); elements.fileInput.addEventListener('change', handleFiles); // Utility Functions function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } function highlight() { const label = elements.dropZone.querySelector('.lb-upload-zone'); if (label) label.classList.add('lb-drag-over'); } function unhighlight() { const label = elements.dropZone.querySelector('.lb-upload-zone'); if (label) label.classList.remove('lb-drag-over'); } function showToast(message, type = 'success') { const toast = document.createElement('div'); const colors = { success: 'border-green-500/25 bg-green-500/10 text-green-300', error: 'border-red-500/25 bg-red-500/10 text-red-300', info: 'border-blue-500/25 bg-blue-500/10 text-blue-300' }; toast.className = `flex items-center space-x-2 p-4 rounded-lg border ${colors[type]} transform translate-y-2 opacity-0 transition-all duration-300 text-base`; toast.innerHTML = ` ${message} `; elements.toastContainer.appendChild(toast); requestAnimationFrame(() => { toast.classList.remove('translate-y-2', 'opacity-0'); }); setTimeout(() => { toast.classList.add('translate-y-2', 'opacity-0'); setTimeout(() => toast.remove(), UPLOAD_CONFIG.fadeDelay); }, UPLOAD_CONFIG.toastDuration); } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function formatTimestamp(timestamp) { return new Date(timestamp).toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } function updateProgress(step, completed = false) { const stepCircle = step === 1 ? elements.step1Circle : elements.step2Circle; const stepText = step === 1 ? elements.step1Text : elements.step2Text; if (completed && step === 1) { stepCircle.classList.remove('bg-red-500/10', 'border-red-500', 'bg-red-500/8', 'border-red-500/40', 'bg-black/50', 'border-gray-700'); stepCircle.classList.add('bg-green-500/8', 'border-green-500/40'); stepText.innerHTML = ` `; elements.progressLine.classList.remove('to-gray-800'); elements.progressLine.classList.add('to-red-500/15'); elements.step2Circle.classList.remove('bg-black/50', 'border-gray-700'); elements.step2Circle.classList.add('bg-red-500/8', 'border-red-500/40'); elements.step2Text.classList.remove('text-gray-500'); elements.step2Text.classList.add('text-red-300'); } } // File type detection and UI updates. // // The analysis-mode selector is a single segmented control with one tab // per mode (Static / Dynamic / each EDR profile / HolyGrail). Each tab // is tagged with one or more `data-family` values (space-separated) and // only tabs matching the uploaded file's family are shown. // // Four families: // driver -- .sys (-> static-driver, holygrail) // office -- Word / Excel macro-bearing documents (-> static only; // dynamic / EDR don't make sense without an Office install // on the target host -- olevba is the relevant scanner) // html -- .html / .htm (-> static only; SmuggleShield-derived // pattern analyzer runs at upload time as html_smuggle_info) // regular -- everything else (-> all / static / dynamic / edr:*) const DRIVER_EXTS = new Set(['sys']); const OFFICE_EXTS = new Set([ 'docx', 'docm', 'dotm', 'doc', 'rtf', 'xlsx', 'xlsm', 'xltm', 'xls', ]); const HTML_EXTS = new Set(['html', 'htm']); function updateAnalysisOptions(fileExtension) { const ext = (fileExtension || '').toLowerCase(); isDriverFile = DRIVER_EXTS.has(ext); const family = isDriverFile ? 'driver' : OFFICE_EXTS.has(ext) ? 'office' : HTML_EXTS.has(ext) ? 'html' : 'regular'; const tabs = document.querySelectorAll('#modeTabs .lb-tab'); const bodies = document.querySelectorAll('.lb-mode-body'); // Show only tabs whose `data-family` list contains this file's family. // Multiple families are space-separated (e.g. `regular office` for the // Static tab, which serves both classes). let firstVisible = null; tabs.forEach(t => { const families = (t.dataset.family || '').split(/\s+/); const matches = families.includes(family); t.classList.toggle('hidden', !matches); t.classList.remove('active'); if (matches && !firstVisible) firstVisible = t; }); if (firstVisible) { firstVisible.classList.add('active'); // Hide all bodies, then show the one that matches. bodies.forEach(b => b.classList.add('hidden')); const target = document.querySelector(`.lb-mode-body[data-mode="${firstVisible.dataset.mode}"]`); if (target) target.classList.remove('hidden'); } } // Wire tab clicks. Activating a tab swaps the active class and reveals // the matching mode body. document.getElementById('modeTabs').addEventListener('click', (e) => { const tab = e.target.closest('.lb-tab'); if (!tab || tab.classList.contains('hidden')) return; document.querySelectorAll('#modeTabs .lb-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); document.querySelectorAll('.lb-mode-body').forEach(b => b.classList.add('hidden')); const target = document.querySelector(`.lb-mode-body[data-mode="${tab.dataset.mode}"]`); if (target) target.classList.remove('hidden'); }); function getDetectionRiskColor(risk) { const colors = { 'High': 'bg-red-500/10 text-red-300 border border-red-500/25', 'Medium': 'bg-yellow-500/10 text-yellow-300 border border-yellow-500/25', 'Low': 'bg-green-500/10 text-green-300 border border-green-500/25' }; return colors[risk] || colors['Low']; } // File Info Functions (all the existing functions remain the same) function renderFileTypeSpecificInfo(fileInfo) { elements.peInfo.classList.add('hidden'); elements.officeInfo.classList.add('hidden'); if (elements.htmlSmuggleInfo) elements.htmlSmuggleInfo.classList.add('hidden'); elements.suspiciousImports.classList.add('hidden'); if (fileInfo.entropy_analysis) { const entropyPercentage = (fileInfo.entropy / 8) * 100; elements.entropyBar.style.width = `${entropyPercentage}%`; elements.entropyBar.className = `absolute h-full transition-all duration-300 ${ fileInfo.entropy_analysis.detection_risk === 'High' ? 'bg-red-400' : fileInfo.entropy_analysis.detection_risk === 'Medium' ? 'bg-yellow-400' : 'bg-green-400' }`; elements.detectionRisk.className = `px-3 py-1 text-sm rounded-full ${ getDetectionRiskColor(fileInfo.entropy_analysis.detection_risk) }`; elements.detectionRisk.textContent = `${fileInfo.entropy_analysis.detection_risk} Detection Risk`; elements.entropyNotes.innerHTML = fileInfo.entropy_analysis.notes.map(note => `
${note}
`).join(''); } if (fileInfo.pe_info) { elements.peInfo.classList.remove('hidden'); const pe = fileInfo.pe_info; if (pe.suspicious_imports && pe.suspicious_imports.length > 0) { elements.suspiciousImports.classList.remove('hidden'); const buildWith = pe.build_with || null; const runtimeConfig = getRuntimeConfig(buildWith); const runtimeImports = pe.suspicious_imports.filter(imp => imp.is_runtime_import); const genuinelySuspicious = pe.suspicious_imports.filter(imp => !imp.is_runtime_import); elements.suspiciousImportsTitle.textContent = runtimeConfig.title; elements.suspiciousImportsCount.className = `px-3 py-1 text-sm ${runtimeConfig.countClass} rounded-full`; elements.suspiciousImportsCount.textContent = `${pe.suspicious_imports.length} Found${runtimeConfig.badge ? ` (${runtimeConfig.badge})` : ''}`; elements.suspiciousImportsList.innerHTML = pe.suspicious_imports.map(imp => { const isRuntimeImport = imp.is_runtime_import || false; const config = isRuntimeImport ? runtimeConfig : getRuntimeConfig(null); return `
${imp.dll} ${imp.function} [${imp.category || 'Unknown'}] ${isRuntimeImport ? `${runtimeConfig.badgeLabel}` : ''}
${imp.hint !== null && imp.hint !== undefined ? `Hint: ${imp.hint}` : ''}
${imp.note}
`; }).join(''); updateImportsSummary(buildWith, runtimeImports.length, genuinelySuspicious.length); } if (pe.checksum_info) { elements.checksumInfo.classList.remove('hidden'); elements.storedChecksum.textContent = pe.checksum_info.stored_checksum; elements.calculatedChecksum.textContent = pe.checksum_info.calculated_checksum; const buildWith = pe.checksum_info.build_with; const isValid = pe.checksum_info.is_valid; if (isValid) { elements.checksumStatus.className = 'px-3 py-1 text-sm rounded-full bg-green-500/8 text-green-300 border border-green-500/22'; elements.checksumStatus.textContent = 'Valid'; } else if (buildWith) { const runtimeConfig = getRuntimeConfig(buildWith); elements.checksumStatus.className = `px-3 py-1 text-sm rounded-full ${runtimeConfig.checksumClass}`; elements.checksumStatus.textContent = `${buildWith.charAt(0).toUpperCase() + buildWith.slice(1)} Binary`; } else { elements.checksumStatus.className = 'px-3 py-1 text-sm rounded-full bg-red-500/8 text-red-300 border border-red-500/22'; elements.checksumStatus.textContent = 'Invalid'; } if (!pe.checksum_info.is_valid) { const buildWith = pe.checksum_info.build_with; const runtimeConfig = getRuntimeConfig(buildWith); elements.checksumNotes.innerHTML = `
${runtimeConfig.checksumNote}
`; } } else { elements.checksumInfo.classList.add('hidden'); } renderPEBasicInfo(pe); } else if (fileInfo.office_info) { elements.officeInfo.classList.remove('hidden'); renderOfficeInfo(fileInfo.office_info); } else if (fileInfo.lnk_info) { // Show LNK-specific information section const lnkInfoSection = document.getElementById('lnkInfo') || createLnkInfoSection(); lnkInfoSection.classList.remove('hidden'); renderLnkInfo(fileInfo.lnk_info); } else if (fileInfo.html_smuggle_info) { const htmlSection = document.getElementById('htmlSmuggleInfo'); if (htmlSection) htmlSection.classList.remove('hidden'); renderHtmlSmuggleInfo(fileInfo.html_smuggle_info); } } // -- Office macro / template-injection rendering -------------------- // // Surfaces every non-empty piece of the `office_info` structure: // * Status pill: Macros Present / No Macros // * Detection notes (one-line summaries) // * Autoexec triggers (table: keyword + description) // * Suspicious keywords (table: keyword + description) // * IOCs (table: type + value) // * External refs (table: relationship + target -- T1221 etc.) // * Per-module VBA source code (collapsible
) // * Hex / Base64 / VBA strings (collapsible) // // The DOM container (#officeInfo) already exists in upload.html; this // function rewrites #macroDetectionNotes (status notes) and #macroInfo // (detail blocks) every time it runs. function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ( { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] )); } function macroSeverityClass(office) { // Treat external attachedTemplate references and live macros as the // strong signals. Everything else goes "info". if (office.has_macros) return 'critical'; if ((office.external_refs || []).some(r => r.relationship === 'attachedTemplate')) return 'critical'; if ((office.external_refs || []).length > 0) return 'medium'; return 'low'; } function renderTable(headers, rows) { if (!rows.length) return ''; const head = headers.map(h => `${escapeHtml(h)}`).join(''); const body = rows.map(r => `${r.map(c => `${c}`).join('')}`).join(''); return `${head}${body}
`; } function renderSection(title, body, opts) { opts = opts || {}; if (!body) return ''; const collapsible = opts.collapsible; const open = opts.open === undefined ? false : opts.open; const heading = `
${escapeHtml(title)}
`; if (collapsible) { return `${heading}
${escapeHtml(opts.summary || 'show')}${body}
`; } return `${heading}${body}`; } function renderOfficeInfo(office) { // Status pill const sev = macroSeverityClass(office); const sevClassMap = { critical: 'bg-red-500/8 text-red-300 border border-red-500/22', medium: 'bg-yellow-500/8 text-yellow-300 border border-yellow-500/22', low: 'bg-green-500/8 text-green-300 border border-green-500/22', }; elements.macroStatus.className = `px-3 py-1 text-sm rounded-full ${sevClassMap[sev]}`; elements.macroStatus.textContent = office.has_macros ? 'Macros Present' : ((office.external_refs || []).length > 0 ? 'External Refs' : 'No Macros'); // Top-level detection notes (one-line summaries) const notes = office.detection_notes || []; elements.macroDetectionNotes.innerHTML = notes.map(note => `
${escapeHtml(note)}
`).join(''); // Detailed sections const macroInfo = document.getElementById('macroInfo'); if (!macroInfo) return; const parts = []; // External references (T1221 etc.) -- shown FIRST when present // since they're often the only signal for documents that have no VBA. const refs = office.external_refs || []; if (refs.length > 0) { const rows = refs.map(r => [ `${escapeHtml(r.relationship)}`, `${escapeHtml(r.target)}`, `${escapeHtml(r.rels_file)}`, ]); parts.push(renderSection('External References (Remote Targets)', renderTable(['Relationship', 'Target', 'In .rels'], rows))); } const a = office.analysis || {}; // Autoexec triggers if ((a.autoexec || []).length > 0) { const rows = a.autoexec.map(e => [ `${escapeHtml(e.keyword || '?')}`, `${escapeHtml(e.description || '')}`, ]); parts.push(renderSection(`Auto-Execution Triggers (${a.autoexec.length})`, renderTable(['Keyword', 'Description'], rows))); } // Suspicious keywords if ((a.suspicious || []).length > 0) { const rows = a.suspicious.map(e => [ `${escapeHtml(e.keyword || '?')}`, `${escapeHtml(e.description || '')}`, ]); parts.push(renderSection(`Suspicious Keywords (${a.suspicious.length})`, renderTable(['Keyword', 'Description'], rows))); } // IOCs (URLs, IPs, EXEs, etc. that olevba pulled out of the macro body) if ((a.iocs || []).length > 0) { const rows = a.iocs.map(ioc => [ `${escapeHtml(ioc.type || '?')}`, `${escapeHtml(ioc.value || '')}`, ]); parts.push(renderSection(`IOCs Extracted from Macro (${a.iocs.length})`, renderTable(['Type', 'Value'], rows))); } // Hex / Base64 / VBA-encoded strings (decoded by olevba) const stringSets = [ ['Hex Strings', a.hex_strings || []], ['Base64 Strings', a.base64_strings || []], ['VBA-Encoded Strings', a.vba_strings || []], ]; for (const [label, items] of stringSets) { if (items.length === 0) continue; const body = items.map(e => `
${escapeHtml(e.keyword || '')}: ${escapeHtml(e.description || '')}
`).join(''); parts.push(renderSection(`${label} (${items.length})`, body, { collapsible: true, summary: `${items.length} item(s) -- click to expand` })); } // Per-module VBA source code -- collapsible const modules = office.modules || []; if (modules.length > 0) { const body = modules.map(m => `
${escapeHtml(m.vba_filename || '?')} -- ${escapeHtml(m.stream || '')}
${escapeHtml(m.code || '')}
`).join(''); parts.push(renderSection(`VBA Source (${modules.length} module${modules.length !== 1 ? 's' : ''})`, body, { collapsible: true, summary: `${modules.length} module(s) -- click to view source code` })); } macroInfo.innerHTML = parts.join(''); } // -- HTML smuggling rendering -------------------------------------- // // Surfaces every non-empty piece of the `html_smuggle_info` structure // produced by app/utils/htmlsmuggle.py: // * Status pill: SMUGGLING / SUSPICIOUS / CLEAN with score // * Detection notes (one-line summaries) // * Score bar + matched-categories pill row // * Matched patterns (table: name + category + weight) // * Surface features (table: feature + value) // * IOCs (download filenames, dataset blobs, largest base64 preview) // // Reuses the renderTable / renderSection / escapeHtml helpers defined // for the office macro renderer. function smuggleSeverityClass(h) { if (h.is_smuggling) return 'critical'; if ((h.score || 0) > 0) return 'medium'; return 'low'; } function renderHtmlSmuggleInfo(h) { // Status pill const sev = smuggleSeverityClass(h); const sevClassMap = { critical: 'bg-red-500/8 text-red-300 border border-red-500/22', medium: 'bg-yellow-500/8 text-yellow-300 border border-yellow-500/22', low: 'bg-green-500/8 text-green-300 border border-green-500/22', }; if (elements.smuggleStatus) { elements.smuggleStatus.className = `px-3 py-1 text-sm rounded-full ${sevClassMap[sev]}`; const label = h.is_smuggling ? `SMUGGLING (score ${h.score}/${h.threshold})` : (h.score > 0 ? `SUSPICIOUS (score ${h.score}/${h.threshold})` : 'CLEAN'); elements.smuggleStatus.textContent = label; } // Detection notes const notes = h.detection_notes || []; if (elements.smuggleDetectionNotes) { elements.smuggleDetectionNotes.innerHTML = notes.map(note => `
${escapeHtml(note)}
`).join(''); } // Detail blocks const host = elements.smuggleInfo; if (!host) return; const parts = []; // Score line + matched-category pills const cats = h.matched_categories || {}; if (Object.keys(cats).length > 0) { const pills = Object.entries(cats).map(([cat, count]) => `${escapeHtml(cat)} × ${count}` ).join(' '); parts.push(renderSection('Pattern Categories', `
${pills}
`)); } // Matched patterns -- the actual signatures that fired const matches = h.matched_patterns || []; if (matches.length > 0) { const rows = matches.map(m => [ `${escapeHtml(m.name)}`, `${escapeHtml(m.category || '?')}`, `+${m.weight || 0}`, ]); parts.push(renderSection(`Matched Patterns (${matches.length})`, renderTable(['Pattern', 'Category', 'Weight'], rows))); } // Surface features const f = h.features || {}; if (Object.keys(f).length > 0) { const featureRows = [ ['File size (bytes)', f.file_size], ['Script tags', f.script_tags], ['iframe tags', f.iframe_tags], ['embed tags', f.embed_tags], ['Base64 blob count (>=50 chars)', f.base64_blob_count], ['Largest base64 blob (chars)', f.largest_base64_chars], ['Has blob()', f.has_blob], ['Has atob()', f.has_atob], ['Has Uint8Array', f.has_uint8array], ['Has URL.createObjectURL', f.has_createobjecturl], ['Has ', f.has_download_attr], ['Has String.fromCharCode', f.has_fromcharcode], ].filter(([, v]) => v !== undefined && v !== null && v !== false && v !== 0) .map(([label, v]) => [ `${escapeHtml(label)}`, `${escapeHtml(String(v))}`, ]); if (featureRows.length > 0) { parts.push(renderSection('Surface Features', renderTable(['Feature', 'Value'], featureRows))); } } // IOCs const iocs = h.iocs || {}; const iocBits = []; if ((iocs.download_filenames || []).length > 0) { const rows = iocs.download_filenames.map(name => [ `download=`, `${escapeHtml(name)}`, ]); iocBits.push(renderTable(['Type', 'Value'], rows)); } if ((iocs.data_file_attrs || []).length > 0) { const rows = iocs.data_file_attrs.map(d => [ `data-file=`, `${escapeHtml(d)}`, ]); iocBits.push(renderTable(['Type', 'Value (truncated)'], rows)); } if (iocs.largest_base64_blob && iocs.largest_base64_blob.length > 0) { const b = iocs.largest_base64_blob; iocBits.push(`
Largest base64 blob: ${b.length} chars
First 120: ${escapeHtml(b.preview_first_120)}
${b.preview_last_120 ? `
Last 120: ${escapeHtml(b.preview_last_120)}
` : ''}
`); } if (iocBits.length > 0) { parts.push(renderSection('IOCs', iocBits.join(''))); } if (h.truncated) { parts.push(`
⚠ Scan was truncated -- file exceeds the 5 MiB cap.
`); } host.innerHTML = parts.join(''); } function getRuntimeConfig(buildWith) { const configs = { 'go': { title: 'API Imports Analysis (Go Runtime)', badge: 'Go Runtime', badgeLabel: 'Go Runtime', countClass: 'bg-blue-500/5 text-blue-300', dllColor: 'text-blue-300', categoryBg: 'bg-blue-500/10', categoryText: 'text-blue-300', badgeBg: 'bg-gray-500/10', badgeText: 'text-gray-400', iconColor: 'text-blue-400', checksumClass: 'bg-blue-500/5 text-blue-300', checksumNote: 'Go binaries typically have non-standard PE checksums - This is normal behavior' }, 'rust': { title: 'API Imports Analysis (Rust Runtime)', badge: 'Rust Runtime', badgeLabel: 'Rust Runtime', countClass: 'bg-purple-500/5 text-purple-300', dllColor: 'text-purple-300', categoryBg: 'bg-purple-500/10', categoryText: 'text-purple-300', badgeBg: 'bg-gray-500/10', badgeText: 'text-gray-400', iconColor: 'text-purple-400', checksumClass: 'bg-purple-500/5 text-purple-300', checksumNote: 'Rust binaries may have non-standard PE checksums - This is normal behavior' } }; const defaultConfig = { title: 'Sensitive Imports Analysis', badge: null, badgeLabel: 'Sensitive', countClass: 'bg-red-500/5 text-red-300', dllColor: 'text-red-300', categoryBg: 'bg-red-500/10', categoryText: 'text-red-300', badgeBg: 'bg-red-500/10', badgeText: 'text-red-300', iconColor: 'text-yellow-400', checksumClass: 'bg-red-500/5 text-red-300', checksumNote: 'Invalid checksum - Common in packed/modified payloads' }; return configs[buildWith] || defaultConfig; } function renderPEBasicInfo(pe) { const html = `
PE File Information
File Type: ${pe.file_type} Compile Time: ${pe.compile_time}
Machine Type
${pe.machine_type}
Subsystem
${pe.subsystem}
Entry Point
${pe.entry_point}
PE Sections ${pe.sections.length} sections
${pe.sections.map(section => { const isStandardSection = ['.text', '.data', '.bss', '.rdata', '.edata', '.idata', '.pdata', '.reloc', '.rsrc', '.tls', '.debug'].includes(section.name); return ` ${section.name} `; }).join('')}
${pe.imports && pe.imports.length > 0 ? `
Imported DLLs ${pe.imports.length} imports
${pe.imports.map(imp => ` ${imp} `).join('')}
` : ''}
`; elements.fileSpecificInfo.innerHTML = html; } function updateImportsSummary(buildWith, runtimeCount, suspiciousCount) { let summaryText = ''; if (buildWith === 'go') { if (suspiciousCount > 0) { summaryText = `Go binary detected: ${runtimeCount} standard Go runtime imports and ${suspiciousCount} sensitive imports observed.`; } else { summaryText = `Go binary detected: ${runtimeCount} standard Go runtime imports — typically not user logic.`; } } else if (buildWith === 'rust') { if (suspiciousCount > 0) { summaryText = `Rust binary detected: ${runtimeCount} standard Rust runtime imports and ${suspiciousCount} sensitive imports observed.`; } else { summaryText = `Rust binary detected: ${runtimeCount} standard Rust runtime imports — typically not user logic.`; } } else { const totalImports = runtimeCount + suspiciousCount; summaryText = `${totalImports} sensitive imports observed — these are APIs commonly watched by AV/EDR.`; } elements.suspiciousImportsSummary.textContent = summaryText; } function updateFileInfo(fileInfo) { currentFileHash = fileInfo.md5; currentFileExtension = fileInfo.extension; elements.fileName.textContent = fileInfo.original_name; elements.fileSize.textContent = formatFileSize(fileInfo.size); elements.fileType.textContent = fileInfo.extension.toUpperCase(); elements.fileFormat.textContent = fileInfo.mime_type; elements.fileCategory.textContent = fileInfo.extension.toUpperCase(); elements.fileEntropy.textContent = fileInfo.entropy; elements.uploadTime.textContent = formatTimestamp(fileInfo.upload_time); elements.md5Hash.textContent = fileInfo.md5; elements.sha256Hash.textContent = `${fileInfo.sha256.substring(0, 32)}...`; document.getElementById('sha256HashFull').textContent = fileInfo.sha256; localStorage.setItem('currentFileExtension', fileInfo.extension); // Update analysis options based on file type updateAnalysisOptions(fileInfo.extension); renderFileTypeSpecificInfo(fileInfo); elements.uploadArea.classList.add('opacity-0', 'scale-95'); setTimeout(() => { elements.uploadArea.classList.add('hidden'); elements.fileAnalysisArea.classList.remove('hidden'); setTimeout(() => { elements.fileAnalysisArea.classList.remove('opacity-0', 'scale-95'); }, UPLOAD_CONFIG.fadeDelay); }, UPLOAD_CONFIG.transitionDelay); updateProgress(1, true); } // File Handling Functions function handleDrop(e) { const dt = e.dataTransfer; const files = dt.files; if (files.length > 1) { showToast('Please upload only one file at a time', 'error'); return; } handleFiles({ target: { files } }); } function handleFiles(e) { const file = e.target.files[0]; if (!file) return; const extension = file.name.split('.').pop().toLowerCase(); const allowedExtensions = (window.serverConfig && window.serverConfig.allowedExtensions) || []; if (!allowedExtensions.includes(extension)) { showToast(`Unsupported file type. Allowed types: ${allowedExtensions.map(e => '.' + e).join(', ')}`, 'error'); return; } if (file.size > UPLOAD_CONFIG.maxFileSize) { showToast('File size exceeds limit', 'error'); return; } uploadFile(file); } function uploadFile(file) { showToast('Uploading file...', 'info'); const formData = new FormData(); formData.append('file', file); fetch('/upload', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.error) throw new Error(data.error); showToast('File uploaded successfully', 'success'); if (data.file_info) updateFileInfo(data.file_info); }) .catch(error => { showToast(error.message, 'error'); }); } // Global Functions window.copyHash = function(elementId) { const hashType = elementId === 'md5Hash' ? 'md5' : 'sha256'; const fullHash = document.getElementById(`${hashType}HashFull`).textContent; navigator.clipboard.writeText(fullHash).then(() => { showToast(`${hashType.toUpperCase()} hash copied to clipboard`, 'success'); }); } window.selectAnalysisType = function(type) { if (!currentFileHash) return; updateProgress(2, true); elements.fileAnalysisArea.classList.add('opacity-0', 'scale-95'); setTimeout(() => { if (type === 'holygrail') { // Show loading message showToast('Starting HolyGrail BYOVD analysis...', 'info'); // Call HolyGrail analysis endpoint fetch(`/holygrail?hash=${currentFileHash}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.status === 'success') { showToast('HolyGrail analysis completed successfully', 'success'); // Redirect to results page setTimeout(() => { window.location.href = `/results/byovd/${currentFileHash}`; }, 1000); } else { // Handle error showToast(`Analysis failed: ${data.error || data.message}`, 'error'); // Reset UI elements.fileAnalysisArea.classList.remove('opacity-0', 'scale-95'); } }) .catch(error => { console.error('HolyGrail analysis error:', error); showToast(`Analysis failed: ${error.message}`, 'error'); // Reset UI elements.fileAnalysisArea.classList.remove('opacity-0', 'scale-95'); }); } else if (type === 'all') { // "All" pipeline: static + (every registered EDR) in parallel, // then dynamic. Args (if any) shared across dynamic + EDR. const argsInput = document.getElementById('allAnalysisArgs'); const argsValue = argsInput ? argsInput.value : ''; const args = argsValue.split(' ').filter(arg => arg.trim() !== ''); localStorage.setItem('analysisArgs', JSON.stringify(args)); window.location.href = `/analyze/all/${currentFileHash}`; } else if (type === 'dynamic') { // Get user-specified arguments for dynamic analysis const argsInput = document.getElementById('analysisArgs').value; const args = argsInput.split(' ').filter(arg => arg.trim() !== ''); // Save arguments to localStorage localStorage.setItem('analysisArgs', JSON.stringify(args)); // Navigate to dynamic analysis window.location.href = `/analyze/${type}/${currentFileHash}`; } else if (type.startsWith('edr:')) { // EDR profile dispatch: type is "edr:". // Each profile body has its own args input (id = // edrArgs-); read it, persist to localStorage so // the results page's POST forwards it to Whiskers. const profile = type.slice(4); const argsInput = document.getElementById(`edrArgs-${profile}`); const argsValue = argsInput ? argsInput.value : ''; const args = argsValue.split(' ').filter(arg => arg.trim() !== ''); localStorage.setItem('analysisArgs', JSON.stringify(args)); window.location.href = `/analyze/edr/${encodeURIComponent(profile)}/${currentFileHash}`; } else { // Navigate to static analysis window.location.href = `/analyze/${type}/${currentFileHash}`; } }, UPLOAD_CONFIG.transitionDelay); }; });