Spaces:
Paused
Paused
Download app.py from Skydata001/Apkk: direct link, hf CLI and curl.
- Browser
- Download file 34.2 kB
-
https://huggingface.co/spaces/Skydata001/Apkk/resolve/main/app.py
- Command line
-
hf download hf://spaces/Skydata001/Apkk/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Skydata001/Apkk/resolve/main/app.py
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()) | |