""" 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"""

📦 APK الأصلي

الحجم: {orig.get('size_formatted', 'N/A')}

SHA-256: {orig.get('sha256', 'N/A')[:32]}...

📦 APK المعدل

الحجم: {mod.get('size_formatted', 'N/A')}

SHA-256: {mod.get('sha256', 'N/A')[:32]}...

📊 إجمالي الملفات

{summary.get('total', 0)}

✅ متطابقة

{summary.get('unchanged', 0)}

⚠️ معدلة

{summary.get('modified', 0)}

➕ مضافة

{summary.get('added', 0)}

➖ محذوفة

{summary.get('removed', 0)}

🔐 التوقيع

{'تغير!' if sig.get('signature_changed') else 'متطابق'}

📋 ملفات DEX

العدد: {dex.get('dex_count', 0)}

🔧 Native Libs

مضافة: {len(native.get('added', []))}

محذوفة: {len(native.get('removed', []))}

🔑 صلاحيات جديدة

{len(perms.get('added', []))}

""" return html def create_manifest_display(data: dict) -> str: """Create manifest changes display.""" manifest = data.get("manifest_comparison", {}) changes = manifest.get("changes", {}) html = '
' html += '

📋 تغييرات AndroidManifest.xml

' # Package info pkg = changes.get("package_info", {}) if pkg: html += '

معلومات الحزمة:

' # Permissions perms = changes.get("permissions", {}) if perms.get("added"): html += '

➕ صلاحيات مضافة:

' if perms.get("removed"): html += '

➖ صلاحيات محذوفة:

' # Activities acts = changes.get("activities", {}) if acts.get("added"): html += '

➕ Activities جديدة:

' if acts.get("removed"): html += '

➖ Activities محذوفة:

' # Services svcs = changes.get("services", {}) if svcs.get("added"): html += '

➕ Services جديدة:

' if svcs.get("removed"): html += '

➖ Services محذوفة:

' # Receivers recs = changes.get("receivers", {}) if recs.get("added"): html += '

➕ Receivers جديدة:

' if recs.get("removed"): html += '

➖ Receivers محذوفة:

' html += '
' return html def create_dex_display(data: dict) -> str: """Create DEX analysis display.""" dex = data.get("dex_analysis", {}) html = '
' html += '

📊 تحليل ملفات DEX

' comp = dex.get("comparison", {}) html += f'

عدد ملفات DEX في الأصلي: {comp.get("original_count", 0)}

' html += f'

عدد ملفات DEX في المعدل: {comp.get("modified_count", 0)}

' if comp.get("added"): html += '

➕ DEX files مضافة:

' if comp.get("removed"): html += '

➖ DEX files محذوفة:

' # DEX file details html += '

تفاصيل ملفات DEX:

' for fname, fdata in dex.get("dex_files", {}).items(): header = fdata.get("header", {}) if "error" not in header: html += f'
' html += f'{fname}
' 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 += '
' # URLs urls = dex.get("all_urls", []) if urls: html += '

🔗 URLs المستخرجة:

' # IPs ips = dex.get("all_ips", []) if ips: html += '

🌐 عناوين IP:

' html += '
' return html def create_native_display(data: dict) -> str: """Create native libraries display.""" native = data.get("native_libraries", {}) html = '
' html += '

🔧 مكتبات Native

' if native.get("added"): html += '

➕ مكتبات مضافة:

' if native.get("removed"): html += '

➖ مكتبات محذوفة:

' if native.get("modified"): html += '

⚠️ مكتبات معدلة:

' if native.get("details"): html += '

تفاصيل جميع المكتبات:

' html += '' html += '' for detail in native["details"]: status_color = {"Added": "#4ecdc4", "Removed": "#ff6b6b", "Modified": "#feca57", "Unchanged": "#48dbfb"}.get(detail["status"], "#eee") html += f'' html += f'' html += f'' html += f'' html += '
المكتبةالحالةالحجم الأصليالحجم المعدل
{detail["name"]}{detail["status"]}{detail.get("original_size", "N/A")}{detail.get("modified_size", "N/A")}
' html += '
' return html def create_resources_display(data: dict) -> str: """Create resources display.""" res = data.get("resources", {}) html = '
' html += '

🎨 الموارد (Resources)

' res_data = res.get("res", {}) orig_res = res_data.get("original", {}) mod_res = res_data.get("modified", {}) html += f'

ملفات res في الأصلي: {orig_res.get("count", 0)}

' html += f'

ملفات res في المعدل: {mod_res.get("count", 0)}

' assets_data = res.get("assets", {}) orig_assets = assets_data.get("original", {}) mod_assets = assets_data.get("modified", {}) html += f'

ملفات assets في الأصلي: {orig_assets.get("count", 0)}

' html += f'

ملفات assets في المعدل: {mod_assets.get("count", 0)}

' arsc = res.get("resources_arsc", {}) if arsc.get("original_exists") or arsc.get("modified_exists"): html += '

resources.arsc:

' html += f'

الأصلي: {"موجود" if arsc.get("original_exists") else "غير موجود"} ({arsc.get("original_size", 0):,} bytes)

' html += f'

المعدل: {"موجود" if arsc.get("modified_exists") else "غير موجود"} ({arsc.get("modified_size", 0):,} bytes)

' html += '
' return html def create_signature_display(data: dict) -> str: """Create signature display.""" sig = data.get("signature_comparison", {}) html = '
' html += '

🔐 تحليل التوقيع

' if sig.get("signature_changed"): html += '
' html += '

⚠️ التوقيع تغير!

' html += '

تم إعادة توقيع APK. هذا أمر طبيعي إذا تم تعديل التطبيق.

' html += '
' else: html += '
' html += '

✅ التوقيع متطابق

' html += '

التوقيع الرقمي لم يتغير بين النسختين.

' html += '
' orig_sig = sig.get("original", {}) mod_sig = sig.get("modified", {}) html += '

معلومات التوقيع الأصلي:

' html += f'

V1 Signing: {"نعم" if orig_sig.get("has_v1_signing") else "لا"}

' html += f'

V2 Signing: {"نعم" if orig_sig.get("has_v2_signing") else "لا"}

' html += f'

V3 Signing: {"نعم" if orig_sig.get("has_v3_signing") else "لا"}

' html += '

معلومات التوقيع المعدل:

' html += f'

V1 Signing: {"نعم" if mod_sig.get("has_v1_signing") else "لا"}

' html += f'

V2 Signing: {"نعم" if mod_sig.get("has_v2_signing") else "لا"}

' html += f'

V3 Signing: {"نعم" if mod_sig.get("has_v3_signing") else "لا"}

' # Certificates orig_certs = orig_sig.get("certificates", []) if orig_certs: html += '

الشهادات الأصلية:

' html += '
' return html def create_suspicious_display(data: dict) -> str: """Create suspicious changes display.""" suspicious = data.get("suspicious_changes", []) html = '
' html += '

⚠️ تغييرات مشبوهة محتملة

' if not suspicious: html += '
' html += '

✅ لم يتم اكتشاف تغييرات مشبوهة واضحة.

' html += '

ملاحظة: هذا لا يعني أن التطبيق آمن 100%. يُنصح بالمراجعة اليدوية.

' html += '
' else: html += '
' html += '

⚠️ تم اكتشاف المؤشرات التالية (تتطلب مراجعة يدوية):

' html += '' html += '

ملاحظة: هذه المؤشرات لا تعني بالضرورة وجود تهديد. تتطلب مراجعة يدوية.

' html += '
' html += '
' return html def create_file_table(data: dict) -> str: """Create detailed file comparison table.""" details = data.get("file_comparison", {}).get("details", []) html = '
' html += '

📑 مقارنة تفصيلية للملفات

' html += '
' html += '' html += '' html += '' html += '' html += '' html += '' html += '' html += '' html += '' 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'' html += f'' html += f'' html += f'' html += f'' html += f'' html += f'' html += '' html += '
المسارالحالةحجم أصليحجم معدلSHA-256 أصليSHA-256 معدل
{detail["path"]}{status}{orig_size:,}{mod_size:,}{detail.get("original_sha256", "")[:16]}...{detail.get("modified_sha256", "")[:16]}...
' 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("""

🔍 APK Deep Diff Analyzer

أداة تحليل عميق ومقارنة احترافية لملفات Android APK

Professional APK Forensic Analysis & Deep Diff Tool

""") # 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("""

🔒 Privacy Notice: الملفات المرفوعة تُستخدم للتحليل فقط ولا تُحفظ على الخادم.
Uploaded files are used for analysis only and are not stored on the server.

""") # 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())