malware_analysis / src /ui /app.py
RohanSardar's picture
changed the ui for download button
e918ac9 verified
Raw
History Blame Contribute Delete
10.7 kB
"""
Gradio UI Application for the Dual-Engine Malware Analysis Pipeline.
This module provides the frontend interface for uploading executables or simulated profiles,
routing them to the static structural engine (Engine A) and the visual byte-map engine (Engine B),
and rendering their ensemble metrics securely in the browser.
"""
import gradio as gr
from src.engine_a.inference import EngineAInfer
from src.engine_b.inference import EngineBInfer
import os
from datetime import datetime
from zoneinfo import ZoneInfo
import torch
from fpdf import FPDF
import tempfile
print("Loading ML models...")
engine_a = EngineAInfer()
engine_b = EngineBInfer()
print("Models loaded successfully.")
def analyze_malware(file_path):
"""
Main processing pipeline triggered by the Gradio 'Analyze File' button.
Args:
file_path (str): The path to the uploaded file.
Returns:
tuple: (output_a_text, image_b, prob_table, ensemble_verdict) for the UI components.
"""
if not file_path:
return "No file provided.", None, {}, "N/A"
try:
# Engine A Analysis
result_a = engine_a.predict(file_path)
prob_a = result_a["malware_prob"] * 100
status_a = "Malicious" if result_a["is_malware"] else "Benign"
text_a = {
"Malicious": result_a["malware_prob"],
"Benign": 1.0 - result_a["malware_prob"],
}
# Explainability
explain_dict = {}
total_abs_shap = sum(abs(f[1]) for f in result_a["top_features"])
if total_abs_shap == 0:
total_abs_shap = 1
for feature, shap_val in result_a["top_features"]:
impact_dir = "Malicious" if shap_val > 0 else "Benign"
label_name = f"{feature} [{impact_dir}]"
# Normalize to 0-1 so gr.Label can render the horizontal bars properly
normalized_impact = float(abs(shap_val) / total_abs_shap)
explain_dict[label_name] = normalized_impact
# Engine B Analysis
result_b = engine_b.predict(file_path)
all_probs = result_b["all_probabilities"]
image_b = result_b["image"]
# Ensemble Logic
ensemble_score = (prob_a + result_b["confidence"] * 100) / 2
final_verdict_text = "MALICIOUS" if ensemble_score > 50 else "BENIGN"
ensemble_verdict = f"Final Verdict: {final_verdict_text} (Risk Score: {ensemble_score:.1f}/100)"
top_3_probs = sorted(all_probs.items(), key=lambda x: x[1], reverse=True)[:3]
# Generate PDF Report
pdf_path = generate_pdf_report(
file_path=file_path,
status_a=status_a,
prob_a=prob_a,
top_features=result_a["top_features"],
family_b=result_b["family"],
prob_b=result_b["confidence"] * 100,
image_b=image_b,
ensemble_score=ensemble_score,
final_verdict=final_verdict_text,
top_3_probs=top_3_probs,
)
return (
text_a,
explain_dict,
image_b,
all_probs,
ensemble_verdict,
gr.update(value=pdf_path, visible=True),
)
except Exception as e:
return (
f"Error processing file in Engine A: {e}",
{},
None,
{},
"Error",
gr.update(visible=False),
)
def generate_pdf_report(
file_path,
status_a,
prob_a,
top_features,
family_b,
prob_b,
image_b,
ensemble_score,
final_verdict,
top_3_probs,
):
class PDFReport(FPDF):
def header(self):
# Header Bar
self.set_fill_color(40, 40, 40)
self.rect(0, 0, 210, 30, "F")
self.set_y(10)
self.set_font("Helvetica", style="B", size=24)
self.set_text_color(255, 255, 255)
self.cell(0, 10, "MALWARE ANALYSIS REPORT", border=0, align="C")
self.ln(15)
pdf = PDFReport()
pdf.add_page()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.set_y(40) # Start below header
# 1. Overview Section
pdf.set_fill_color(230, 230, 230)
pdf.set_text_color(0, 0, 0)
pdf.set_font("Helvetica", style="B", size=14)
pdf.cell(
0,
10,
" 1. Execution Overview",
border=0,
new_x="LMARGIN",
new_y="NEXT",
fill=True,
)
pdf.ln(2)
pdf.set_font("Helvetica", size=11)
pdf.cell(40, 8, "Target File:", border=0)
pdf.set_font("Helvetica", style="B", size=11)
pdf.cell(
0, 8, f"{os.path.basename(file_path)}", border=0, new_x="LMARGIN", new_y="NEXT"
)
pdf.set_font("Helvetica", size=11)
pdf.cell(40, 8, "Timestamp:", border=0)
pdf.set_font("Helvetica", style="B", size=11)
pdf.cell(
0,
8,
f"{datetime.now(ZoneInfo('Asia/Kolkata')).strftime('%Y-%m-%d %H:%M:%S')} IST",
border=0,
new_x="LMARGIN",
new_y="NEXT",
)
pdf.set_font("Helvetica", size=11)
pdf.cell(40, 8, "Final Verdict:", border=0)
pdf.set_font("Helvetica", style="B", size=12)
pdf.cell(
0,
8,
f"{final_verdict} (Risk Score: {ensemble_score:.1f} / 100)",
border=0,
new_x="LMARGIN",
new_y="NEXT",
)
pdf.ln(5)
# 2. Engine A
pdf.set_font("Helvetica", style="B", size=14)
pdf.cell(
0,
10,
" 2. Structural Engine (EMBER)",
border=0,
new_x="LMARGIN",
new_y="NEXT",
fill=True,
)
pdf.ln(2)
pdf.set_font("Helvetica", size=11)
pdf.cell(
0,
8,
f"Verdict: {status_a.upper()} (Confidence: {prob_a:.1f}%)",
new_x="LMARGIN",
new_y="NEXT",
)
pdf.ln(2)
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(140, 8, "Top Contributing Feature (SHAP)", border=1, fill=True)
pdf.cell(
50,
8,
"Impact Direction",
border=1,
new_x="LMARGIN",
new_y="NEXT",
fill=True,
align="C",
)
pdf.set_font("Helvetica", size=10)
for feature, shap_val in top_features:
impact_dir = "MALICIOUS" if shap_val > 0 else "BENIGN"
pdf.cell(140, 8, f" {feature}", border=1)
pdf.cell(
50,
8,
f"{shap_val:+.2f} ({impact_dir})",
border=1,
new_x="LMARGIN",
new_y="NEXT",
align="C",
)
pdf.ln(8)
# 3. Engine B
pdf.set_font("Helvetica", style="B", size=14)
pdf.cell(
0,
10,
" 3. Visual Engine (Malimg)",
border=0,
new_x="LMARGIN",
new_y="NEXT",
fill=True,
)
pdf.ln(2)
pdf.set_font("Helvetica", size=11)
pdf.cell(
0,
8,
f"Predicted Malware Family: {family_b.upper()} (Confidence: {prob_b:.1f}%)",
new_x="LMARGIN",
new_y="NEXT",
)
pdf.ln(2)
# Embed Image
temp_dir = tempfile.gettempdir()
img_path = os.path.join(temp_dir, "byte_map.png")
image_b.save(img_path)
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(0, 8, "Grayscale Byte Map Render:", new_x="LMARGIN", new_y="NEXT")
# Draw image with a border
x_pos = pdf.get_x()
y_pos = pdf.get_y()
pdf.rect(x_pos, y_pos, 70, 70)
pdf.image(img_path, x=x_pos, y=y_pos, w=70, h=70)
# Draw Top 3 Probabilities Table next to the image
pdf.set_y(y_pos)
# Table Header
pdf.set_x(x_pos + 75)
pdf.set_font("Helvetica", style="B", size=10)
pdf.cell(70, 8, "Predicted Family", border=1, fill=True)
pdf.cell(
40,
8,
"Confidence",
border=1,
new_x="LMARGIN",
new_y="NEXT",
fill=True,
align="C",
)
# Table Rows
pdf.set_font("Helvetica", size=10)
for family, prob in top_3_probs:
pdf.set_x(x_pos + 75)
pdf.cell(70, 8, f" {family}", border=1)
pdf.cell(
40,
8,
f"{prob * 100:.2f}%",
border=1,
new_x="LMARGIN",
new_y="NEXT",
align="C",
)
report_path = os.path.join(
temp_dir, f"Analysis_Report_{os.path.basename(file_path)}.pdf"
)
pdf.output(report_path)
return report_path
def get_system_status():
"""
Fetches real-time system metrics for the UI header.
Returns:
str: Formatted markdown string containing time, compute device, and engine status.
"""
current_time = datetime.now(ZoneInfo("Asia/Kolkata")).strftime("%Y-%m-%d %H:%M:%S")
device = "CUDA" if torch.cuda.is_available() else "CPU"
return f"**System Time:** {current_time} IST | **Compute Device:** {device} | **Engines:** 2 (Static Structural & Visual Byte-Map)"
# Build the Gradio UI
with gr.Blocks() as app:
# Use HTML to make the title massively larger than the buttons
gr.HTML(
"<h1 style='text-align: center; font-size: 3.5rem; margin-bottom: 0.2rem; font-weight: bold;'>Dual-Engine Malware Analysis Pipeline</h1>"
)
gr.HTML(
"<p style='text-align: center; font-size: 1.2rem; color: gray;'>Upload a Windows Executable (.exe, .dll) or a Simulated Profile (.json)</p>"
)
status_bar = gr.Markdown(get_system_status())
# Compact inputs
with gr.Row():
file_input = gr.File(label="Upload File", type="filepath", scale=4)
with gr.Column(scale=1):
analyze_btn = gr.Button("Analyze File", variant="primary", size="lg")
download_btn = gr.DownloadButton("Download Report (PDF)", visible=False, size="lg")
gr.Markdown("---")
# Single column layout for maximum horizontal space
ensemble_output = gr.Textbox(label="Final Verdict", interactive=False, lines=1)
output_a = gr.Label(
label="Engine A: Structural (EMBER)",
)
explain_plot = gr.Label(
label="Engine A: Explainability (Feature Impact)", num_top_classes=3
)
image_output = gr.Image(label="Engine B: Grayscale Byte Map", type="pil")
output_b = gr.Label(label="Engine B: Family Probabilities", num_top_classes=24)
analyze_btn.click(
analyze_malware,
inputs=[file_input],
outputs=[
output_a,
explain_plot,
image_output,
output_b,
ensemble_output,
download_btn,
],
)
# Live update the status bar
timer = gr.Timer(1)
timer.tick(get_system_status, inputs=None, outputs=status_bar)
# Load immediately on page open
app.load(get_system_status, inputs=None, outputs=status_bar)