Apkk / app.py
Skydata001's picture
Upload app.py
31f70b4 verified
Raw History Blame Contribute Delete
34.2 kB
"""
APK Deep Diff Analyzer - Gradio Web Interface
Hugging Face Space Application
"""
import gradio as gr
import os
import json
import tempfile
import shutil
from typing import Optional, Tuple, List
# Add src to path
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from apk_analyzer import APKDeepAnalyzer
from report_generator import ReportGenerator
from utils import get_apk_info, get_file_size_formatted
# Global state for analysis results
_analysis_results = {}
def validate_apk_file(file_path: str) -> Tuple[bool, str]:
"""Validate uploaded APK file."""
if file_path is None:
return False, "ู„ู… ูŠุชู… ุงุฎุชูŠุงุฑ ู…ู„ู"
if not os.path.exists(file_path):
return False, "ุงู„ู…ู„ู ุบูŠุฑ ู…ูˆุฌูˆุฏ"
if not file_path.endswith('.apk'):
return False, "ุงู„ู…ู„ู ู„ูŠุณ ุจุตูŠุบุฉ APK"
import zipfile
if not zipfile.is_zipfile(file_path):
return False, "ุงู„ู…ู„ู ู„ูŠุณ ู…ู„ู ZIP/APK ุตุงู„ุญ"
try:
with zipfile.ZipFile(file_path, 'r') as zf:
if "AndroidManifest.xml" not in zf.namelist():
return False, "AndroidManifest.xml ุบูŠุฑ ู…ูˆุฌูˆุฏ"
has_dex = any(name.endswith('.dex') for name in zf.namelist())
if not has_dex:
return False, "ู„ุง ูŠูˆุฌุฏ ู…ู„ู DEX"
except Exception as e:
return False, f"ุฎุทุฃ ููŠ ุงู„ุชุญู‚ู‚: {str(e)}"
return True, "ุตุงู„ุญ"
def get_file_info_display(file_path: str) -> str:
"""Get formatted file info for display."""
if file_path is None or not os.path.exists(file_path):
return "ู„ู… ูŠุชู… ุงุฎุชูŠุงุฑ ู…ู„ู"
info = get_apk_info(file_path)
return f"""
๐Ÿ“„ **ุงู„ุงุณู…:** `{info['filename']}`
๐Ÿ“ฆ **ุงู„ุญุฌู…:** {info['size_formatted']} ({info['size']:,} bytes)
๐Ÿ” **SHA-256:** `{info['sha256']}`
๐Ÿ”‘ **MD5:** `{info['md5']}`
"""
def update_file_info(file_path, is_original=True):
"""Update file info display."""
if file_path is None:
return ""
info = get_file_info_display(file_path)
return info
def run_analysis(orig_file, mod_file, progress=gr.Progress()):
"""Run deep analysis and return all results."""
global _analysis_results
if orig_file is None or mod_file is None:
return [
gr.update(visible=True, value="โŒ ุงู„ุฑุฌุงุก ุฑูุน ุงู„ู…ู„ููŠู† ุฃูˆู„ุงู‹"),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
]
# Validate files
valid_orig, msg_orig = validate_apk_file(orig_file)
valid_mod, msg_mod = validate_apk_file(mod_file)
if not valid_orig:
return [
gr.update(visible=True, value=f"โŒ APK ุงู„ุฃุตู„ูŠ: {msg_orig}"),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
]
if not valid_mod:
return [
gr.update(visible=True, value=f"โŒ APK ุงู„ู…ุนุฏู„: {msg_mod}"),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
]
progress(0, desc="ุจุฏุก ุงู„ุชุญู„ูŠู„...")
def progress_callback(stage, message):
progress(stage / 12, desc=message)
try:
analyzer = APKDeepAnalyzer(orig_file, mod_file, progress_callback)
results = analyzer.analyze()
_analysis_results = results
if "error" in results:
return [
gr.update(visible=True, value=f"โŒ ุฎุทุฃ: {results['error']}"),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
]
# Generate displays
dashboard = create_dashboard(results)
manifest_html = create_manifest_display(results)
dex_html = create_dex_display(results)
native_html = create_native_display(results)
resources_html = create_resources_display(results)
signature_html = create_signature_display(results)
suspicious_html = create_suspicious_display(results)
file_table = create_file_table(results)
report_gen = ReportGenerator(results)
txt_report = report_gen.generate_txt_report()
json_report = report_gen.generate_json_report()
html_report = report_gen.generate_html_report()
progress(1.0, desc="ุงูƒุชู…ู„ ุงู„ุชุญู„ูŠู„!")
return [
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
dashboard,
manifest_html,
dex_html,
native_html,
resources_html,
signature_html,
suspicious_html,
file_table,
txt_report,
json_report,
html_report,
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
]
except Exception as e:
return [
gr.update(visible=True, value=f"โŒ ุฎุทุฃ ุบูŠุฑ ู…ุชูˆู‚ุน: {str(e)}"),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
gr.update(visible=False), gr.update(visible=False),
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
]
def create_dashboard(data: dict) -> str:
"""Create dashboard HTML."""
orig = data.get("original_info", {})
mod = data.get("modified_info", {})
summary = data.get("file_comparison", {}).get("summary", {})
manifest = data.get("manifest_comparison", {}).get("changes", {})
perms = manifest.get("permissions", {})
dex = data.get("dex_analysis", {})
native = data.get("native_libraries", {})
sig = data.get("signature_comparison", {})
html = f"""
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; padding: 10px;">
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #00d4ff; margin: 0 0 10px 0;">๐Ÿ“ฆ APK ุงู„ุฃุตู„ูŠ</h4>
<p style="color: #eee; margin: 5px 0;">ุงู„ุญุฌู…: <strong>{orig.get('size_formatted', 'N/A')}</strong></p>
<p style="color: #888; font-size: 11px; word-break: break-all;">SHA-256: {orig.get('sha256', 'N/A')[:32]}...</p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #ff6b6b; margin: 0 0 10px 0;">๐Ÿ“ฆ APK ุงู„ู…ุนุฏู„</h4>
<p style="color: #eee; margin: 5px 0;">ุงู„ุญุฌู…: <strong>{mod.get('size_formatted', 'N/A')}</strong></p>
<p style="color: #888; font-size: 11px; word-break: break-all;">SHA-256: {mod.get('sha256', 'N/A')[:32]}...</p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #feca57; margin: 0 0 10px 0;">๐Ÿ“Š ุฅุฌู…ุงู„ูŠ ุงู„ู…ู„ูุงุช</h4>
<p style="color: #4ecdc4; font-size: 32px; margin: 0;"><strong>{summary.get('total', 0)}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #48dbfb; margin: 0 0 10px 0;">โœ… ู…ุชุทุงุจู‚ุฉ</h4>
<p style="color: #4ecdc4; font-size: 32px; margin: 0;"><strong>{summary.get('unchanged', 0)}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #feca57; margin: 0 0 10px 0;">โš ๏ธ ู…ุนุฏู„ุฉ</h4>
<p style="color: #feca57; font-size: 32px; margin: 0;"><strong>{summary.get('modified', 0)}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #4ecdc4; margin: 0 0 10px 0;">โž• ู…ุถุงูุฉ</h4>
<p style="color: #4ecdc4; font-size: 32px; margin: 0;"><strong>{summary.get('added', 0)}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #ff6b6b; margin: 0 0 10px 0;">โž– ู…ุญุฐูˆูุฉ</h4>
<p style="color: #ff6b6b; font-size: 32px; margin: 0;"><strong>{summary.get('removed', 0)}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #ff9f43; margin: 0 0 10px 0;">๐Ÿ” ุงู„ุชูˆู‚ูŠุน</h4>
<p style="color: {'#ff6b6b' if sig.get('signature_changed') else '#4ecdc4'}; font-size: 18px; margin: 0;">
<strong>{'ุชุบูŠุฑ!' if sig.get('signature_changed') else 'ู…ุชุทุงุจู‚'}</strong>
</p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #a29bfe; margin: 0 0 10px 0;">๐Ÿ“‹ ู…ู„ูุงุช DEX</h4>
<p style="color: #eee; margin: 5px 0;">ุงู„ุนุฏุฏ: <strong>{dex.get('dex_count', 0)}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #fd79a8; margin: 0 0 10px 0;">๐Ÿ”ง Native Libs</h4>
<p style="color: #eee; margin: 5px 0;">ู…ุถุงูุฉ: <strong>{len(native.get('added', []))}</strong></p>
<p style="color: #eee; margin: 5px 0;">ู…ุญุฐูˆูุฉ: <strong>{len(native.get('removed', []))}</strong></p>
</div>
<div style="background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; padding: 20px; border: 1px solid #0f3460;">
<h4 style="color: #00b894; margin: 0 0 10px 0;">๐Ÿ”‘ ุตู„ุงุญูŠุงุช ุฌุฏูŠุฏุฉ</h4>
<p style="color: #4ecdc4; font-size: 24px; margin: 0;"><strong>{len(perms.get('added', []))}</strong></p>
</div>
</div>
"""
return html
def create_manifest_display(data: dict) -> str:
"""Create manifest changes display."""
manifest = data.get("manifest_comparison", {})
changes = manifest.get("changes", {})
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #00d4ff;">๐Ÿ“‹ ุชุบูŠูŠุฑุงุช AndroidManifest.xml</h3>'
# Package info
pkg = changes.get("package_info", {})
if pkg:
html += '<h4 style="color: #feca57;">ู…ุนู„ูˆู…ุงุช ุงู„ุญุฒู…ุฉ:</h4><ul>'
for key, vals in pkg.items():
html += f'<li><code>{key}</code>: <span style="color: #ff6b6b;">{vals.get("original", "N/A")}</span> โ†’ <span style="color: #4ecdc4;">{vals.get("modified", "N/A")}</span></li>'
html += '</ul>'
# Permissions
perms = changes.get("permissions", {})
if perms.get("added"):
html += '<h4 style="color: #4ecdc4;">โž• ุตู„ุงุญูŠุงุช ู…ุถุงูุฉ:</h4><ul>'
for p in perms["added"]:
html += f'<li style="color: #4ecdc4;">+ {p}</li>'
html += '</ul>'
if perms.get("removed"):
html += '<h4 style="color: #ff6b6b;">โž– ุตู„ุงุญูŠุงุช ู…ุญุฐูˆูุฉ:</h4><ul>'
for p in perms["removed"]:
html += f'<li style="color: #ff6b6b;">- {p}</li>'
html += '</ul>'
# Activities
acts = changes.get("activities", {})
if acts.get("added"):
html += '<h4 style="color: #4ecdc4;">โž• Activities ุฌุฏูŠุฏุฉ:</h4><ul>'
for a in acts["added"]:
html += f'<li style="color: #4ecdc4;">+ {a.get("name", "Unknown")}</li>'
html += '</ul>'
if acts.get("removed"):
html += '<h4 style="color: #ff6b6b;">โž– Activities ู…ุญุฐูˆูุฉ:</h4><ul>'
for a in acts["removed"]:
html += f'<li style="color: #ff6b6b;">- {a.get("name", "Unknown")}</li>'
html += '</ul>'
# Services
svcs = changes.get("services", {})
if svcs.get("added"):
html += '<h4 style="color: #4ecdc4;">โž• Services ุฌุฏูŠุฏุฉ:</h4><ul>'
for s in svcs["added"]:
html += f'<li style="color: #4ecdc4;">+ {s.get("name", "Unknown")}</li>'
html += '</ul>'
if svcs.get("removed"):
html += '<h4 style="color: #ff6b6b;">โž– Services ู…ุญุฐูˆูุฉ:</h4><ul>'
for s in svcs["removed"]:
html += f'<li style="color: #ff6b6b;">- {s.get("name", "Unknown")}</li>'
html += '</ul>'
# Receivers
recs = changes.get("receivers", {})
if recs.get("added"):
html += '<h4 style="color: #4ecdc4;">โž• Receivers ุฌุฏูŠุฏุฉ:</h4><ul>'
for r in recs["added"]:
html += f'<li style="color: #4ecdc4;">+ {r.get("name", "Unknown")}</li>'
html += '</ul>'
if recs.get("removed"):
html += '<h4 style="color: #ff6b6b;">โž– Receivers ู…ุญุฐูˆูุฉ:</h4><ul>'
for r in recs["removed"]:
html += f'<li style="color: #ff6b6b;">- {r.get("name", "Unknown")}</li>'
html += '</ul>'
html += '</div>'
return html
def create_dex_display(data: dict) -> str:
"""Create DEX analysis display."""
dex = data.get("dex_analysis", {})
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #00d4ff;">๐Ÿ“Š ุชุญู„ูŠู„ ู…ู„ูุงุช DEX</h3>'
comp = dex.get("comparison", {})
html += f'<p>ุนุฏุฏ ู…ู„ูุงุช DEX ููŠ ุงู„ุฃุตู„ูŠ: <strong>{comp.get("original_count", 0)}</strong></p>'
html += f'<p>ุนุฏุฏ ู…ู„ูุงุช DEX ููŠ ุงู„ู…ุนุฏู„: <strong>{comp.get("modified_count", 0)}</strong></p>'
if comp.get("added"):
html += '<h4 style="color: #4ecdc4;">โž• DEX files ู…ุถุงูุฉ:</h4><ul>'
for f in comp["added"]:
html += f'<li style="color: #4ecdc4;">+ {f}</li>'
html += '</ul>'
if comp.get("removed"):
html += '<h4 style="color: #ff6b6b;">โž– DEX files ู…ุญุฐูˆูุฉ:</h4><ul>'
for f in comp["removed"]:
html += f'<li style="color: #ff6b6b;">- {f}</li>'
html += '</ul>'
# DEX file details
html += '<h4 style="color: #feca57;">ุชูุงุตูŠู„ ู…ู„ูุงุช DEX:</h4>'
for fname, fdata in dex.get("dex_files", {}).items():
header = fdata.get("header", {})
if "error" not in header:
html += f'<div style="background: #16213e; padding: 10px; margin: 10px 0; border-radius: 8px;">'
html += f'<strong>{fname}</strong><br>'
html += f'Classes: {header.get("class_defs_count", "N/A")} | '
html += f'Methods: {header.get("method_ids_count", "N/A")} | '
html += f'Fields: {header.get("field_ids_count", "N/A")} | '
html += f'Strings: {header.get("string_ids_count", "N/A")}'
html += '</div>'
# URLs
urls = dex.get("all_urls", [])
if urls:
html += '<h4 style="color: #a29bfe;">๐Ÿ”— URLs ุงู„ู…ุณุชุฎุฑุฌุฉ:</h4><ul>'
for url in urls[:30]:
html += f'<li><code>{url}</code></li>'
if len(urls) > 30:
html += f'<li style="color: #888;">... ูˆ {len(urls) - 30} ุฃุฎุฑู‰</li>'
html += '</ul>'
# IPs
ips = dex.get("all_ips", [])
if ips:
html += '<h4 style="color: #fd79a8;">๐ŸŒ ุนู†ุงูˆูŠู† IP:</h4><ul>'
for ip in ips[:20]:
html += f'<li><code>{ip}</code></li>'
html += '</ul>'
html += '</div>'
return html
def create_native_display(data: dict) -> str:
"""Create native libraries display."""
native = data.get("native_libraries", {})
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #00d4ff;">๐Ÿ”ง ู…ูƒุชุจุงุช Native</h3>'
if native.get("added"):
html += '<h4 style="color: #4ecdc4;">โž• ู…ูƒุชุจุงุช ู…ุถุงูุฉ:</h4><ul>'
for lib in native["added"]:
html += f'<li style="color: #4ecdc4;">+ {lib}</li>'
html += '</ul>'
if native.get("removed"):
html += '<h4 style="color: #ff6b6b;">โž– ู…ูƒุชุจุงุช ู…ุญุฐูˆูุฉ:</h4><ul>'
for lib in native["removed"]:
html += f'<li style="color: #ff6b6b;">- {lib}</li>'
html += '</ul>'
if native.get("modified"):
html += '<h4 style="color: #feca57;">โš ๏ธ ู…ูƒุชุจุงุช ู…ุนุฏู„ุฉ:</h4><ul>'
for lib in native["modified"]:
html += f'<li style="color: #feca57;">~ {lib}</li>'
html += '</ul>'
if native.get("details"):
html += '<h4 style="color: #feca57;">ุชูุงุตูŠู„ ุฌู…ูŠุน ุงู„ู…ูƒุชุจุงุช:</h4>'
html += '<table style="width:100%; border-collapse: collapse;">'
html += '<tr style="background: #0f3460;"><th style="padding: 8px; border: 1px solid #333;">ุงู„ู…ูƒุชุจุฉ</th><th style="padding: 8px; border: 1px solid #333;">ุงู„ุญุงู„ุฉ</th><th style="padding: 8px; border: 1px solid #333;">ุงู„ุญุฌู… ุงู„ุฃุตู„ูŠ</th><th style="padding: 8px; border: 1px solid #333;">ุงู„ุญุฌู… ุงู„ู…ุนุฏู„</th></tr>'
for detail in native["details"]:
status_color = {"Added": "#4ecdc4", "Removed": "#ff6b6b", "Modified": "#feca57", "Unchanged": "#48dbfb"}.get(detail["status"], "#eee")
html += f'<tr><td style="padding: 8px; border: 1px solid #333;">{detail["name"]}</td>'
html += f'<td style="padding: 8px; border: 1px solid #333; color: {status_color};">{detail["status"]}</td>'
html += f'<td style="padding: 8px; border: 1px solid #333;">{detail.get("original_size", "N/A")}</td>'
html += f'<td style="padding: 8px; border: 1px solid #333;">{detail.get("modified_size", "N/A")}</td></tr>'
html += '</table>'
html += '</div>'
return html
def create_resources_display(data: dict) -> str:
"""Create resources display."""
res = data.get("resources", {})
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #00d4ff;">๐ŸŽจ ุงู„ู…ูˆุงุฑุฏ (Resources)</h3>'
res_data = res.get("res", {})
orig_res = res_data.get("original", {})
mod_res = res_data.get("modified", {})
html += f'<p>ู…ู„ูุงุช res ููŠ ุงู„ุฃุตู„ูŠ: <strong>{orig_res.get("count", 0)}</strong></p>'
html += f'<p>ู…ู„ูุงุช res ููŠ ุงู„ู…ุนุฏู„: <strong>{mod_res.get("count", 0)}</strong></p>'
assets_data = res.get("assets", {})
orig_assets = assets_data.get("original", {})
mod_assets = assets_data.get("modified", {})
html += f'<p>ู…ู„ูุงุช assets ููŠ ุงู„ุฃุตู„ูŠ: <strong>{orig_assets.get("count", 0)}</strong></p>'
html += f'<p>ู…ู„ูุงุช assets ููŠ ุงู„ู…ุนุฏู„: <strong>{mod_assets.get("count", 0)}</strong></p>'
arsc = res.get("resources_arsc", {})
if arsc.get("original_exists") or arsc.get("modified_exists"):
html += '<h4 style="color: #feca57;">resources.arsc:</h4>'
html += f'<p>ุงู„ุฃุตู„ูŠ: {"ู…ูˆุฌูˆุฏ" if arsc.get("original_exists") else "ุบูŠุฑ ู…ูˆุฌูˆุฏ"} ({arsc.get("original_size", 0):,} bytes)</p>'
html += f'<p>ุงู„ู…ุนุฏู„: {"ู…ูˆุฌูˆุฏ" if arsc.get("modified_exists") else "ุบูŠุฑ ู…ูˆุฌูˆุฏ"} ({arsc.get("modified_size", 0):,} bytes)</p>'
html += '</div>'
return html
def create_signature_display(data: dict) -> str:
"""Create signature display."""
sig = data.get("signature_comparison", {})
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #00d4ff;">๐Ÿ” ุชุญู„ูŠู„ ุงู„ุชูˆู‚ูŠุน</h3>'
if sig.get("signature_changed"):
html += '<div style="background: #ff6b6b22; border: 2px solid #ff6b6b; border-radius: 10px; padding: 15px; margin: 10px 0;">'
html += '<h4 style="color: #ff6b6b;">โš ๏ธ ุงู„ุชูˆู‚ูŠุน ุชุบูŠุฑ!</h4>'
html += '<p>ุชู… ุฅุนุงุฏุฉ ุชูˆู‚ูŠุน APK. ู‡ุฐุง ุฃู…ุฑ ุทุจูŠุนูŠ ุฅุฐุง ุชู… ุชุนุฏูŠู„ ุงู„ุชุทุจูŠู‚.</p>'
html += '</div>'
else:
html += '<div style="background: #4ecdc422; border: 2px solid #4ecdc4; border-radius: 10px; padding: 15px; margin: 10px 0;">'
html += '<h4 style="color: #4ecdc4;">โœ… ุงู„ุชูˆู‚ูŠุน ู…ุชุทุงุจู‚</h4>'
html += '<p>ุงู„ุชูˆู‚ูŠุน ุงู„ุฑู‚ู…ูŠ ู„ู… ูŠุชุบูŠุฑ ุจูŠู† ุงู„ู†ุณุฎุชูŠู†.</p>'
html += '</div>'
orig_sig = sig.get("original", {})
mod_sig = sig.get("modified", {})
html += '<h4 style="color: #feca57;">ู…ุนู„ูˆู…ุงุช ุงู„ุชูˆู‚ูŠุน ุงู„ุฃุตู„ูŠ:</h4>'
html += f'<p>V1 Signing: <strong>{"ู†ุนู…" if orig_sig.get("has_v1_signing") else "ู„ุง"}</strong></p>'
html += f'<p>V2 Signing: <strong>{"ู†ุนู…" if orig_sig.get("has_v2_signing") else "ู„ุง"}</strong></p>'
html += f'<p>V3 Signing: <strong>{"ู†ุนู…" if orig_sig.get("has_v3_signing") else "ู„ุง"}</strong></p>'
html += '<h4 style="color: #feca57;">ู…ุนู„ูˆู…ุงุช ุงู„ุชูˆู‚ูŠุน ุงู„ู…ุนุฏู„:</h4>'
html += f'<p>V1 Signing: <strong>{"ู†ุนู…" if mod_sig.get("has_v1_signing") else "ู„ุง"}</strong></p>'
html += f'<p>V2 Signing: <strong>{"ู†ุนู…" if mod_sig.get("has_v2_signing") else "ู„ุง"}</strong></p>'
html += f'<p>V3 Signing: <strong>{"ู†ุนู…" if mod_sig.get("has_v3_signing") else "ู„ุง"}</strong></p>'
# Certificates
orig_certs = orig_sig.get("certificates", [])
if orig_certs:
html += '<h4 style="color: #a29bfe;">ุงู„ุดู‡ุงุฏุงุช ุงู„ุฃุตู„ูŠุฉ:</h4><ul>'
for cert in orig_certs:
html += f'<li><code>{cert["filename"]}</code> - SHA-256: <code>{cert["sha256"][:32]}...</code></li>'
html += '</ul>'
html += '</div>'
return html
def create_suspicious_display(data: dict) -> str:
"""Create suspicious changes display."""
suspicious = data.get("suspicious_changes", [])
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #ff6b6b;">โš ๏ธ ุชุบูŠูŠุฑุงุช ู…ุดุจูˆู‡ุฉ ู…ุญุชู…ู„ุฉ</h3>'
if not suspicious:
html += '<div style="background: #4ecdc422; border: 2px solid #4ecdc4; border-radius: 10px; padding: 15px;">'
html += '<p style="color: #4ecdc4;">โœ… ู„ู… ูŠุชู… ุงูƒุชุดุงู ุชุบูŠูŠุฑุงุช ู…ุดุจูˆู‡ุฉ ูˆุงุถุญุฉ.</p>'
html += '<p style="color: #888; font-size: 12px;">ู…ู„ุงุญุธุฉ: ู‡ุฐุง ู„ุง ูŠุนู†ูŠ ุฃู† ุงู„ุชุทุจูŠู‚ ุขู…ู† 100%. ูŠูู†ุตุญ ุจุงู„ู…ุฑุงุฌุนุฉ ุงู„ูŠุฏูˆูŠุฉ.</p>'
html += '</div>'
else:
html += '<div style="background: #ff6b6b22; border: 2px solid #ff6b6b; border-radius: 10px; padding: 15px;">'
html += '<p style="color: #ff6b6b;">โš ๏ธ ุชู… ุงูƒุชุดุงู ุงู„ู…ุคุดุฑุงุช ุงู„ุชุงู„ูŠุฉ (ุชุชุทู„ุจ ู…ุฑุงุฌุนุฉ ูŠุฏูˆูŠุฉ):</p>'
html += '<ul>'
for item in suspicious:
html += f'<li style="color: #ff9f43; margin: 5px 0;">โš ๏ธ {item}</li>'
html += '</ul>'
html += '<p style="color: #888; font-size: 12px;">ู…ู„ุงุญุธุฉ: ู‡ุฐู‡ ุงู„ู…ุคุดุฑุงุช ู„ุง ุชุนู†ูŠ ุจุงู„ุถุฑูˆุฑุฉ ูˆุฌูˆุฏ ุชู‡ุฏูŠุฏ. ุชุชุทู„ุจ ู…ุฑุงุฌุนุฉ ูŠุฏูˆูŠุฉ.</p>'
html += '</div>'
html += '</div>'
return html
def create_file_table(data: dict) -> str:
"""Create detailed file comparison table."""
details = data.get("file_comparison", {}).get("details", [])
html = '<div style="padding: 15px;">'
html += '<h3 style="color: #00d4ff;">๐Ÿ“‘ ู…ู‚ุงุฑู†ุฉ ุชูุตูŠู„ูŠุฉ ู„ู„ู…ู„ูุงุช</h3>'
html += '<div style="max-height: 600px; overflow-y: auto;">'
html += '<table style="width:100%; border-collapse: collapse; font-size: 12px;">'
html += '<tr style="background: #0f3460; position: sticky; top: 0;">'
html += '<th style="padding: 8px; border: 1px solid #333; text-align: left;">ุงู„ู…ุณุงุฑ</th>'
html += '<th style="padding: 8px; border: 1px solid #333;">ุงู„ุญุงู„ุฉ</th>'
html += '<th style="padding: 8px; border: 1px solid #333;">ุญุฌู… ุฃุตู„ูŠ</th>'
html += '<th style="padding: 8px; border: 1px solid #333;">ุญุฌู… ู…ุนุฏู„</th>'
html += '<th style="padding: 8px; border: 1px solid #333;">SHA-256 ุฃุตู„ูŠ</th>'
html += '<th style="padding: 8px; border: 1px solid #333;">SHA-256 ู…ุนุฏู„</th>'
html += '</tr>'
for detail in details:
status = detail["status"]
status_color = {
"Added": "#4ecdc4",
"Removed": "#ff6b6b",
"Modified": "#feca57",
"Unchanged": "#48dbfb"
}.get(status, "#eee")
orig_size = detail.get("original_size", 0)
mod_size = detail.get("modified_size", 0)
html += f'<tr>'
html += f'<td style="padding: 6px; border: 1px solid #333; word-break: break-all;"><code>{detail["path"]}</code></td>'
html += f'<td style="padding: 6px; border: 1px solid #333; color: {status_color}; text-align: center;"><strong>{status}</strong></td>'
html += f'<td style="padding: 6px; border: 1px solid #333; text-align: right;">{orig_size:,}</td>'
html += f'<td style="padding: 6px; border: 1px solid #333; text-align: right;">{mod_size:,}</td>'
html += f'<td style="padding: 6px; border: 1px solid #333; font-size: 10px; word-break: break-all;"><code>{detail.get("original_sha256", "")[:16]}...</code></td>'
html += f'<td style="padding: 6px; border: 1px solid #333; font-size: 10px; word-break: break-all;"><code>{detail.get("modified_sha256", "")[:16]}...</code></td>'
html += '</tr>'
html += '</table></div></div>'
return html
def copy_report(report_text):
"""Return report for copying."""
return report_text
def download_txt(report_text):
"""Prepare TXT download."""
return report_text
def download_json(report_text):
"""Prepare JSON download."""
return report_text
def download_html(report_text):
"""Prepare HTML download."""
return report_text
# ==================== GRADIO UI ====================
css = """
:root {
--primary: #00d4ff;
--secondary: #ff6b6b;
--accent: #feca57;
--success: #4ecdc4;
--bg: #1a1a2e;
--card: #16213e;
--border: #0f3460;
}
body {
background: var(--bg) !important;
color: #eee !important;
}
.gradio-container {
background: var(--bg) !important;
}
.tabs {
background: var(--card) !important;
border-radius: 12px !important;
border: 1px solid var(--border) !important;
}
.tab-nav {
background: var(--card) !important;
border-bottom: 2px solid var(--border) !important;
}
.tab-nav button {
color: #eee !important;
font-weight: bold !important;
}
.tab-nav button.selected {
color: var(--primary) !important;
border-bottom: 3px solid var(--primary) !important;
}
.upload-box {
border: 2px dashed var(--border) !important;
border-radius: 12px !important;
background: var(--card) !important;
}
.upload-box:hover {
border-color: var(--primary) !important;
}
.primary-btn {
background: linear-gradient(135deg, var(--primary), #0099cc) !important;
color: #000 !important;
font-weight: bold !important;
border: none !important;
border-radius: 8px !important;
}
.primary-btn:hover {
background: linear-gradient(135deg, #00e5ff, var(--primary)) !important;
}
.error-box {
background: #ff6b6b22 !important;
border: 2px solid var(--secondary) !important;
border-radius: 10px !important;
color: var(--secondary) !important;
}
.info-box {
background: var(--card) !important;
border: 1px solid var(--border) !important;
border-radius: 10px !important;
padding: 15px !important;
}
"""
with gr.Blocks(title="APK Deep Diff Analyzer") as demo:
gr.Markdown("""
<div style="text-align: center; padding: 20px;">
<h1 style="color: #00d4ff; font-size: 2.5em; margin: 0;">๐Ÿ” APK Deep Diff Analyzer</h1>
<p style="color: #888; font-size: 1.1em; margin: 10px 0;">
ุฃุฏุงุฉ ุชุญู„ูŠู„ ุนู…ูŠู‚ ูˆู…ู‚ุงุฑู†ุฉ ุงุญุชุฑุงููŠุฉ ู„ู…ู„ูุงุช Android APK
</p>
<p style="color: #666; font-size: 0.9em;">
Professional APK Forensic Analysis & Deep Diff Tool
</p>
</div>
""")
# Error message
error_msg = gr.Markdown(visible=False, elem_classes=["error-box"])
with gr.Row():
with gr.Column():
gr.Markdown("### ๐Ÿ“ฆ APK ุงู„ุฃุตู„ูŠ (Original)")
orig_upload = gr.File(
label="",
file_types=[".apk"],
elem_classes=["upload-box"]
)
orig_info = gr.Markdown(elem_classes=["info-box"])
with gr.Column():
gr.Markdown("### ๐Ÿ“ฆ APK ุงู„ู…ุนุฏู„ (Modified)")
mod_upload = gr.File(
label="",
file_types=[".apk"],
elem_classes=["upload-box"]
)
mod_info = gr.Markdown(elem_classes=["info-box"])
# Update file info on upload
orig_upload.change(
fn=update_file_info,
inputs=[orig_upload],
outputs=[orig_info]
)
mod_upload.change(
fn=update_file_info,
inputs=[mod_upload],
outputs=[mod_info]
)
# Start analysis button
analyze_btn = gr.Button(
"๐Ÿš€ ุจุฏุก ุงู„ุชุญู„ูŠู„ ุงู„ุนู…ูŠู‚ | Start Deep Analysis",
elem_classes=["primary-btn"],
size="lg"
)
# Results tabs (initially hidden)
with gr.Tabs(visible=False) as results_tabs:
with gr.TabItem("๐Ÿ“Š Dashboard"):
dashboard_output = gr.HTML()
with gr.TabItem("๐Ÿ“‹ Manifest"):
manifest_output = gr.HTML()
with gr.TabItem("๐Ÿ“Š DEX Analysis"):
dex_output = gr.HTML()
with gr.TabItem("๐Ÿ”ง Native Libraries"):
native_output = gr.HTML()
with gr.TabItem("๐ŸŽจ Resources"):
resources_output = gr.HTML()
with gr.TabItem("๐Ÿ” Signature"):
signature_output = gr.HTML()
with gr.TabItem("โš ๏ธ Suspicious"):
suspicious_output = gr.HTML()
with gr.TabItem("๐Ÿ“‘ File Comparison"):
file_table_output = gr.HTML()
with gr.TabItem("๐Ÿ“„ Reports"):
with gr.Row():
with gr.Column():
gr.Markdown("### ๐Ÿ“ ุชู‚ุฑูŠุฑ ู†ุตูŠ (TXT)")
txt_report = gr.Textbox(
label="",
lines=20,
interactive=False
)
copy_txt_btn = gr.Button("๐Ÿ“‹ ู†ุณุฎ ุงู„ุชู‚ุฑูŠุฑ", visible=False)
with gr.Column():
gr.Markdown("### ๐Ÿ“Š ุชู‚ุฑูŠุฑ JSON")
json_report = gr.Textbox(
label="",
lines=20,
interactive=False
)
copy_json_btn = gr.Button("๐Ÿ“‹ ู†ุณุฎ JSON", visible=False)
with gr.Row():
with gr.Column():
gr.Markdown("### ๐ŸŒ ุชู‚ุฑูŠุฑ HTML")
html_report = gr.Textbox(
label="",
lines=20,
interactive=False
)
copy_html_btn = gr.Button("๐Ÿ“‹ ู†ุณุฎ HTML", visible=False)
# Privacy notice
gr.Markdown("""
<div style="background: #16213e; border: 1px solid #0f3460; border-radius: 10px; padding: 15px; margin-top: 20px; text-align: center;">
<p style="color: #888; font-size: 0.9em; margin: 0;">
๐Ÿ”’ <strong>Privacy Notice:</strong> ุงู„ู…ู„ูุงุช ุงู„ู…ุฑููˆุนุฉ ุชูุณุชุฎุฏู… ู„ู„ุชุญู„ูŠู„ ูู‚ุท ูˆู„ุง ุชูุญูุธ ุนู„ู‰ ุงู„ุฎุงุฏู….
<br>
Uploaded files are used for analysis only and are not stored on the server.
</p>
</div>
""")
# Analysis button click
analyze_btn.click(
fn=run_analysis,
inputs=[orig_upload, mod_upload],
outputs=[
error_msg,
results_tabs,
dashboard_output,
manifest_output,
dex_output,
native_output,
resources_output,
signature_output,
suspicious_output,
file_table_output,
txt_report,
json_report,
html_report,
copy_txt_btn,
copy_json_btn,
copy_html_btn,
]
)
if __name__ == "__main__":
demo.launch(css=css, theme=gr.themes.Soft())