├── .github └── workflows │ └── build-package.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── py_sip_xnu.py └── setup.py /.github/workflows/build-package.yml: -------------------------------------------------------------------------------- 1 | name: CI - Build Python Package 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | release: 7 | types: [published] 8 | 9 | jobs: 10 | build: 11 | name: Build Python Package 12 | runs-on: macos-latest 13 | env: 14 | branch: ${{ github.ref }} 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set Python 3.9 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: 3.9 22 | 23 | - name: Set up Python 3 libraries 24 | run: pip3 install setuptools wheel twine pylint build 25 | 26 | - name: Set Python 2.7 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: 2.7 30 | 31 | - name: Set up Python 2 libraries 32 | run: pip2 install setuptools wheel build 33 | 34 | - name: Validate Library (py3) 35 | run: python3 py_sip_xnu.py 36 | 37 | - name: Validate Library (py2) 38 | run: python2 py_sip_xnu.py 39 | 40 | - name: Validate setup.py 41 | run: python3 setup.py check -m -s 42 | 43 | - name: Build Package (py3) 44 | run: python3 -m build --wheel 45 | 46 | - name: Test Package (py3) 47 | run: python3 setup.py test 48 | 49 | - name: Build Package (py2) 50 | run: python2 -m build --wheel 51 | 52 | - name: Test Package (py2) 53 | run: python2 setup.py test 54 | 55 | - name: Validate twine 56 | run: twine check dist/* 57 | 58 | - name: Validate PEP8 59 | run: pylint py_sip_xnu.py 60 | 61 | - name: Upload Wheel Package 62 | uses: actions/upload-artifact@v2 63 | with: 64 | name: py_sip_xnu.whl 65 | path: dist/*.whl 66 | 67 | - name: Upload Package to Release 68 | if: github.event_name == 'release' 69 | uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d 70 | with: 71 | repo_token: ${{ secrets.GITHUB_TOKEN }} 72 | file: dist/* 73 | tag: ${{ github.ref }} 74 | file_glob: true 75 | 76 | - name: Publish PyPI Package 77 | if: github.event_name == 'release' 78 | env: 79 | TWINE_USERNAME: __token__ 80 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 81 | run: twine upload dist/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /py_sip_xnu.egg-info 3 | /dist 4 | __pycache__ 5 | build 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # py_sip_xnu changelog 2 | 3 | ## 1.0.4 4 | - Add Python 2 artifacts 5 | 6 | ## 1.0.3 7 | - Fix CI artifacts 8 | 9 | ## 1.0.2 10 | - Initial release -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Mykola Grymalyuk 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py_sip_xnu 2 | 3 | Python module for querying SIP status on XNU-based systems (primarily macOS) through exposed kernel APIs. 4 | No reliance on `csrutil` or NVRAM properties, allowing for more accurate and reliable results. Supporting both Intel and Apple Silicon systems. 5 | 6 | Library returns a SIP object with the following properties: 7 | ``` 8 | value - int - raw value of SIP configuration 9 | breakdown - object - holds each SIP key and its value 10 | can_edit_root - bool - whether SIP allows editing of protected files 11 | can_write_nvram - bool - whether SIP allows writing to NVRAM 12 | can_load_arbitrary_kexts - bool - whether SIP allows loading of arbitrary kexts 13 | ``` 14 | 15 | If module accessed under Yosemite or earlier, `sip_xnu` will treat SIP as disabled. 16 | 17 | Project currently synced against macOS 13.0 ([XNU 8792.41.9](https://github.com/apple-oss-distributions/xnu/tree/xnu-8792.41.9)). Based off of [pudquick's concept](https://gist.github.com/pudquick/8b320be960e1654b908b10346272326b). 18 | 19 | Python validated against 2.7 and 3.9. 20 | 21 | ## Background 22 | 23 | [System Integrity Protection](https://support.apple.com/en-ca/HT204899), generally abbreviated as SIP, is a security feature introduced in OS X El Capitan. The primary purpose of this setting was to control access to sensitive operations such as kernel extension loading, protected file write, task tracking, etc. SIP is part of the XNU kernel, and is a cumulation of several kernel flags into the CSR bitmask seen as SIP configuration. 24 | 25 | The primary benefit of this library over manually invoking either `csrutil` or reading `nvram csr-active-config` is that we check with the kernel directly, and verify what macOS itself is using for SIP configuration. Contrast this with `nvram`, boot.efi and XNU can reject SIP bits such as 0x10 (AppleInternal) during runtime without changing the exposed NVRAM value. 26 | 27 | With `csrutil`, this tool obfuscates much of SIP into a simple on/off state, when in reality SIP is a complex bitmask. Many developers will simply check the output of `csrutil status` and assume SIP is either enabled or disabled, without properly probing specific bits for what the application may need. Using `sip_xnu` allows for better probing and allows users to lower less of SIP for overall better system security. 28 | 29 | * More information can be found here: [System Integrity Protection: The misunderstood setting](https://khronokernel.github.io/macos/2022/12/09/SIP.html) 30 | 31 | Source for SIP configuration can be found in Apple's [csr.h](https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.41.9/bsd/sys/csr.h), and parsing logic from [csr.c](https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.41.9/libsyscall/wrappers/csr.c). 32 | 33 | 34 | ## Installation 35 | 36 | pip-based: 37 | ```sh 38 | pip3 install py_sip_xnu 39 | ``` 40 | 41 | Manual: 42 | ```sh 43 | python3 setup.py install 44 | ``` 45 | 46 | ## Usage 47 | 48 | Invocation: 49 | ```python 50 | import py_sip_xnu 51 | 52 | sip_config = py_sip_xnu.SipXnu().get_sip_status() 53 | 54 | ''' 55 | sip_config = { 56 | 'value': 0, 57 | 'breakdown': { 58 | 'csr_allow_untrusted_kexts': False, 59 | 'csr_allow_unrestricted_fs': False, 60 | 'csr_allow_task_for_pid': False, 61 | 'csr_allow_kernel_debugger': False, 62 | 'csr_allow_apple_internal': False, 63 | 'csr_allow_unrestricted_dtrace': False, 64 | 'csr_allow_unrestricted_nvram': False, 65 | 'csr_allow_device_configuration': False, 66 | 'csr_allow_any_recovery_os': False, 67 | 'csr_allow_unapproved_kexts': False, 68 | 'csr_allow_executable_policy_override': False, 69 | 'csr_allow_unauthenticated_root': False 70 | }, 71 | 'can_edit_root': False, 72 | 'can_write_nvram': False, 73 | 'can_load_arbitrary_kexts': False 74 | } 75 | ''' 76 | ``` 77 | 78 | ## License 79 | 80 | BSD 3-Clause License 81 | 82 | Copyright (c) 2022, Mykola Grymalyuk -------------------------------------------------------------------------------- /py_sip_xnu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # pylint: disable=consider-using-f-string 3 | 4 | ''' 5 | This module is used to detect XNU's current SIP status using direct 6 | kernel calls. This ensures that even if boot.efi strips bits or NVRAM 7 | is reset, current active SIP status is still detected correctly. 8 | 9 | SIP, or System Integrity Protection, is a security bitmask in XNU 10 | used to determine OS security features. ex. Filesystem protections, kext loading 11 | 12 | Source originally written by @pudquick: 13 | https://gist.github.com/pudquick/8b320be960e1654b908b10346272326b 14 | 15 | Adapted by @khronokernel into this module. 16 | ''' 17 | 18 | from ctypes import CDLL, c_uint, byref 19 | import platform 20 | 21 | __version__ = "1.0.4" 22 | 23 | SIP_XNU_LIBRARY_NAME = "sip_xnu" 24 | 25 | 26 | class SipXnu: # pylint: disable=too-many-instance-attributes, too-few-public-methods 27 | ''' 28 | This class is used to detect XNU's current SIP status 29 | ''' 30 | 31 | class _XnuOsVersion(): # pylint: disable=too-few-public-methods 32 | OS_CHEETAH = 4 33 | OS_PUMA = 5 34 | OS_JAGUAR = 6 35 | OS_PANTHER = 7 36 | OS_TIGER = 8 37 | OS_LEOPARD = 9 38 | OS_SNOW_LEOPARD = 10 39 | OS_LION = 11 40 | OS_MOUNTAIN_LION = 12 41 | OS_MAVERICKS = 13 42 | OS_YOSEMITE = 14 43 | OS_EL_CAPITAN = 15 44 | OS_SIERRA = 16 45 | OS_HIGH_SIERRA = 17 46 | OS_MOJAVE = 18 47 | OS_CATALINA = 19 48 | OS_BIG_SUR = 20 49 | OS_MONTEREY = 21 50 | OS_VENTURA = 22 51 | 52 | class _XnuSipBitmask(): # pylint: disable=too-few-public-methods 53 | CSR_ALLOW_UNTRUSTED_KEXTS = 0x1 54 | CSR_ALLOW_UNRESTRICTED_FS = 0x2 55 | CSR_ALLOW_TASK_FOR_PID = 0x4 56 | CSR_ALLOW_KERNEL_DEBUGGER = 0x8 57 | CSR_ALLOW_APPLE_INTERNAL = 0x10 58 | CSR_ALLOW_UNRESTRICTED_DTRACE = 0x20 59 | CSR_ALLOW_UNRESTRICTED_NVRAM = 0x40 60 | CSR_ALLOW_DEVICE_CONFIGURATION = 0x80 61 | CSR_ALLOW_ANY_RECOVERY_OS = 0x100 62 | CSR_ALLOW_UNAPPROVED_KEXTS = 0x200 63 | CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE = 0x400 64 | CSR_ALLOW_UNAUTHENTICATED_ROOT = 0x800 65 | 66 | class _XnuSipStatus(): # pylint: disable=too-many-instance-attributes # pylint: disable=too-few-public-methods 67 | def __init__( 68 | self, 69 | csr_dict): 70 | self.csr_allow_untrusted_kexts = csr_dict["CSR_ALLOW_UNTRUSTED_KEXTS"] 71 | self.csr_allow_unrestricted_fs = csr_dict["CSR_ALLOW_UNRESTRICTED_FS"] 72 | self.csr_allow_task_for_pid = csr_dict["CSR_ALLOW_TASK_FOR_PID"] 73 | self.csr_allow_kernel_debugger = csr_dict["CSR_ALLOW_KERNEL_DEBUGGER"] 74 | self.csr_allow_apple_internal = csr_dict["CSR_ALLOW_APPLE_INTERNAL"] 75 | self.csr_allow_unrestricted_dtrace = csr_dict["CSR_ALLOW_UNRESTRICTED_DTRACE"] 76 | self.csr_allow_unrestricted_nvram = csr_dict["CSR_ALLOW_UNRESTRICTED_NVRAM"] 77 | self.csr_allow_device_configuration = csr_dict["CSR_ALLOW_DEVICE_CONFIGURATION"] 78 | self.csr_allow_any_recovery_os = csr_dict["CSR_ALLOW_ANY_RECOVERY_OS"] 79 | self.csr_allow_unapproved_kexts = csr_dict["CSR_ALLOW_UNAPPROVED_KEXTS"] 80 | self.csr_allow_executable_policy_override = csr_dict[ 81 | "CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE"] 82 | self.csr_allow_unauthenticated_root = csr_dict["CSR_ALLOW_UNAUTHENTICATED_ROOT"] 83 | 84 | class _SipStatus: # pylint: disable=too-many-instance-attributes # pylint: disable=too-few-public-methods 85 | def __init__( # pylint: disable=too-many-arguments 86 | self, 87 | value, 88 | breakdown, 89 | can_edit_root, 90 | can_write_nvram, 91 | can_load_arbitrary_kexts): 92 | self.value = value 93 | self.breakdown = breakdown 94 | self.can_edit_root = can_edit_root 95 | self.can_write_nvram = can_write_nvram 96 | self.can_load_arbitrary_kexts = can_load_arbitrary_kexts 97 | 98 | def __init__(self, debug=False): 99 | self.xnu_major = 0 100 | self.xnu_minor = 0 101 | self.xnu_patch = 0 102 | self.sip_status = 0 103 | 104 | self.debug = debug 105 | 106 | self.lib_system_path = "/usr/lib/libSystem.dylib" 107 | 108 | self.sip_dict = { 109 | "CSR_ALLOW_UNTRUSTED_KEXTS": 0, 110 | "CSR_ALLOW_UNRESTRICTED_FS": 0, 111 | "CSR_ALLOW_TASK_FOR_PID": 0, 112 | "CSR_ALLOW_KERNEL_DEBUGGER": 0, 113 | "CSR_ALLOW_APPLE_INTERNAL": 0, 114 | "CSR_ALLOW_UNRESTRICTED_DTRACE": 0, 115 | "CSR_ALLOW_UNRESTRICTED_NVRAM": 0, 116 | "CSR_ALLOW_DEVICE_CONFIGURATION": 0, 117 | "CSR_ALLOW_ANY_RECOVERY_OS": 0, 118 | "CSR_ALLOW_UNAPPROVED_KEXTS": 0, 119 | "CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE": 0, 120 | "CSR_ALLOW_UNAUTHENTICATED_ROOT": 0 121 | } 122 | 123 | self.__is_darwin() 124 | self.__detect_xnu_version() 125 | 126 | self.sip_status = self.__detect_sip_status() 127 | 128 | self.__update_sip_dict() 129 | 130 | self.sip_object = self._SipStatus( 131 | value=self.sip_status, 132 | breakdown=self._XnuSipStatus(self.sip_dict), 133 | can_edit_root=self.__sip_can_edit_root(), 134 | can_write_nvram=self.__sip_can_write_nvram(), 135 | can_load_arbitrary_kexts=self.__sip_can_load_arbitrary_kexts() 136 | ) 137 | 138 | def get_sip_status(self): 139 | ''' 140 | Returns the current SIP status 141 | 142 | Returns: 143 | dict: Current SIP status 144 | ''' 145 | 146 | self.__debug_printing("Returning SIP status:") 147 | self.__debug_printing(" Value: %s" % self.sip_object.value) 148 | self.__debug_printing(" Breakdown:") 149 | for key, value in self.sip_object.breakdown.__dict__.items(): 150 | self.__debug_printing(" %s: %s" % (key, value)) 151 | self.__debug_printing( 152 | " Can edit root: %s" % 153 | self.sip_object.can_edit_root) 154 | self.__debug_printing( 155 | " Can write NVRAM: %s" % 156 | self.sip_object.can_write_nvram) 157 | self.__debug_printing( 158 | " Can load arbitrary kexts: %s" % 159 | self.sip_object.can_load_arbitrary_kexts) 160 | 161 | return self.sip_object 162 | 163 | def __debug_printing(self, message): 164 | ''' 165 | Prints the message if the debug flag is set 166 | 167 | Args: 168 | message (str): Message to be printed 169 | 170 | Format: 171 | [SIP_XNU_LIBRARY_NAME][TIME] MESSAGE 172 | ''' 173 | 174 | if self.debug: 175 | print( 176 | "[%s] %s" % 177 | (SIP_XNU_LIBRARY_NAME, 178 | message)) 179 | 180 | def __debug_exception(self, message): 181 | raise Exception( 182 | "[%s] %s" % 183 | (SIP_XNU_LIBRARY_NAME, message)) 184 | 185 | def __is_darwin(self): 186 | ''' 187 | Checks if the current system is Darwin 188 | ''' 189 | 190 | if platform.system() != "Darwin": 191 | self.__debug_exception("Not Darwin") 192 | 193 | def __detect_xnu_version(self): 194 | ''' 195 | Detects the XNU version of the current system 196 | ''' 197 | 198 | xnu_version = platform.release() 199 | xnu_version = xnu_version.split(".") 200 | 201 | self.xnu_major = int(xnu_version[0]) 202 | self.xnu_minor = int(xnu_version[1]) 203 | self.xnu_patch = int(xnu_version[2]) 204 | 205 | self.__debug_printing("XNU version: %d.%d.%d" % 206 | (self.xnu_major, self.xnu_minor, self.xnu_patch)) 207 | 208 | def __detect_sip_status(self): 209 | ''' 210 | Detects the SIP status of the current system 211 | 212 | Returns: 213 | int: csr_active_config value 214 | 215 | Notes: 216 | If OS detected is older than 10.11, returns max int value 217 | ''' 218 | 219 | if self.xnu_major < self._XnuOsVersion.OS_EL_CAPITAN: 220 | # Assume unrestricted 221 | return 65535 222 | 223 | libsys = CDLL(self.lib_system_path) 224 | result = c_uint(0) 225 | error = libsys.csr_get_active_config(byref(result)) 226 | 227 | if error != 0: 228 | self.__debug_exception( 229 | "Error while detecting SIP status: %d" % 230 | error) 231 | 232 | self.__debug_printing("csr_active_config: %d" % result.value) 233 | 234 | return result.value 235 | 236 | def __sip_can_edit_root(self): 237 | ''' 238 | Checks if SIP allows root filesystem to be edited 239 | 240 | Returns: 241 | bool: True if SIP allows root filesystem to be edited 242 | ''' 243 | 244 | if self.sip_status & self._XnuSipBitmask.CSR_ALLOW_UNRESTRICTED_FS: 245 | if self.xnu_major < self._XnuOsVersion.OS_BIG_SUR: 246 | return True 247 | 248 | if self.sip_status & self._XnuSipBitmask.CSR_ALLOW_UNAUTHENTICATED_ROOT: 249 | return True 250 | 251 | return False 252 | 253 | def __sip_can_load_arbitrary_kexts(self): 254 | ''' 255 | Checks if SIP allows arbitrary kexts to be loaded 256 | 257 | Returns: 258 | bool: True if SIP allows arbitrary kexts to be loaded 259 | ''' 260 | 261 | if self.sip_status & self._XnuSipBitmask.CSR_ALLOW_UNTRUSTED_KEXTS: 262 | return True 263 | 264 | return False 265 | 266 | def __sip_can_write_nvram(self): 267 | ''' 268 | Checks if SIP allows NVRAM to be written 269 | 270 | Returns: 271 | bool: True if SIP allows NVRAM to be written 272 | ''' 273 | 274 | if self.sip_status & self._XnuSipBitmask.CSR_ALLOW_UNRESTRICTED_NVRAM: 275 | return True 276 | 277 | return False 278 | 279 | def __update_sip_dict(self): 280 | ''' 281 | Updates SIP_DICT with new SIP status 282 | ''' 283 | 284 | for key, value in self.sip_dict.items(): # pylint: disable=unused-variable 285 | for sip_key, sip_value in self._XnuSipBitmask.__dict__.items(): 286 | if sip_key == key: 287 | if self.sip_status & sip_value: 288 | self.sip_dict[key] = True 289 | else: 290 | self.sip_dict[key] = False 291 | 292 | 293 | if __name__ == "__main__": 294 | SipXnu(debug=True).get_sip_status() 295 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='py_sip_xnu', 5 | version='1.0.4', 6 | author='Mykola Grymalyuk', 7 | author_email='khronokernel@icloud.com', 8 | license='BSD 3-Clause License', 9 | description='Module for querying SIP status on XNU-based systems', 10 | long_description=open('README.md').read(), 11 | long_description_content_type='text/markdown', 12 | url='https://github.com/khronokernel/py_sip_xnu', 13 | python_requires='>=2.7', 14 | ) --------------------------------------------------------------------------------