diff --git a/client/app.py b/client/app.py index f948311..009efaf 100644 --- a/client/app.py +++ b/client/app.py @@ -6,7 +6,7 @@ import logging import threading import time -import random +import secrets import requests import bsdiff4 import customtkinter as ctk @@ -51,6 +51,7 @@ def __init__(self, global_config, client_config, base_dir, ui_callback=None): self.ui_callback = ui_callback self.pause_event = threading.Event() self.manual_mode = False + self.current_phase = "idle" # Track phase for vuln testing self.server_url = f"{global_config['server']['url']}/static" self.public_key_path = os.path.join(base_dir, "keys", "public_key.pem") @@ -71,17 +72,89 @@ def _load_public_key(self): return None def trigger_visual(self, phase, message="", data=None): + self.current_phase = phase if self.ui_callback: self.ui_callback(phase, message, data) # Only pause if Manual Mode is enabled - if self.manual_mode and phase not in ["finish", "found"]: + if self.manual_mode and phase not in ["finish", "found", "breach"]: self.pause_event.clear() self.pause_event.wait() else: # Short aesthetic delay for automated mode time.sleep(0.8) + def simulate_attack(self): + """ + Simulates what a failure would look like at the current phase. + """ + vuln_map = { + "scan": ("MITM_HIJACK", "VULN_ERR_404_NOT_FOUND: Attacker redirected traffic to a dead server."), + "verify": ("SIGNATURE_FORGERY", "VULN_ERR_SIG_MISMATCH: Attacker modified manifest.json. Signature is invalid!"), + "ttl": ("REPLAY_ATTACK", "VULN_ERR_STALE_METADATA: Hacker providing an old (signed) manifest to force-downgrade."), + "download_patch": ("DOWNLOAD_CORRUPTION", "VULN_ERR_PACKET_LOSS: Patch download interrupted by network injection."), + "patch": ("BINARY_TAMPER", "VULN_ERR_PATCH_FAILED: Source executable on disk has been modified by malware."), + "integrity": ("FINGERPRINT_MISMATCH", "VULN_ERR_HASH_FAIL: Reconstructed binary does not match developer's original hash!") + } + + if self.current_phase in vuln_map: + code, msg = vuln_map[self.current_phase] + self.trigger_visual("breach", f"SIMULATED ATTACK: {code}\n{msg}") + + def simulate_vulnerability(self): + """ + Simulates a specific attack for the current security phase. + Provides visual feedback and failure codes for educational purposes. + """ + vulnerability_map = { + "scan": ( + "MITM_CONNECTION_HIJACK", + "VULN_ERR_404_SERVER_NOT_FOUND", + "BACKSTAGE: Attacker uses DNS Cache Poisoning or ARP Spoofing to redirect your Hub's request for 'manifest.json' to an unauthorized IP address controlled by the hacker. The Hub detects the server identity is unknown or unreachable." + ), + "verify": ( + "CRYPTOGRAPHIC_FORGERY", + "VULN_ERR_SIGNATURE_INVALID", + "BACKSTAGE: Attacker intercepts the 'manifest.json' and changes a single byte (e.g., the download URL). Because the file content changed, the SHA-512 hash embedded in the Ed25519 signature no longer matches the calculated hash of the tampered file." + ), + "ttl": ( + "METADATA_REPLAY_ATTACK", + "VULN_ERR_STALE_TIMESTAMP", + "BACKSTAGE: Attacker provides a manifest that was legitimately signed by the developer 6 months ago. Without TTL (Time-To-Live) checks, the Hub would accept this 'old' manifest and force-install a legacy version with known security holes." + ), + "version": ( + "DOWNGRADE_FORCE_ATTACK", + "VULN_ERR_VERSION_CONFLICT", + "BACKSTAGE: Attacker attempts to trick the Hub into installing v1.0 over your current v2.0. This is a common tactic to re-introduce vulnerabilities that were patched in newer versions. The Hub blocks this by comparing the signed 'latest_version' vs the local binary header." + ), + "download_patch": ( + "PACKAGE_CORRUPTION", + "VULN_ERR_PACKET_TAMPERING", + "BACKSTAGE: During the binary stream transmission, an attacker injects noise or malicious bytes into the TLS packets. This corrupts the bsdiff4 delta file. The Hub's network layer detects the protocol violation or packet checksum failure." + ), + "patch": ( + "MALWARE_MEMORY_INJECTION", + "VULN_ERR_PATCH_SURGERY_FAIL", + "BACKSTAGE: While the Hub is reading 'app.exe' into RAM, a local piece of malware (Trojan) tries to modify the file on disk. This causes the bsdiff4 reconstruction engine to fail because the 'base' bytes are no longer what the developer used to build the patch." + ), + "integrity": ( + "FINGERPRINT_BREACH", + "VULN_ERR_HASH_IDENTITY_MISMATCH", + "BACKSTAGE: Even if an attacker successfully bypasses the signature, they cannot forge the final SHA-256 hash. The Hub calculates the 'Digital Fingerprint' of the reconstructed app and finds it differs from the developer's signed blueprint. This is the 'Fail-Safe' layer." + ) + } + + if self.current_phase in vulnerability_map: + code, title, backstage = vulnerability_map[self.current_phase] + if self.ui_callback: + self.ui_callback("breach", f"BLOCKED ATTACK: {code}", { + "BREACH_CODE": code, + "ANALYSIS": "Blocked by Zero-Trust Sentinel", + "TECHNICAL_BACKSTAGE": backstage + }) + return backstage + return "No vulnerability scenario defined for this phase." + def calculate_sha256(self, filepath): sha256_hash = hashlib.sha256() with open(filepath, "rb") as f: @@ -145,7 +218,7 @@ def check_app_update(self, app_id): self.trigger_visual("success", "SOURCE VERIFIED: Trusted Developer confirmed.") # 3. Defensive Rules - manifest = json.loads(m_bytes.decode('utf-8')) + manifest = m_resp.json() metadata = manifest["metadata"] # Expiry Check @@ -225,7 +298,7 @@ def apply_update(self, app_id, update_info): computed_hash = self.calculate_sha256(temp_new_exe) self.trigger_visual("hash_compare", f"Comparing Digital Fingerprints:\nTarget: {expected_hash[:20]}...\nActual: {computed_hash[:20]}...", - {"actual": computed_hash[:24] + "..."}) + {"TARGET_HASH": expected_hash[:24] + "...", "ACTUAL_HASH": computed_hash[:24] + "..."}) if computed_hash != expected_hash: if os.path.exists(temp_new_exe): os.remove(temp_new_exe) @@ -289,25 +362,32 @@ def __init__(self, g_cfg, c_cfg, base_dir): # SPLIT-SCREEN CONTAINER self.center_container = ctk.CTkFrame(self.scroll_frame, fg_color="transparent") self.center_container.grid(row=2, column=0, padx=50, pady=10, sticky="nsew") - self.center_container.grid_columnconfigure(0, weight=1) # Left - self.center_container.grid_columnconfigure(1, weight=0) # Right + self.center_container.grid_columnconfigure(0, weight=1, uniform="demo_split") + self.center_container.grid_columnconfigure(1, weight=0, uniform="demo_split") # LEFT HALF: THE BLACKBOARD / COMMAND BOX (Fixed Height) self.data_frame = ctk.CTkFrame(self.center_container, fg_color="#1a1a1a", border_width=2, border_color="#2c3e50", height=400) self.data_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew", columnspan=2) self.data_frame.grid_propagate(False) - self.step_label = ctk.CTkLabel(self.data_frame, text="System Standby. Awaiting Scan...", font=self.status_font, text_color="#bdc3c7") + self.step_label = ctk.CTkLabel(self.data_frame, text="System Standby. Awaiting Scan...", + font=self.status_font, text_color="#bdc3c7", wraplength=450, justify="center") self.step_label.pack(pady=(20, 5)) - self.data_display = ctk.CTkLabel(self.data_frame, text="", font=self.code_font, text_color="#2ecc71") + # Using CTkLabel with wraplength for perfect centering and text wrapping + self.data_display = ctk.CTkLabel(self.data_frame, text="", font=self.code_font, text_color="#2ecc71", wraplength=450, justify="center") self.data_display.pack(pady=10) # NEXT STEP BUTTON self.btn_next = ctk.CTkButton(self.data_frame, text="NEXT STEP ➔", font=self.big_btn_font, - fg_color="#27ae60", hover_color="#2ecc71", height=60, width=300, + fg_color="#27ae60", hover_color="#2ecc71", height=60, width=250, command=self.on_next_click) + # TEST VULNERABILITY BUTTON + self.btn_vuln = ctk.CTkButton(self.data_frame, text="TEST VULNERABILITY ⚠️", font=self.big_btn_font, + fg_color="#c0392b", hover_color="#e74c3c", height=60, width=250, + command=self.on_test_vulnerability) + # RIGHT HALF: THE DEBUG / ANALYSIS BOX (Fixed Height) self.verbose_frame = ctk.CTkFrame(self.center_container, fg_color="#1e272e", border_width=1, border_color="#34495e", height=400) self.verbose_frame.grid_propagate(False) @@ -333,11 +413,12 @@ def __init__(self, g_cfg, c_cfg, base_dir): "hello_world": "Hello World App" } self.app_widgets = {} + self.is_updating = False # Global Concurrency Lock self.refresh_app_list() def setup_canvas(self): # Connection Line - self.canvas.create_line(250, 120, 780, 120, fill="#2c3e50", width=4, dash=(10, 10)) + self.canvas.create_line(250, 120, 800, 120, fill="#2c3e50", width=4, dash=(10, 10)) # Cloud/Server (Left) self.canvas.create_text(260, 120, text="☁️", font=("Arial", 70), fill="#3498db", tags="cloud") @@ -362,13 +443,19 @@ def toggle_demo_mode(self): self.center_container.grid_columnconfigure(1, weight=0, uniform="") self.center_container.grid_columnconfigure(0, weight=1, uniform="") self.data_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew", columnspan=2) + self.btn_vuln.pack_forget() self.step_label.configure(text=f"DEMO MODE: {'ENABLED' if self.client.manual_mode else 'DISABLED'}") def on_next_click(self): self.btn_next.pack_forget() + self.btn_vuln.pack_forget() self.client.pause_event.set() + def on_test_vulnerability(self): + """Triggers a simulated attack for the current phase.""" + self.client.simulate_vulnerability() + def on_security_event(self, phase, message, data=None): self.after(0, lambda: self.step_label.configure(text=message.upper())) @@ -376,11 +463,13 @@ def on_security_event(self, phase, message, data=None): if self.client.manual_mode and phase not in ["finish", "found"]: self.after(0, lambda: self.verbose_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")) self.after(0, lambda: self.data_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew", columnspan=1)) - self.after(500, lambda: self.btn_next.pack(pady=20)) - else: + self.after(500, lambda: self.btn_next.pack(pady=10)) + self.after(500, lambda: self.btn_vuln.pack(pady=10)) + elif not self.client.manual_mode: self.after(0, lambda: self.verbose_frame.grid_forget()) self.after(0, lambda: self.data_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew", columnspan=2)) self.after(0, lambda: self.btn_next.pack_forget()) + self.after(0, lambda: self.btn_vuln.pack_forget()) context = { "scan": "Zero-Trust Handshake: Treating the remote server as untrusted until proven otherwise. Requesting signed metadata.", @@ -396,17 +485,35 @@ def on_security_event(self, phase, message, data=None): "breach": "CRITICAL ALERT: Security barrier tripped. Hub terminated process to protect the system environment." } - if phase in context: self.after(0, lambda p=phase: self.update_verbose_panel(context[p])) + # Handle technical data and backstage info + display_lines = [] + backstage_info = "" if data: - self.after(0, lambda: self.data_display.configure(text="\n".join([f"{k}: {v}" for k, v in data.items()]))) - else: - self.after(0, lambda: self.data_display.configure(text="")) + for k, v in data.items(): + if k == "TECHNICAL_BACKSTAGE": + backstage_info = v + else: + display_lines.append(f"{k}: {v}") + + # Update Right Box (Analysis) + new_verbose_text = context.get(phase, "") + if backstage_info: + new_verbose_text += f"\n\n{backstage_info}" + + if new_verbose_text: + self.after(0, lambda t=new_verbose_text: self.update_verbose_panel(t)) + + # Update Left Box (Telemetry) + self.after(0, lambda l="\n".join(display_lines): self.update_data_display(l)) # Visuals - if phase == "download_meta": [self.after(random.randint(0,400), lambda: self.animate_packet("{}", "#f1c40f")) for _ in range(8)] - if phase == "download_patch": [self.after(random.randint(0,800), lambda: self.animate_packet("01", "#3498db", 25)) for _ in range(20)] + if phase == "download_meta": [self.after(secrets.SystemRandom().randint(0,400), lambda: self.animate_packet("{}", "#f1c40f")) for _ in range(8)] + if phase == "download_patch": [self.after(secrets.SystemRandom().randint(0,800), lambda: self.animate_packet("01", "#3498db", 25)) for _ in range(20)] if phase == "breach": self.after(0, self.flash_red) + def update_data_display(self, text): + self.data_display.configure(text=text) + def update_verbose_panel(self, text): self.verbose_text.configure(state="normal") self.verbose_text.delete("0.0", "end") @@ -418,7 +525,7 @@ def animate_packet(self, char, color, speed=12): def move(): coords = self.canvas.coords(item) if coords and coords[0] < 800: - self.canvas.move(item, speed, random.randint(-3, 3)) + self.canvas.move(item, speed, secrets.SystemRandom().randint(-3, 3)) self.after(20, move) else: self.canvas.delete(item) move() @@ -444,9 +551,20 @@ def refresh_app_list(self): btn.pack(side="right", padx=30) self.app_widgets[app_id] = {"btn": btn, "pbar": p_bar, "vlab": v_label} + def set_all_buttons_state(self, state): + """Helper to enable/disable all SECURE SCAN buttons.""" + for widgets in self.app_widgets.values(): + widgets["btn"].configure(state=state) + def start_update(self, app_id): + if self.is_updating: + return + + self.is_updating = True + self.set_all_buttons_state("disabled") + w = self.app_widgets[app_id] - w["btn"].configure(state="disabled", text="GUARDING...") + w["btn"].configure(text="GUARDING...") w["pbar"].configure(mode="indeterminate"); w["pbar"].start() threading.Thread(target=lambda: self.update_logic(app_id), daemon=True).start() @@ -463,7 +581,11 @@ def update_logic(self, app_id): except Exception as e: messagebox.showerror("Error", str(e)) finally: self.after(0, lambda: self.btn_next.pack_forget()) - self.after(0, lambda: (w["btn"].configure(state="normal", text="SECURE SCAN"), w["pbar"].stop(), w["pbar"].configure(mode="determinate"), w["pbar"].set(0))) + self.after(0, lambda: self.btn_vuln.pack_forget()) + self.after(0, lambda: w["btn"].configure(text="SECURE SCAN")) + self.after(0, lambda: (w["pbar"].stop(), w["pbar"].configure(mode="determinate"), w["pbar"].set(0))) + self.after(0, lambda: self.set_all_buttons_state("normal")) + self.is_updating = False def set_status_text(self, text): self.step_label.configure(text=text.upper()) diff --git a/client/managed_apps/engine_monitor/app.exe b/client/managed_apps/engine_monitor/app.exe index 25217d1..71c991f 100644 Binary files a/client/managed_apps/engine_monitor/app.exe and b/client/managed_apps/engine_monitor/app.exe differ diff --git a/client/managed_apps/hello_world/app.exe b/client/managed_apps/hello_world/app.exe deleted file mode 100644 index 5385494..0000000 Binary files a/client/managed_apps/hello_world/app.exe and /dev/null differ diff --git a/client/managed_apps/security_scanner/app.exe b/client/managed_apps/security_scanner/app.exe index 84b1f11..7573918 100644 Binary files a/client/managed_apps/security_scanner/app.exe and b/client/managed_apps/security_scanner/app.exe differ diff --git a/server/static/engine_monitor/manifest.json b/server/static/engine_monitor/manifest.json index 05d665b..cdf9935 100644 --- a/server/static/engine_monitor/manifest.json +++ b/server/static/engine_monitor/manifest.json @@ -6,21 +6,21 @@ }, "metadata": { "latest_version": "1.0.5", - "release_date": "2026-05-09T21:06:45Z", - "valid_until": "2026-06-08T21:06:45Z" + "release_date": "2026-05-09T23:07:30Z", + "valid_until": "2026-06-08T23:07:30Z" }, "assets": { "full_installer": { "file_name": "monitor_v1.0.5_full.exe", "url": "http://localhost:8000/static/engine_monitor/monitor_v1.0.5_full.exe", - "sha256_hash": "6f2caeac1e8c34eacc1e75243e8fd0222f2925f9743df93141e1713f53047e18" + "sha256_hash": "16fa57f536b99cd5cf9197165168622e2badf11871a9fa27f1428d908678a9ba" }, "patches": [ { "from_version": "1.0.0", "file_name": "monitor_patch_v1.0.0_to_v1.0.5.bin", "url": "http://localhost:8000/static/engine_monitor/monitor_patch_v1.0.0_to_v1.0.5.bin", - "result_sha256_hash": "6f2caeac1e8c34eacc1e75243e8fd0222f2925f9743df93141e1713f53047e18" + "result_sha256_hash": "16fa57f536b99cd5cf9197165168622e2badf11871a9fa27f1428d908678a9ba" } ] } diff --git a/server/static/engine_monitor/manifest.sig b/server/static/engine_monitor/manifest.sig index 0fdc0ee..b5e9529 100644 --- a/server/static/engine_monitor/manifest.sig +++ b/server/static/engine_monitor/manifest.sig @@ -1 +1 @@ -ooDw4K+Pe/M/bHpSWEUvyync4fyFQxsmo2mwvSi8R57gvEgBU79qRkykY17rdGAVsZ0wM0HousrtYM7+F+fzDA== \ No newline at end of file +07YDzT4DfHuiFEuEyErhSlFVplDgJi49k6Lcjbhxb9oNuL83IBufuKheZ8yCFxl55zt4AyAUOHuYLD6izvGsAA== \ No newline at end of file diff --git a/server/static/engine_monitor/monitor_patch_v1.0.0_to_v1.0.5.bin b/server/static/engine_monitor/monitor_patch_v1.0.0_to_v1.0.5.bin index 1f566ad..5b927ee 100644 Binary files a/server/static/engine_monitor/monitor_patch_v1.0.0_to_v1.0.5.bin and b/server/static/engine_monitor/monitor_patch_v1.0.0_to_v1.0.5.bin differ diff --git a/server/static/engine_monitor/monitor_v1.0.5_full.exe b/server/static/engine_monitor/monitor_v1.0.5_full.exe index a6ea403..71c991f 100644 Binary files a/server/static/engine_monitor/monitor_v1.0.5_full.exe and b/server/static/engine_monitor/monitor_v1.0.5_full.exe differ diff --git a/server/static/hello_world/hello_patch_v1.0.0_to_v1.1.0.bin b/server/static/hello_world/hello_patch_v1.0.0_to_v1.1.0.bin index f3fad04..b166771 100644 Binary files a/server/static/hello_world/hello_patch_v1.0.0_to_v1.1.0.bin and b/server/static/hello_world/hello_patch_v1.0.0_to_v1.1.0.bin differ diff --git a/server/static/hello_world/hello_world_v1.1.0_full.exe b/server/static/hello_world/hello_world_v1.1.0_full.exe index 5385494..ca53598 100644 Binary files a/server/static/hello_world/hello_world_v1.1.0_full.exe and b/server/static/hello_world/hello_world_v1.1.0_full.exe differ diff --git a/server/static/hello_world/manifest.json b/server/static/hello_world/manifest.json index 2e58a51..c109bb5 100644 --- a/server/static/hello_world/manifest.json +++ b/server/static/hello_world/manifest.json @@ -6,21 +6,21 @@ }, "metadata": { "latest_version": "1.1.0", - "release_date": "2026-05-09T21:06:58Z", - "valid_until": "2026-06-08T21:06:58Z" + "release_date": "2026-05-09T23:07:42Z", + "valid_until": "2026-06-08T23:07:42Z" }, "assets": { "full_installer": { "file_name": "hello_world_v1.1.0_full.exe", "url": "http://localhost:8000/static/hello_world/hello_world_v1.1.0_full.exe", - "sha256_hash": "97644fe029792e9f6adff443dd956765d74acb3d234f15fbed40e34609562d39" + "sha256_hash": "ecfa4d91cb8ac5b00f2f8518a92d289d415fa8b9126b19e304fc9c69aea18e5f" }, "patches": [ { "from_version": "1.0.0", "file_name": "hello_patch_v1.0.0_to_v1.1.0.bin", "url": "http://localhost:8000/static/hello_world/hello_patch_v1.0.0_to_v1.1.0.bin", - "result_sha256_hash": "97644fe029792e9f6adff443dd956765d74acb3d234f15fbed40e34609562d39" + "result_sha256_hash": "ecfa4d91cb8ac5b00f2f8518a92d289d415fa8b9126b19e304fc9c69aea18e5f" } ] } diff --git a/server/static/hello_world/manifest.sig b/server/static/hello_world/manifest.sig index d136d37..df4ecb7 100644 --- a/server/static/hello_world/manifest.sig +++ b/server/static/hello_world/manifest.sig @@ -1 +1 @@ -pi+QYFpqVLRSNQi4Q2ji9BLLrnLlkF3x7i+i5IA+XiU6oPDk1FR1g8DrB/yYebBe0wskPZn/k4bi5r31eruNCg== \ No newline at end of file +Ohw04vPYSfNoZEBDMS5J0HsSSAgTXOyT9nxxwqqWthg7Mb4n4p//itxI+tayVCZe7g0sb7ZpTqK0NYa/CsV2Cw== \ No newline at end of file diff --git a/server/static/security_scanner/manifest.json b/server/static/security_scanner/manifest.json index d944013..980d862 100644 --- a/server/static/security_scanner/manifest.json +++ b/server/static/security_scanner/manifest.json @@ -6,21 +6,21 @@ }, "metadata": { "latest_version": "2.1.0", - "release_date": "2026-05-09T21:06:45Z", - "valid_until": "2026-06-08T21:06:45Z" + "release_date": "2026-05-09T23:07:30Z", + "valid_until": "2026-06-08T23:07:30Z" }, "assets": { "full_installer": { "file_name": "scanner_v2.1.0_full.exe", "url": "http://localhost:8000/static/security_scanner/scanner_v2.1.0_full.exe", - "sha256_hash": "2791072cc371c3388fce706cdcc1bdac14fa5cc85e367655f79aa68d38e9c141" + "sha256_hash": "9a462fbfd4ebbdc3657b63f3711cb00bec31d735cd100dcd5471ba45043811bc" }, "patches": [ { "from_version": "2.0.0", "file_name": "scanner_patch_v2.0.0_to_v2.1.0.bin", "url": "http://localhost:8000/static/security_scanner/scanner_patch_v2.0.0_to_v2.1.0.bin", - "result_sha256_hash": "2791072cc371c3388fce706cdcc1bdac14fa5cc85e367655f79aa68d38e9c141" + "result_sha256_hash": "9a462fbfd4ebbdc3657b63f3711cb00bec31d735cd100dcd5471ba45043811bc" } ] } diff --git a/server/static/security_scanner/manifest.sig b/server/static/security_scanner/manifest.sig index d9bd54f..85d5475 100644 --- a/server/static/security_scanner/manifest.sig +++ b/server/static/security_scanner/manifest.sig @@ -1 +1 @@ -vPBFNSQtTGLlf6frAHr3WIJ54jB5JOxDKDfSHcr371sz7TxoAAFPTj7ORTk6ZBRAtnwsHti6WYS2phiCQwLQDg== \ No newline at end of file +8gTadUqrWeOe8myIzqxGWCTQcy6XTuPUaVr+wzeHprksb2hw/lhJ/k+PZ5wU64IxF1vgbP5zABLPVkiTbTrZBw== \ No newline at end of file diff --git a/server/static/security_scanner/scanner_patch_v2.0.0_to_v2.1.0.bin b/server/static/security_scanner/scanner_patch_v2.0.0_to_v2.1.0.bin index d9278f6..3d0b3b8 100644 Binary files a/server/static/security_scanner/scanner_patch_v2.0.0_to_v2.1.0.bin and b/server/static/security_scanner/scanner_patch_v2.0.0_to_v2.1.0.bin differ diff --git a/server/static/security_scanner/scanner_v2.1.0_full.exe b/server/static/security_scanner/scanner_v2.1.0_full.exe index e5708de..7573918 100644 Binary files a/server/static/security_scanner/scanner_v2.1.0_full.exe and b/server/static/security_scanner/scanner_v2.1.0_full.exe differ