From 3a2ac7a310cfe432540a72b3fcbb2d6a78ae3011 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 30 Sep 2022 06:44:34 -0600 Subject: [PATCH] kdk_handler: Add KDK downloader for Ventura --- .gitignore | 1 + gui/gui_main.py | 26 ++++- resources/kdk_handler.py | 177 +++++++++++++++++++++++++++++++++ resources/sys_patch.py | 12 ++- resources/sys_patch_detect.py | 32 ++---- resources/sys_patch_helpers.py | 25 ++--- 6 files changed, 232 insertions(+), 41 deletions(-) create mode 100644 resources/kdk_handler.py diff --git a/.gitignore b/.gitignore index 9870d5167..b53573509 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ __pycache__/ /payloads/InstallAssistant.pkg.integrityDataV1 /payloads.dmg /payloads/OpenCore-Legacy-Patcher-*.plist +/payloads/KDK.dmg diff --git a/gui/gui_main.py b/gui/gui_main.py index 9c22faec6..9d8e493a1 100644 --- a/gui/gui_main.py +++ b/gui/gui_main.py @@ -16,7 +16,7 @@ from pathlib import Path import binascii import hashlib -from resources import constants, defaults, build, install, installer, sys_patch_download, utilities, sys_patch_detect, sys_patch, run, generate_smbios, updates, integrity_verification, global_settings +from resources import constants, defaults, build, install, installer, sys_patch_download, utilities, sys_patch_detect, sys_patch, run, generate_smbios, updates, integrity_verification, global_settings, kdk_handler from data import model_array, os_data, smbios_data, sip_data from gui import menu_redirect, gui_help @@ -1156,6 +1156,8 @@ class wx_python_gui: self.pulse_alternative(self.progress_bar) wx.GetApp().Yield() + self.progress_bar.Hide() + # Download resources sys.stdout=menu_redirect.RedirectLabel(self.developer_note) download_result, link = sys_patch_download.grab_patcher_support_pkg(self.constants).download_files() @@ -1175,6 +1177,28 @@ class wx_python_gui: webbrowser.open(self.constants.repo_link_latest) self.main_menu() + if self.patches["Settings: Kernel Debug Kit missing"] is True: + # Download KDK (if needed) + self.subheader.SetLabel("Downloading Kernel Debug Kit") + self.subheader.Centre(wx.HORIZONTAL) + self.developer_note.SetLabel("Starting shortly") + + sys.stdout=menu_redirect.RedirectLabel(self.developer_note) + kdk_result, error_msg = kdk_handler.kernel_debug_kit_handler(self.constants).download_kdk(self.constants.detected_os_version, self.constants.detected_os_build) + sys.stdout=sys.__stdout__ + + if kdk_result is False: + # Create popup window to inform user of error + self.popup = wx.MessageDialog( + self.frame, + f"A problem occurred trying to download the Kernel Debug Kit:\n\n{error_msg}", + "Kernel Debug Kit", + wx.ICON_ERROR + ) + self.popup.ShowModal() + self.finished_auto_patch = True + self.main_menu() + self.reset_frame_modal() self.frame_modal.SetSize(-1, self.WINDOW_HEIGHT_MAIN) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py new file mode 100644 index 000000000..878dda5d1 --- /dev/null +++ b/resources/kdk_handler.py @@ -0,0 +1,177 @@ + +from multiprocessing.spawn import is_forking +from pathlib import Path +import requests, urllib +from resources import utilities + +SESSION = requests.Session() + +class kernel_debug_kit_handler: + + + def __init__(self, constants): + self.constants = constants + + + def get_closest_match(self, host_version: str, host_build: str): + # Get the closest match to the provided version + # KDKs are generally a few days late, so we'll rely on N-1 matching + + # Note: AppleDB is manually updated, so this is not a perfect solution + + OS_DATABASE_LINK = "https://api.appledb.dev/main.json" + + newest_build = { + "name": "", + "version": "", + "build": "", + "date": "", + } + + print(f"- Checking closest match for: {host_version} build {host_build}") + + results = SESSION.get(OS_DATABASE_LINK) + + if results.status_code != 200: + print(" - Could not fetch database") + return None + + results = results.json() + + # Iterate through, find build that is closest to the host version + # Use date to determine which is closest + if "ios" in results: + for variant in results["ios"]: + if variant["osStr"] == "macOS": + + name = variant["version"] + version = name.split(" ")[0] + build = variant["build"] + date = variant["released"] + + if version != host_version: + # Check if this is a secuirty update (ie. 13.0.1) + version_split = version.split(".") + host_version_split = host_version.split(".") + if len(version_split) >= 2 and len(host_version_split) >= 2: + if not (version_split[0] == host_version_split[0] and version_split[1] == host_version_split[1]): + continue + else: + continue + + if build == host_build: + # Skip, as we want the next closest match + continue + + # Check if this is the newest build + if newest_build["date"] == "": + newest_build = { + "name": name, + "version": version, + "build": build, + "date": date, + } + + else: + if date > newest_build["date"]: + newest_build = { + "name": name, + "version": version, + "build": build, + "date": date, + } + + if newest_build["date"] != "": + print(f"- Closest match: {newest_build['version']} build {newest_build['build']}") + return self.generate_kdk_link(newest_build["version"], newest_build["build"]), newest_build["build"] + + print(" - Could not find a match") + return None, "" + + + def generate_kdk_link(self, version: str, build: str): + # Note: cannot do lazy matching as we don't store old version/build numbers nor can we enumerate KDKs from the portal + URL_TEMPLATE = f"https://download.developer.apple.com/macOS/Kernel_Debug_Kit_{version}_build_{build}/Kernel_Debug_Kit_{version}_build_{build}.dmg" + + return URL_TEMPLATE + + + def verify_apple_developer_portal(self, link): + # Determine whether Apple Developer Portal is up + # and if the requested file is available + + # Returns following: + # 0: Portal is up and file is available + # 1: Portal is up but file is not available + # 2: Portal is down + + TOKEN_URL_BASE = "https://developerservices2.apple.com/services/download?path=" + remote_path = urllib.parse.urlparse(link).path + token_url = urllib.parse.urlunparse(urllib.parse.urlparse(TOKEN_URL_BASE)._replace(query=urllib.parse.urlencode({"path": remote_path}))) + + try: + response = SESSION.get(token_url, timeout=5) + except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError): + print(" - Could not contact Apple download servers") + return 2 + + try: + response.raise_for_status() + except requests.exceptions.HTTPError: + if response.status_code == 400 and "The path specified is invalid" in response.text: + print(" - File does not exist on Apple download servers") + return 1 + else: + print(" - Could not request download authorization from Apple download servers") + return 2 + return 0 + + + def download_kdk(self, version: str, build: str): + error_msg = "" + + if self.is_kdk_installed(build) is True: + print(" - KDK is already installed") + return True, error_msg + + # Note: cannot do lazy matching as we don't store old version/build numbers nor can we enumerate KDKs from the portal + URL_TEMPLATE = self.generate_kdk_link(version, build) + + print(f"- Downloading Apple KDK for macOS {version} build {build}") + result = self.verify_apple_developer_portal(URL_TEMPLATE) + if result == 2: + error_msg = "Could not contact Apple download servers" + return False, error_msg + elif result == 0: + print(" - Downloading KDK") + elif result == 1: + print(" - Could not find KDK, finding closest match") + URL_TEMPLATE, closest_build = self.get_closest_match(version, build) + + if self.is_kdk_installed(closest_build) is True: + return True, error_msg + if URL_TEMPLATE is None: + error_msg = "Could not find KDK for host, nor closest match" + return False, error_msg + result = self.verify_apple_developer_portal(URL_TEMPLATE) + if result == 2: + error_msg = "Could not contact Apple download servers" + return False, error_msg + elif result == 0: + print(" - Downloading KDK") + elif result == 1: + print(" - Could not find KDK") + error_msg = "Could not find KDK for host on Apple's servers, nor closest match" + return False, error_msg + + if utilities.download_apple_developer_portal(URL_TEMPLATE, self.constants.kdk_download_path): + return True, error_msg + error_msg = "Failed to download KDK" + return False, error_msg + + def is_kdk_installed(self, build): + for file in Path("/Library/Developer/KDKs").iterdir(): + if file.is_dir(): + if build in file.name: + return True + return False \ No newline at end of file diff --git a/resources/sys_patch.py b/resources/sys_patch.py index faf6f72c1..422910ada 100644 --- a/resources/sys_patch.py +++ b/resources/sys_patch.py @@ -37,7 +37,7 @@ import shutil import subprocess from pathlib import Path -from resources import constants, utilities, sys_patch_download, sys_patch_detect, sys_patch_auto, sys_patch_helpers +from resources import constants, utilities, sys_patch_download, sys_patch_detect, sys_patch_auto, sys_patch_helpers, kdk_handler from data import os_data @@ -117,7 +117,15 @@ class PatchSysVolume: # Assume KDK is already merged return - kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present() + kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=False) + if kdk_path is None: + if not self.constants.kdk_download_path.exists(): + kdk_result, error_msg = kdk_handler.kernel_debug_kit_handler(self.constants).download_kdk(self.constants.detected_os_version, self.constants.detected_os_build) + if kdk_result is False: + raise Exception(f"Unable to download KDK: {error_msg}") + sys_patch_helpers.sys_patch_helpers(self.constants).install_kdk() + + kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=True) if kdk_path is None: print("- Unable to find Kernel Debug Kit") raise Exception("Unable to find Kernel Debug Kit") diff --git a/resources/sys_patch_detect.py b/resources/sys_patch_detect.py index e2f9aedd9..8ff3619c5 100644 --- a/resources/sys_patch_detect.py +++ b/resources/sys_patch_detect.py @@ -129,9 +129,8 @@ class detect_root_patch: self.legacy_gcn = True self.supports_metal = True self.requires_root_kc = True - if self.constants.detected_os > os_data.os_data.ventura: - self.amfi_must_disable = True - self.amfi_shim_bins = True + self.amfi_must_disable = True + self.amfi_shim_bins = True elif gpu.arch == device_probe.Intel.Archs.Iron_Lake: if self.constants.detected_os > non_metal_os: self.iron_gpu = True @@ -149,26 +148,23 @@ class detect_root_patch: elif gpu.arch == device_probe.Intel.Archs.Ivy_Bridge: if self.constants.detected_os > os_data.os_data.big_sur: self.ivy_gpu = True - if self.constants.detected_os > os_data.os_data.ventura: + if self.constants.detected_os >= os_data.os_data.ventura: self.amfi_must_disable = True self.supports_metal = True elif gpu.arch == device_probe.Intel.Archs.Haswell: if self.constants.detected_os > os_data.os_data.monterey: self.haswell_gpu = True - if self.constants.detected_os > os_data.os_data.ventura: - self.amfi_must_disable = True + self.amfi_must_disable = True self.supports_metal = True elif gpu.arch == device_probe.Intel.Archs.Broadwell: if self.constants.detected_os > os_data.os_data.monterey: self.broadwell_gpu = True - if self.constants.detected_os > os_data.os_data.ventura: - self.amfi_must_disable = True + self.amfi_must_disable = True self.supports_metal = True elif gpu.arch == device_probe.Intel.Archs.Skylake: if self.constants.detected_os > os_data.os_data.monterey: self.skylake_gpu = True - if self.constants.detected_os > os_data.os_data.ventura: - self.amfi_must_disable = True + self.amfi_must_disable = True self.supports_metal = True if self.supports_metal is True: # Avoid patching Metal and non-Metal GPUs if both present, prioritize Metal GPU @@ -184,6 +180,9 @@ class detect_root_patch: if self.constants.detected_os <= os_data.os_data.monterey: # Always assume Root KC requirement on Monterey and older self.requires_root_kc = True + else: + if self.requires_root_kc is True: + self.missing_kdk = not self.check_kdk() def check_dgpu_status(self): @@ -334,6 +333,7 @@ class detect_root_patch: "Miscellaneous: Legacy Keyboard Backlight": self.legacy_keyboard_backlight, "Settings: Requires AMFI exemption": self.amfi_must_disable, "Settings: Supports Auxiliary Cache": not self.requires_root_kc, + "Settings: Kernel Debug Kit missing": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura.value else False, "Validation: Patching Possible": self.verify_patch_allowed(), f"Validation: SIP is enabled (Required: {self.check_sip()[2]} or higher)": self.sip_enabled, f"Validation: Currently Booted SIP: ({hex(utilities.csr_dump())})": self.sip_enabled, @@ -345,7 +345,6 @@ class detect_root_patch: "Validation: Force OpenGL property missing": self.missing_nv_web_opengl if self.nvidia_web is True else False, "Validation: Force compat property missing": self.missing_nv_compat if self.nvidia_web is True else False, "Validation: nvda_drv(_vrl) variable missing": self.missing_nv_web_nvram if self.nvidia_web is True else False, - f"Validation: Kernel Debug Kit missing (need {self.constants.detected_os_build})": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura else False, } return self.root_patch_dict @@ -367,11 +366,7 @@ class detect_root_patch: sip_value = sip_dict[1] self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched = utilities.patching_status(sip, self.constants.detected_os) - self.amfi_enabled = amfi_detect.amfi_configuration_detection().check_config(self.get_amfi_level_needed()) - - if self.constants.detected_os >= os_data.os_data.ventura: - if self.requires_root_kc is True: - self.missing_kdk = not self.check_kdk() + self.amfi_enabled = not amfi_detect.amfi_configuration_detection().check_config(self.get_amfi_level_needed()) if self.nvidia_web is True: self.missing_nv_web_nvram = not self.check_nv_web_nvram() @@ -422,15 +417,10 @@ class detect_root_patch: print("\nCannot patch! WhateverGreen.kext missing") print("Please ensure WhateverGreen.kext is installed") - if self.missing_kdk is True: - print("\nCannot patch! Kernel Debug Kit missing") - print(f"Please ensure the correct KDK is installed (required: {self.constants.detected_os_build})") - if any( [ # General patch checks self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched, - self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura else False, # non-Metal specific self.amfi_enabled if self.amfi_must_disable is True else False, diff --git a/resources/sys_patch_helpers.py b/resources/sys_patch_helpers.py index 3819e7858..ac89fc12f 100644 --- a/resources/sys_patch_helpers.py +++ b/resources/sys_patch_helpers.py @@ -70,24 +70,15 @@ class sys_patch_helpers: return True return False - def download_and_install_kdk(self, version: str, build: str): - # Note: cannot do lazy matching as we don't store old version/build numbers nor can we enumerate KDKs from the portal - URL_TEMPLATE = f"https://download.developer.apple.com/macOS/Kernel_Debug_Kit_{version}_build_{build}/Kernel_Debug_Kit_{version}_build_{build}.dmg" + def install_kdk(self): + print(f"- Installing downloaded KDK (this may take a while)") + with tempfile.TemporaryDirectory() as mount_point: + utilities.process_status(subprocess.run(["hdiutil", "attach", self.constants.kdk_download_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + # Install the KDK + utilities.process_status(utilities.elevated(["installer", "-pkg", f"{mount_point}/KernelDebugKit.pkg", "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Do not really care if this fails + print("- Successfully installed KDK") - print(f"- Downloading Apple KDK for macOS {version} build {build}") - if utilities.download_apple_developer_portal(URL_TEMPLATE, self.constants.kdk_download_path): - print("- Successfully downloaded KDK, installing") - # Set up a new temporary directory to mount the KDK to - with tempfile.TemporaryDirectory() as mount_point: - utilities.process_status(subprocess.run(["hdiutil", "attach", self.constants.kdk_download_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - # Install the KDK - utilities.process_status(utilities.elevated(["installer", "-pkg", f"{mount_point}/KDK.pkg", "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Do not really care if this fails - print("- Successfully installed KDK") - return True - else: - print("- Failed to download KDK") - return False def determine_kdk_present(self, match_closest=False): # Check if KDK is present