├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── SSLKillSwitch ├── Info.plist ├── SSLKillSwitch.h └── SSLKillSwitch.m ├── SSLKillSwitch2.plist ├── SSLKillSwitchTests ├── Info.plist ├── Makefile └── SSLKillSwitchTests.m └── layout ├── DEBIAN └── control └── Library └── PreferenceLoader └── Preferences ├── SSLKillSwitch.png └── SSLKillSwitch_prefs.plist /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: TheOS CI 2 | 3 | on: 4 | push: 5 | #tags: 6 | # - 'v*' 7 | 8 | #branches: [ master ] 9 | release: 10 | types: 11 | - created 12 | workflow_dispatch: 13 | inputs: 14 | debug_enabled: 15 | required: false 16 | default: '' 17 | 18 | jobs: 19 | build: 20 | runs-on: macos-latest 21 | 22 | steps: 23 | ################################################ 24 | ### Pre-init 25 | ################################################ 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 0 30 | submodules: recursive 31 | 32 | # - name: Update submodules 33 | # run: | 34 | # git submodule update --init --recursive 35 | # git submodule update --remote 36 | 37 | ################################################ 38 | ### Setup Theos environment 39 | ################################################ 40 | - name: Theos Setup (Check Cache) 41 | id: verify-cache 42 | run: | 43 | #echo "::set-output name=heads::`git ls-remote https://github.com/theos/theos | head -n 1 | cut -f 1`-`git ls-remote https://github.com/xybp888/iOS-SDKs | head -n 1 | cut -f 1`" 44 | echo "::set-output name=heads::`git ls-remote https://github.com/roothide/theos | head -n 1 | cut -f 1`-`git ls-remote https://github.com/xybp888/iOS-SDKs | head -n 1 | cut -f 1`" 45 | 46 | - name: Theos Setup (Use Cache) 47 | id: cache 48 | uses: actions/cache@v2 49 | with: 50 | path: ${{ github.workspace }}/theos 51 | key: ${{ runner.os }}-${{ steps.verify-cache.outputs.heads }} 52 | 53 | - name: Theos Setup (Setup) 54 | uses: NyaMisty/theos-action@master 55 | with: 56 | theos-src: https://github.com/roothide/theos 57 | #theos-sdks: https://github.com/xybp888/iOS-SDKs 58 | 59 | # Enable tmate debugging of manually-triggered workflows if the input option was provided 60 | - name: Setup tmate session 61 | uses: mxschmitt/action-tmate@v3 62 | if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} 63 | 64 | - name: Run Tests (Fishhook only) 65 | run: | 66 | set -e 67 | set -x 68 | make FISHHOOK=1 ARCHS=x86_64 TARGET=macosx:clang:latest 69 | (cd SSLKillSwitchTests && make ARCHS=x86_64 TARGET=macosx:clang:latest) 70 | DYLD_INSERT_LIBRARIES=$PWD/.theos/obj/macosx/debug/SSLKillSwitch2.dylib ./SSLKillSwitchTests/.theos/obj/macosx/debug/SSLKillSwitchTest 71 | set +x 72 | echo "Test Successful!" 73 | - name: Cleanup 74 | run: | 75 | make clean 76 | rm -f packages/* 77 | 78 | - name: Build Debug package - Rootful Substrate 79 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 80 | run: | 81 | make clean 82 | make package 83 | - name: Build Debug package - Rootless Substrate 84 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 85 | run: | 86 | make clean 87 | make package ROOTLESS=1 88 | - name: Build Debug package - Roothide Substrate 89 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 90 | run: | 91 | make clean 92 | make package ROOTHIDE=1 93 | - name: Build Debug package - Fishhook 94 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 95 | run: | 96 | make clean 97 | make FISHHOOK=1 98 | # We have to split into two call because actions/upload-artifact will use common ancestor if multi path given 99 | - name: Publish Debug artifact - Substrate 100 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 101 | uses: actions/upload-artifact@v2 102 | with: 103 | name: sslkillswitch3-debug 104 | path: | 105 | ${{ github.workspace }}/packages/*.deb 106 | - name: Publish Debug artifact - Fishhook 107 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 108 | uses: actions/upload-artifact@v2 109 | with: 110 | name: sslkillswitch3-debug 111 | path: | 112 | ${{ github.workspace }}/.theos/obj/debug/*.dylib 113 | 114 | - name: Get tag 115 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 116 | id: tag 117 | uses: dawidd6/action-get-tag@v1 118 | 119 | - name: Build Release package - Rootful Substrate 120 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 121 | run: | 122 | make clean 123 | TAGNAME=${{ steps.tag.outputs.tag }} 124 | make package FINALPACKAGE=1 PACKAGE_VERSION=${TAGNAME#v}+rootful 125 | - name: Build Release package - Rootless Substrate 126 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 127 | run: | 128 | make clean 129 | TAGNAME=${{ steps.tag.outputs.tag }} 130 | make package ROOTLESS=1 FINALPACKAGE=1 PACKAGE_VERSION=${TAGNAME#v}+rootless 131 | - name: Build Release package - Rootless Substrate 132 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 133 | run: | 134 | make clean 135 | TAGNAME=${{ steps.tag.outputs.tag }} 136 | make package ROOTHIDE=1 FINALPACKAGE=1 PACKAGE_VERSION=${TAGNAME#v}+roothide 137 | - name: Build Release package - Fishhook 138 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 139 | run: | 140 | make clean 141 | TAGNAME=${{ steps.tag.outputs.tag }} 142 | make FISHHOOK=1 FINALPACKAGE=1 143 | 144 | - name: Release 145 | uses: softprops/action-gh-release@v1 146 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 147 | with: 148 | files: | 149 | ${{ github.workspace }}/packages/*.deb 150 | ${{ github.workspace }}/.theos/obj/*.dylib 151 | env: 152 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 153 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Appledoc 29 | html/ 30 | 31 | # Facebook Infer 32 | infer-out/ 33 | 34 | # Theos builds 35 | /theos 36 | /obj 37 | /_ 38 | *.deb 39 | .theos 40 | 41 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SSLKillSwitch/fishhook"] 2 | path = SSLKillSwitch/fishhook 3 | url = https://github.com/jevinskie/fishhook 4 | branch = jev/arm64e 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license: http://www.opensource.org/licenses/mit-license.php 2 | 3 | Copyright 2015 Alban Diquet and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 9 | to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCHS := arm64 arm64e 2 | 3 | TWEAK_NAME = SSLKillSwitch2 4 | SSLKillSwitch2_FILES = SSLKillSwitch/SSLKillSwitch.m 5 | SSLKillSwitch2_CFLAGS = -fobjc-arc 6 | SSLKillSwitch2_CFLAGS += -ISSLKillSwitch/fishhook 7 | 8 | SSLKillSwitch2_FRAMEWORKS = Security 9 | 10 | ifndef FISHHOOK 11 | 12 | ifdef ROOTLESS 13 | $(info Build as a ROOTLESS Substrate Tweak) 14 | THEOS_PACKAGE_SCHEME=rootless 15 | PACKAGE_BUILDNAME := rootless 16 | else ifdef ROOTHIDE 17 | $(info Build as a ROOTHIDE Substrate Tweak) 18 | # THEOS_PACKAGE_ARCH := iphoneos-arm64e # must set afterwards if using original theos 19 | THEOS_PACKAGE_SCHEME=roothide 20 | PACKAGE_BUILDNAME := roothide 21 | else # ROOTLESS / ROOTHIDE 22 | $(info Build as a ROOTFUL Substrate Tweak) 23 | PACKAGE_BUILDNAME := rootful 24 | endif # ROOTLESS / ROOTHIDE 25 | 26 | ifneq ($(findstring DEBUG,$(THEOS_SCHEMA)),) 27 | PACKAGE_BUILDNAME := $(PACKAGE_BUILDNAME)debug 28 | endif 29 | 30 | SSLKillSwitch2_CFLAGS += -DSUBSTRATE_BUILD 31 | 32 | else # FISHHOOK 33 | 34 | $(info Build as a FishHook Tweak) 35 | SSLKillSwitch2_FILES += SSLKillSwitch/fishhook/fishhook.c 36 | # avoid linking Substrate 37 | SSLKillSwitch2_LOGOS_DEFAULT_GENERATOR = internal 38 | 39 | endif # FISHHOOK 40 | 41 | include $(THEOS)/makefiles/common.mk 42 | 43 | include $(THEOS_MAKE_PATH)/tweak.mk 44 | include $(THEOS_MAKE_PATH)/aggregate.mk 45 | 46 | 47 | after-install:: 48 | # Respring the device 49 | install.exec "killall -9 SpringBoard" 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSL Kill Switch 3 2 | 3 | Next Generation of iOS Tweak SSLKillSwitch (https://github.com/nabla-c0d3/ssl-kill-switch2) with much more functionality! 4 | 5 | ## What's New? 6 | 7 | - [FIXED] Fishhook Support (iOS 15+, ARM64/ARM64e), so that you can hook in non-jailbreak era 8 | - [FIXED] Rootless Support (iOS 15+, ARM64/ARM64e), happy rootless :) 9 | 10 | - [ADDED] Hooks SecIsInternalRelease, so AppleServerAuthenticationNoPinning can be set 11 | - see https://vtky.github.io/2021/01/05/apple-globalpreferences for more 12 | - [ADDED] Hooks to Disable Security SecTrustEvaluate series function 13 | - [ADDED] Hooks to Disable [NSURLSessionDelegate URLSession:didReceiveChallenge:completionHandler:] 14 | - [ADDED] Various bypass technique from [sensepost/objection](https://github.com/sensepost/objection) 15 | - AFNetworking, TrustKit, Cordova SSLCertificateChecker-PhoneGap-Plugin 16 | 17 | ## Usage 18 | 19 | 1. Grab a build from https://github.com/NyaMisty/ssl-kill-switch3/releases, or build it yourself 20 | - Note: nightly build also available in GitHub CI 21 | 2. (For New Rootless Jailbreak, like Dopamine) Download `+rootless` deb, and open it in Sileo (or install the deb using `dpkg -i`), then check Settings after respring 22 | 2. (For Old Rootful Jailbreak, like checkra1n) Download `+rootful` deb, and open it in Sileo (or install the deb using `dpkg -i`), then check Settings after respring 23 | 3. (If Not Jailbroken) Use Signing tools like *Sideloadly* or *ESign* to inject the **dylib** into IPA and install it 24 | 25 | ## Building 26 | 27 | Note: **Theos** Needed! **MacOS** is also needed if you are building for rootless 28 | 29 | - Substrate Version (jailbreak version): 30 | - Rootful: 31 | ``` 32 | make package 33 | ls packages 34 | ``` 35 | - Rootless: 36 | ``` 37 | make package ROOTLESS=1 38 | ls packages 39 | ``` 40 | - Fishhook Version (non-jailbreak version) 41 | - Debug Version: 42 | ``` 43 | make FISHHOOK=1 44 | ls .theos/obj/debug/SSLKillSwitch2.dylib 45 | ``` 46 | - Release Version: 47 | ``` 48 | make FISHHOOK=1 FINALPACKAGE=1 49 | ls .theos/obj/SSLKillSwitch2.dylib 50 | ``` 51 | -------------------------------------------------------------------------------- /SSLKillSwitch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 nablac0d3. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SSLKillSwitch/SSLKillSwitch.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSLKillSwitch.h 3 | // SSLKillSwitch 4 | // 5 | // Created by Alban Diquet on 7/10/15. 6 | // Copyright (c) 2015 Alban Diquet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SSLKillSwitch. 12 | FOUNDATION_EXPORT double SSLKillSwitchVersionNumber; 13 | 14 | //! Project version string for SSLKillSwitch. 15 | FOUNDATION_EXPORT const unsigned char SSLKillSwitchVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SSLKillSwitch/SSLKillSwitch.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSLKillSwitch.m 3 | // SSLKillSwitch 4 | // 5 | // Created by Alban Diquet on 7/10/15. 6 | // Copyright (c) 2015 Alban Diquet. All rights reserved. 7 | // 8 | 9 | // avoid deprecation warnings like kSSLSessionOptionBreakOnServerAuth 10 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 11 | #pragma clang diagnostic ignored "-Wunused-function" 12 | #pragma clang diagnostic ignored "-Wunused-variable" 13 | 14 | #import 15 | #import 16 | 17 | #import 18 | #import 19 | 20 | #ifndef THEOS_PACKAGE_SCHEME_ROOTHIDE 21 | #import 22 | #else 23 | #import 24 | #define ROOT_PATH_NS jbroot 25 | #endif 26 | 27 | #if SUBSTRATE_BUILD 28 | #import "substrate.h" 29 | 30 | #define PREFERENCE_FILE ROOT_PATH_NS(@"/var/mobile/Library/Preferences/com.nablac0d3.SSLKillSwitchSettings.plist") 31 | #define PREFERENCE_KEY @"shouldDisableCertificateValidation" 32 | 33 | #else // SUBSTRATE_BUILD 34 | 35 | #import "fishhook.h" 36 | 37 | #endif // SUBSTRATE_BUILD 38 | 39 | 40 | #pragma mark Utility Functions 41 | 42 | #define SSKLOGLEVEL_VERBOSE 10 43 | #define SSKLOGLEVEL_INFO 6 44 | #define SSKLOGLEVEL_WARNING 4 45 | #define SSKLOGLEVEL_DISABLED 1 46 | 47 | int g_logLevel = SSKLOGLEVEL_VERBOSE; // for situations like Non-Substrate 48 | 49 | #ifndef USE_NSLOG 50 | 51 | // we use os_log instead, because we actually don't want to pollute the stderr 52 | #import 53 | #define _SSKLog(format, ...) os_log(OS_LOG_DEFAULT, "=== SSL Kill Switch 3: " format, ##__VA_ARGS__) 54 | // static void __SSKLog(NSString *format, ...) 55 | // { 56 | // va_list args; 57 | // va_start(args, format); 58 | // NSString *newFormat = [[NSString alloc] initWithFormat:@"=== SSL Kill Switch 3: %{public}@", format]; 59 | // NSString *formatted = [[NSString alloc] initWithFormat:newFormat arguments:args]; 60 | // os_log(OS_LOG_DEFAULT, "%{public}@", formatted); 61 | // va_end(args); 62 | // } 63 | // #define _SSKLog(format, ...) __SSKLog(@format, ##__VA_ARGS__) 64 | 65 | #else // USE_NSLOG 66 | 67 | static void __SSKLog(NSString *format, ...) 68 | { 69 | NSString *newFormat = [[NSString alloc] initWithFormat:@"=== SSL Kill Switch 3: %{public}@", format]; 70 | va_list args; 71 | va_start(args, format); 72 | NSLogv(newFormat, args); 73 | va_end(args); 74 | } 75 | #define _SSKLog(format, ...) __SSKLog(@format, ##__VA_ARGS__) 76 | 77 | #endif// USE_NSLOG 78 | 79 | #define SSKVerboseLog(format, ...) { if (g_logLevel >= SSKLOGLEVEL_VERBOSE) _SSKLog("[verb] " format, ##__VA_ARGS__); } 80 | #define SSKInfoLog(format, ...) { if (g_logLevel >= SSKLOGLEVEL_INFO) _SSKLog("[info] " format, ##__VA_ARGS__); } 81 | #define SSKWarningLog(format, ...) { if (g_logLevel >= SSKLOGLEVEL_WARNING) _SSKLog("[warn] " format, ##__VA_ARGS__); } 82 | 83 | 84 | static void __hookbody_leaved(char (*p)[]) { 85 | SSKVerboseLog(" << Leaving %{public}s()", *p); 86 | } 87 | 88 | #define HOOKBODY(body) { \ 89 | SSKVerboseLog(" >> Entering %{public}s()", __func__); \ 90 | __attribute__((cleanup(__hookbody_leaved))) char _hookbody_defer[0x100]; \ 91 | strcpy(_hookbody_defer, __func__); body; \ 92 | } 93 | 94 | #define UNUSED(var) ((void)(var)); 95 | 96 | // Utility function to read the Tweak's preferences 97 | static BOOL shouldHookFromPreference() 98 | { 99 | #if SUBSTRATE_BUILD 100 | NSString *preferenceSetting = PREFERENCE_KEY; 101 | BOOL shouldHook = NO; 102 | NSMutableDictionary* plist = [[NSMutableDictionary alloc] initWithContentsOfFile:PREFERENCE_FILE]; 103 | 104 | if (!plist) 105 | { 106 | SSKWarningLog("Preference file %{public}@ not found.", PREFERENCE_FILE); 107 | } 108 | else 109 | { 110 | g_logLevel = [[plist objectForKey:@"logLevel"] integerValue]; 111 | if (!g_logLevel) { 112 | SSKWarningLog("LogLevel is wrong (set to zero), we will output everything!"); 113 | g_logLevel = SSKLOGLEVEL_VERBOSE; 114 | } 115 | SSKInfoLog("Using LogLevel = %d", g_logLevel); 116 | 117 | shouldHook = [[plist objectForKey:@"shouldDisableCertificateValidation"] boolValue]; 118 | SSKInfoLog("Preference set to %d.", shouldHook); 119 | 120 | // Checking if BundleId has been excluded by user 121 | NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; 122 | bundleId = [bundleId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 123 | 124 | NSString *excludedBundleIdsString = [plist objectForKey:@"excludedBundleIds"]; 125 | excludedBundleIdsString = [excludedBundleIdsString stringByReplacingOccurrencesOfString:@" " withString:@""]; 126 | 127 | NSArray *excludedBundleIds = [excludedBundleIdsString componentsSeparatedByString:@","]; 128 | 129 | if ([excludedBundleIds containsObject:bundleId]) 130 | { 131 | SSKInfoLog("Not hooking excluded bundle: %{public}@", bundleId); 132 | shouldHook = NO; 133 | } 134 | } 135 | return shouldHook; 136 | #else 137 | // Always hook when using fishhook (for iOS jailed / macOS) 138 | return YES; 139 | #endif 140 | } 141 | 142 | 143 | 144 | #pragma mark SecureTransport hooks - iOS 9 and below 145 | // Explanation here: https://nabla-c0d3.github.io/blog/2013/08/20/ios-ssl-kill-switch-v0-dot-5-released/ 146 | 147 | static OSStatus (*original_SSLSetSessionOption)(SSLContextRef context, 148 | SSLSessionOption option, 149 | Boolean value); 150 | 151 | static OSStatus replaced_SSLSetSessionOption(SSLContextRef context, 152 | SSLSessionOption option, 153 | Boolean value) 154 | HOOKBODY({ 155 | // Remove the ability to modify the value of the kSSLSessionOptionBreakOnServerAuth option 156 | if (option == kSSLSessionOptionBreakOnServerAuth) 157 | { 158 | return noErr; 159 | } 160 | return original_SSLSetSessionOption(context, option, value); 161 | }) 162 | 163 | 164 | static SSLContextRef (*original_SSLCreateContext)(CFAllocatorRef alloc, 165 | SSLProtocolSide protocolSide, 166 | SSLConnectionType connectionType); 167 | 168 | static SSLContextRef replaced_SSLCreateContext(CFAllocatorRef alloc, 169 | SSLProtocolSide protocolSide, 170 | SSLConnectionType connectionType) 171 | HOOKBODY({ 172 | SSLContextRef sslContext = original_SSLCreateContext(alloc, protocolSide, connectionType); 173 | 174 | // Immediately set the kSSLSessionOptionBreakOnServerAuth option in order to disable cert validation 175 | original_SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true); 176 | return sslContext; 177 | }) 178 | 179 | 180 | static OSStatus (*original_SSLHandshake)(SSLContextRef context); 181 | 182 | static OSStatus replaced_SSLHandshake(SSLContextRef context) 183 | HOOKBODY({ 184 | 185 | OSStatus result = original_SSLHandshake(context); 186 | 187 | // Hijack the flow when breaking on server authentication 188 | if (result == errSSLServerAuthCompleted) 189 | { 190 | // Do not check the cert and call SSLHandshake() again 191 | return original_SSLHandshake(context); 192 | } 193 | 194 | return result; 195 | }) 196 | 197 | 198 | #pragma mark libsystem_coretls.dylib hooks - iOS 10 199 | // Explanation here: https://nabla-c0d3.github.io/blog/2017/02/05/ios10-ssl-kill-switch/ 200 | 201 | static OSStatus (*original_tls_helper_create_peer_trust)(void *hdsk, bool server, SecTrustRef *trustRef); 202 | 203 | static OSStatus replaced_tls_helper_create_peer_trust(void *hdsk, bool server, SecTrustRef *trustRef) 204 | HOOKBODY({ 205 | // Do not actually set the trustRef 206 | return errSecSuccess; 207 | }) 208 | 209 | 210 | #pragma mark BoringSSL hooks - iOS 12 211 | // Explanation here: https://nabla-c0d3.github.io/blog/2019/05/18/ssl-kill-switch-for-ios12/ 212 | 213 | // Everyone's favorite OpenSSL constant 214 | #define SSL_VERIFY_NONE 0 215 | 216 | // Constant defined in BoringSSL 217 | enum ssl_verify_result_t { 218 | ssl_verify_ok = 0, 219 | ssl_verify_invalid, 220 | ssl_verify_retry, 221 | }; 222 | 223 | 224 | char *replaced_SSL_get_psk_identity(void *ssl) 225 | HOOKBODY({ 226 | return "notarealPSKidentity"; 227 | }) 228 | 229 | 230 | static int custom_verify_callback_that_does_not_validate(void *ssl, uint8_t *out_alert) 231 | HOOKBODY({ 232 | // Yes this certificate is 100% valid... 233 | return ssl_verify_ok; 234 | }) 235 | 236 | 237 | static void (*original_SSL_CTX_set_custom_verify)(void *ctx, int mode, int (*callback)(void *ssl, uint8_t *out_alert)); 238 | static void replaced_SSL_CTX_set_custom_verify(void *ctx, int mode, int (*callback)(void *ssl, uint8_t *out_alert)) 239 | HOOKBODY({ 240 | original_SSL_CTX_set_custom_verify(ctx, SSL_VERIFY_NONE, custom_verify_callback_that_does_not_validate); 241 | return; 242 | }) 243 | 244 | 245 | static void (*original_SSL_set_custom_verify)(void *ssl, int mode, int (*callback)(void *ssl, uint8_t *out_alert)); 246 | static void replaced_SSL_set_custom_verify(void *ssl, int mode, int (*callback)(void *ssl, uint8_t *out_alert)) 247 | HOOKBODY({ 248 | original_SSL_set_custom_verify(ssl, SSL_VERIFY_NONE, custom_verify_callback_that_does_not_validate); 249 | return; 250 | }) 251 | 252 | 253 | #pragma mark CocoaSPDY hook 254 | 255 | static void (*oldSetTLSTrustEvaluator)(id self, SEL _cmd, id evaluator); 256 | 257 | static void newSetTLSTrustEvaluator(id self, SEL _cmd, id evaluator) 258 | HOOKBODY({ 259 | // Set a nil evaluator to disable SSL validation 260 | oldSetTLSTrustEvaluator(self, _cmd, nil); 261 | }) 262 | 263 | static void (*oldSetprotocolClasses)(id self, SEL _cmd, NSArray *protocolClasses); 264 | 265 | static void newSetprotocolClasses(id self, SEL _cmd, NSArray *protocolClasses) 266 | HOOKBODY({ 267 | // Do not register protocol classes which is how CocoaSPDY works 268 | // This should force the App to downgrade from SPDY to HTTPS 269 | }) 270 | 271 | static void (*oldRegisterOrigin)(id self, SEL _cmd, NSString *origin); 272 | 273 | static void newRegisterOrigin(id self, SEL _cmd, NSString *origin) 274 | HOOKBODY({ 275 | // Do not register protocol classes which is how CocoaSPDY works 276 | // This should force the App to downgrade from SPDY to HTTPS 277 | }) 278 | 279 | #pragma mark SecPolicyCreateAppleSSLPinned hook 280 | // adapted from https://github.com/sskaje/ssl-kill-switch2/commit/92a4222a4db7b16179b5a3045e1647ce13532c75 281 | // use with AppleServerAuthenticationNoPinning in https://vtky.github.io/2021/01/05/apple-globalpreferences 282 | 283 | static bool (*original_SecIsInternalRelease)(void); 284 | static bool replace_SecIsInternalRelease(void) 285 | HOOKBODY({ 286 | SSKVerboseLog("replace_SecIsInternalRelease: void"); 287 | static bool isInternal = true; 288 | return isInternal; 289 | }) 290 | 291 | #pragma mark SecTrustEvaluate API hook 292 | // adapted from https://github.com/doug-leith/cydia/blob/7b14460d01224526a440267f3735b079bf0ab4eb/unpin/Tweak.m 293 | 294 | static OSStatus (*original_SecTrustEvaluate)(SecTrustRef trust, SecTrustResultType *result); 295 | static OSStatus replaced_SecTrustEvaluate(SecTrustRef trust, SecTrustResultType *result) 296 | HOOKBODY({ 297 | OSStatus res = original_SecTrustEvaluate(trust, result); 298 | UNUSED (res); 299 | if (result) { 300 | SSKVerboseLog("Overrided SecTrustEvaluate() = %d, original result %d -> kSecTrustResultUnspecified(4)", res, *result); 301 | // Actually, this certificate chain is trusted 302 | *result = kSecTrustResultUnspecified; 303 | } 304 | return 0; // errSecSuccess 305 | }) 306 | 307 | static bool (*original_SecTrustEvaluateWithError)(SecTrustRef trust, CFErrorRef *error); 308 | static bool replaced_SecTrustEvaluateWithError(SecTrustRef trust, CFErrorRef *error) 309 | HOOKBODY({ 310 | bool res = original_SecTrustEvaluateWithError(trust, error); 311 | UNUSED (res); 312 | if (error) { 313 | if (*error) { 314 | SSKVerboseLog("Overrided SecTrustEvaluateWithError() = %d, original err %{public}@", (int)res, *error); 315 | *error = nil; 316 | } 317 | } 318 | return true; // true means trusted 319 | }) 320 | 321 | static OSStatus (*original_SecTrustEvaluateAsync)(SecTrustRef trust, dispatch_queue_t queue, SecTrustCallback result); 322 | static OSStatus replaced_SecTrustEvaluateAsync(SecTrustRef trust, dispatch_queue_t queue, SecTrustCallback result) 323 | HOOKBODY({ 324 | dispatch_async(queue, ^{ 325 | SSKVerboseLog("Overrided SecTrustEvaluateAsync!"); 326 | result( 327 | trust, // SecTrustRef trust 328 | 1 // bool result 329 | ); // call the callback with success result 330 | }); 331 | return 0; // errSecSuccess 332 | }) 333 | 334 | static OSStatus (*original_SecTrustEvaluateAsyncWithError)(SecTrustRef trust, dispatch_queue_t queue, SecTrustWithErrorCallback result); 335 | static OSStatus replaced_SecTrustEvaluateAsyncWithError(SecTrustRef trust, dispatch_queue_t queue, SecTrustWithErrorCallback result) 336 | HOOKBODY({ 337 | dispatch_async(queue, ^{ 338 | SSKVerboseLog("Overrided SecTrustEvaluateAsyncWithError!"); 339 | result( 340 | trust, // SecTrustRef trust 341 | 1, // bool result 342 | NULL // CFErrorRef error (nullable) 343 | ); // call the callback with success result 344 | }); 345 | return 0; // errSecSuccess 346 | }) 347 | 348 | static OSStatus (*original_SecTrustEvaluateFastAsync)(SecTrustRef trust, dispatch_queue_t queue, SecTrustCallback result); 349 | static OSStatus replaced_SecTrustEvaluateFastAsync(SecTrustRef trust, dispatch_queue_t queue, SecTrustCallback result) 350 | HOOKBODY({ 351 | dispatch_async(queue, ^{ 352 | SSKVerboseLog("Overrided SecTrustEvaluateFastAsync!"); 353 | result( 354 | trust, // SecTrustRef trust 355 | 1 // bool result 356 | ); // call the callback with success result 357 | }); 358 | return 0; // errSecSuccess 359 | }) 360 | 361 | static OSStatus (*original_SecTrustSetPolicies)(SecTrustRef trust, void* policies); 362 | static OSStatus replaced_SecTrustSetPolicies(SecTrustRef trust, void* policies) 363 | HOOKBODY({ 364 | SSKVerboseLog("Overrided SecTrustSetPolicies!"); 365 | return 0; // errSecSuccess 366 | }) 367 | 368 | static Boolean (*original_SecKeyVerifySignature)(SecKeyRef key, SecKeyAlgorithm algorithm, CFDataRef signedData, CFDataRef signature, CFErrorRef *error); 369 | static Boolean replaced_SecKeyVerifySignature(SecKeyRef key, SecKeyAlgorithm algorithm, CFDataRef signedData, CFDataRef signature, CFErrorRef *error) 370 | HOOKBODY({ 371 | SSKVerboseLog("Overrided SecKeyVerifySignature!"); 372 | if (!!error) { 373 | *error = NULL; 374 | } 375 | return TRUE; 376 | }) 377 | 378 | static OSStatus (*original_SecKeyRawVerify)(SecKeyRef key, SecPadding padding, const uint8_t *signedData, size_t signedDataLen, const uint8_t *sig, size_t sigLen); 379 | static OSStatus replaced_SecKeyRawVerify(SecKeyRef key, SecPadding padding, const uint8_t *signedData, size_t signedDataLen, const uint8_t *sig, size_t sigLen) 380 | HOOKBODY({ 381 | SSKVerboseLog("Overrided SecKeyRawVerify!"); 382 | return errSecSuccess; 383 | }) 384 | 385 | #pragma mark Manual Pinning ([NSURLSessionDelegate URLSession:didReceiveChallenge:completionHandler:]) 386 | // https://developer.apple.com/documentation/foundation/nsurlauthenticationmethodservertrust 387 | // URLSession:didReceiveChallenge:completionHandler: are triggered in CFNetwork from 4 places: 388 | // -[__NSCFLocalSessionTask _onqueue_didReceiveChallenge:request:withCompletion:] - easy to patch, usually triggers 389 | // -[__NSCFTCPIOStreamTask _onqueue_sendSessionChallenge:completionHandler:] - easy to patch, hardly triggers 390 | // -[__NSURLBackgroundSession backgroundTask:didReceiveChallenge:reply:] - hard to patch (have some password auth setup inside), hardly triggers 391 | // unknown - cannot analysis due to missing xref 392 | 393 | void checkChallengeAndOverride(id challenge, void (^completion)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential)) { 394 | BOOL needOverrideCompletion = NO; 395 | 396 | NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; 397 | if ([@"https" isEqualToString:[protectionSpace protocol]]) { 398 | needOverrideCompletion = YES; 399 | } 400 | if (needOverrideCompletion) { 401 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 402 | completion(NSURLSessionAuthChallengeUseCredential, [[NSURLCredential alloc] initWithTrust:[protectionSpace serverTrust]]); 403 | }); 404 | } 405 | } 406 | 407 | static void (*old__NSCFLocalSessionTask__onqueue_didReceiveChallenge)(id self, SEL _cmd, id challenge, id request, void (^completion)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) ); 408 | static void new__NSCFLocalSessionTask__onqueue_didReceiveChallenge(id self, SEL _cmd, id challenge, id request, void (^completion)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) ) 409 | HOOKBODY({ 410 | SSKVerboseLog("__NSCFLocalSessionTask _onqueue_didReceiveChallenge! protectionSpace: %{public}@", [challenge protectionSpace]); 411 | checkChallengeAndOverride(challenge, completion); 412 | // return %orig(challenge, req, completion); 413 | }) 414 | 415 | static BOOL (*old__NSCFTCPIOStreamTask__onqueue_sendSessionChallenge)(id self, SEL _cmd, id challenge, void (^completion)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) ); 416 | static BOOL new__NSCFTCPIOStreamTask__onqueue_sendSessionChallenge(id self, SEL _cmd, id challenge, void (^completion)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) ) 417 | HOOKBODY({ 418 | SSKVerboseLog("__NSCFTCPIOStreamTask _onqueue_sendSessionChallenge! protectionSpace: %{public}@", [challenge protectionSpace]); 419 | checkChallengeAndOverride(challenge, completion); 420 | return YES; 421 | // return %orig; 422 | }) 423 | 424 | #pragma mark AFNetworking 425 | static BOOL (*old__AFSecurityPolicy_setSSLPinningMode)(id self, SEL _cmd, uintptr_t mode); 426 | static BOOL new__AFSecurityPolicy_setSSLPinningMode(id self, SEL _cmd, int mode) 427 | HOOKBODY({ 428 | SSKVerboseLog("AFSecurityPolicy setSSLPinningMode: %d -> 0", mode); 429 | return old__AFSecurityPolicy_setSSLPinningMode(self, _cmd, 0); // AFSSLPinningModeNone 430 | }) 431 | 432 | static BOOL (*old__AFSecurityPolicy_setAllowInvalidCertificates)(id self, SEL _cmd, BOOL allow); 433 | static BOOL new__AFSecurityPolicy_setAllowInvalidCertificates(id self, SEL _cmd, BOOL allow) 434 | HOOKBODY({ 435 | SSKVerboseLog("AFSecurityPolicy setAllowInvalidCertificates: %d -> YES", allow); 436 | return old__AFSecurityPolicy_setAllowInvalidCertificates(self, _cmd, YES); 437 | }) 438 | 439 | static BOOL (*old__AFSecurityPolicy_policyWithPinningMode)(id cls, SEL _cmd, BOOL mode); 440 | static BOOL new__AFSecurityPolicy_policyWithPinningMode(id cls, SEL _cmd, BOOL mode) 441 | HOOKBODY({ 442 | SSKVerboseLog("AFSecurityPolicy policyWithPinningMode: %d -> AFSSLPinningModeNone", mode); 443 | return old__AFSecurityPolicy_setAllowInvalidCertificates(cls, _cmd, 0); // AFSSLPinningModeNone 444 | }) 445 | 446 | static BOOL (*old__AFSecurityPolicy_policyWithPinningMode_withPinnedCertificates)(id cls, SEL _cmd, BOOL mode, id cert); 447 | static BOOL new__AFSecurityPolicy_policyWithPinningMode_withPinnedCertificates(id cls, SEL _cmd, BOOL mode, id cert) 448 | HOOKBODY({ 449 | SSKVerboseLog("AFSecurityPolicy policyWithPinningMode: %d withPinnedCertificates: %{public}@ -> AFSSLPinningModeNone", mode, cert); 450 | return old__AFSecurityPolicy_policyWithPinningMode_withPinnedCertificates(cls, _cmd, 0, cert); // AFSSLPinningModeNone 451 | }) 452 | 453 | #pragma mark TrustKit - TSKPinningValidator 454 | 455 | // "- evaluateTrust:forHostname:" 456 | static int (*old__TSKPinningValidator_evaluateTrust_forHostname)(id self, SEL _cmd, id trust, id hostname); 457 | static int new__TSKPinningValidator_evaluateTrust_forHostname(id self, SEL _cmd, id trust, id hostname) 458 | HOOKBODY({ 459 | int ret = old__TSKPinningValidator_evaluateTrust_forHostname(self, _cmd, trust, hostname); // AFSSLPinningModeNone 460 | SSKVerboseLog("TSKPinningValidator evaluateTrust: %{public}@ forHostname: %{public}@ ret: %d -> 0", trust, hostname, ret); 461 | return 0; // pass 462 | }) 463 | 464 | #pragma mark cordova - CustomURLConnectionDelegate 465 | // "- isFingerprintTrusted:" 466 | static int (*old__CustomURLConnectionDelegate_isFingerprintTrusted)(id self, SEL _cmd, id fingerprint); 467 | static int new__CustomURLConnectionDelegate_isFingerprintTrusted(id self, SEL _cmd, id fingerprint) 468 | HOOKBODY({ 469 | int ret = old__CustomURLConnectionDelegate_isFingerprintTrusted(self, _cmd, fingerprint); // AFSSLPinningModeNone 470 | SSKVerboseLog("CustomURLConnectionDelegate isFingerprintTrusted: %{public}@ ret: %d -> 0", fingerprint, ret); 471 | return 0; // pass 472 | }) 473 | 474 | #pragma mark Dylib Constructor 475 | 476 | #include 477 | 478 | static uint64_t parse_branch_instruction(uint32_t instruction, uint64_t pc) { 479 | // parse B instruction 480 | uint32_t opcode = (instruction >> 26) & 0x3F; 481 | printf("%x\n", opcode); 482 | uint32_t imm26 = instruction & 0x03FFFFFF; 483 | 484 | // check if it's B instruction(opcode == 0b100101) 485 | if (opcode != 0b000101) { 486 | return 0; 487 | } 488 | 489 | // calc target address 490 | uint32_t sign_bit = imm26 >> 25; 491 | uint64_t offset = (imm26 << 2) & 0x1FFFFFF; 492 | uint64_t target_address = pc + offset; 493 | 494 | // handle imm26 sign bit 495 | if (sign_bit) { 496 | target_address -= (1 << 25); 497 | } 498 | 499 | return target_address; 500 | } 501 | 502 | void hookF(const char *libName, const char *funcName, void *replaceFun, void **origFun) { 503 | SSKVerboseLog("[init] hookF('%{public}s', '%{public}s', %p, %p);", libName ? libName:"(null)", funcName, replaceFun, origFun); 504 | 505 | void *libHandle = RTLD_DEFAULT; 506 | if (libName) { 507 | libHandle = dlopen(libName, RTLD_NOW); 508 | if (!libHandle) { 509 | libHandle = RTLD_DEFAULT; 510 | } 511 | } 512 | void *pFunc = dlsym(libHandle, funcName); 513 | if (!pFunc) { 514 | SSKInfoLog("[init] hookF failed to find function %{public}s", funcName); 515 | return; 516 | } 517 | uint32_t *pIns = (uint32_t *)ptrauth_strip(pFunc, ptrauth_key_function_pointer); 518 | SSKVerboseLog("[init] hookF resolved pFunc -> %p -> %p", pFunc, pIns); 519 | 520 | #if SUBSTRATE_BUILD 521 | void *originalMem[3] = {0}; 522 | memcpy(originalMem, pIns, sizeof(originalMem)); 523 | uintptr_t targetAddr = parse_branch_instruction(pIns[0], (uint64_t)pIns); 524 | if (targetAddr) { 525 | SSKInfoLog("%{public}s jumps to %p: %llx, hook new addr instead!", funcName, targetAddr, *(void **)targetAddr); 526 | pFunc = (void *)targetAddr; 527 | } 528 | // SSKVerboseLog("MSHookFunction(%p, %p, %p)"); 529 | MSHookFunction(pFunc, replaceFun, origFun); 530 | 531 | void *newMem[3] = {0}; 532 | memcpy(newMem, pIns, sizeof(newMem)); 533 | SSKVerboseLog("[init] hookF result: func %{public}s ptr %p, from %p %p %p to %p %p %p", 534 | funcName, pIns, 535 | originalMem[0], originalMem[1], originalMem[2], 536 | newMem[0], newMem[1], newMem[2]); 537 | #else 538 | if (origFun) 539 | *origFun = pFunc; 540 | if (rebind_symbols((struct rebinding[1]){{(char *)funcName, (void *)replaceFun}}, 1) < 0) { 541 | SSKInfoLog("Failed to do fish hook for %{public}s!", funcName); 542 | } 543 | #endif 544 | } 545 | 546 | BOOL hookM(Class _class, SEL _cmd, IMP _new, IMP *_old) { 547 | SSKVerboseLog("[init] hookM(%p, '%{public}@', %p, %p);", _class, NSStringFromSelector(_cmd), _new, _old); 548 | if (!_class) { 549 | return NO; 550 | } 551 | #if SUBSTRATE_BUILD 552 | MSHookMessageEx(_class, _cmd, _new, _old); 553 | return YES; 554 | #else 555 | // From: static void _logos_register_hook(Class _class, SEL _cmd, IMP _new, IMP* _old) 556 | unsigned int _count, _i; 557 | Class _searchedClass = _class; 558 | Method* _methods; 559 | while (_searchedClass) { 560 | _methods = class_copyMethodList(_searchedClass, &_count); 561 | for (_i = 0; _i < _count; _i++) { 562 | if (method_getName(_methods[_i]) == _cmd) { 563 | if (_class == _searchedClass) { 564 | *_old = method_getImplementation(_methods[_i]); 565 | *_old = method_setImplementation(_methods[_i], _new); 566 | } else { 567 | class_addMethod(_class, _cmd, _new, 568 | method_getTypeEncoding(_methods[_i])); 569 | } 570 | free(_methods); 571 | return YES; 572 | } 573 | } 574 | free(_methods); 575 | _searchedClass = class_getSuperclass(_searchedClass); 576 | } 577 | return NO; 578 | #endif 579 | } 580 | 581 | __attribute__((constructor)) static void init(int argc, const char **argv) 582 | { 583 | // Only hook if the preference file says so 584 | if (shouldHookFromPreference()) 585 | { 586 | SSKInfoLog("Hook enabled."); 587 | 588 | NSProcessInfo *processInfo = [NSProcessInfo processInfo]; 589 | if ([processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){12, 0, 0}]) 590 | { 591 | // Support for iOS 12 and 13 592 | 593 | if ([processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){13, 0, 0}]) 594 | { 595 | SSKInfoLog("iOS 13+ detected"); 596 | // iOS 13 uses SSL_set_custom_verify() which was recently added to BoringSSL 597 | hookF("/usr/lib/libboringssl.dylib", "SSL_set_custom_verify", (void *) replaced_SSL_set_custom_verify, (void **) &original_SSL_set_custom_verify); 598 | } 599 | else 600 | { 601 | SSKInfoLog("iOS 12 detected"); 602 | // iOS 12 uses the older SSL_CTX_set_custom_verify() 603 | hookF("/usr/lib/libboringssl.dylib", "SSL_CTX_set_custom_verify", (void *) replaced_SSL_CTX_set_custom_verify, (void **) &original_SSL_CTX_set_custom_verify); 604 | } 605 | 606 | // Hook SSL_get_psk_identity() on both iOS 12 and 13 607 | hookF("/usr/lib/libboringssl.dylib", "SSL_get_psk_identity", (void *) replaced_SSL_get_psk_identity, (void **) NULL); 608 | } 609 | else if ([processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){11, 0, 0}]) 610 | { 611 | // Support for iOS 11 612 | SSKInfoLog("iOS 11 detected; hooking nw_tls_create_peer_trust()..."); 613 | hookF("/usr/lib/libnetwork.dylib", "nw_tls_create_peer_trust", (void *) replaced_tls_helper_create_peer_trust, (void **) &original_tls_helper_create_peer_trust); 614 | } 615 | else if ([processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 0, 0}]) 616 | { 617 | // Support for iOS 10 618 | SSKInfoLog("iOS 10 detected; hooking tls_helper_create_peer_trust()..."); 619 | hookF(NULL, "tls_helper_create_peer_trust", (void *) replaced_tls_helper_create_peer_trust, (void **) &original_tls_helper_create_peer_trust); 620 | } 621 | else if ([processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){8, 0, 0}]) 622 | { 623 | // SecureTransport hooks - works up to iOS 9 624 | SSKInfoLog("iOS 8 or 9 detected; hooking SecureTransport..."); 625 | hookF(NULL, "SSLHandshake",(void *) replaced_SSLHandshake, (void **) &original_SSLHandshake); 626 | hookF(NULL, "SSLSetSessionOption",(void *) replaced_SSLSetSessionOption, (void **) &original_SSLSetSessionOption); 627 | hookF(NULL, "SSLCreateContext",(void *) replaced_SSLCreateContext, (void **) &original_SSLCreateContext); 628 | } 629 | 630 | // CocoaSPDY hooks - https://github.com/twitter/CocoaSPDY 631 | // TODO: Enable these hooks for the fishhook-based hooking so it works on OS X too 632 | Class spdyProtocolClass = NSClassFromString(@"SPDYProtocol"); 633 | if (spdyProtocolClass) 634 | { 635 | SSKInfoLog("CocoaSPDY detected; hooking it..."); 636 | // Disable trust evaluation 637 | hookM(object_getClass(spdyProtocolClass), NSSelectorFromString(@"setTLSTrustEvaluator:"), (IMP) &newSetTLSTrustEvaluator, (IMP *)&oldSetTLSTrustEvaluator); 638 | 639 | // CocoaSPDY works by getting registered as a NSURLProtocol; block that so the Apps switches back to HTTP as SPDY is tricky to proxy 640 | Class spdyUrlConnectionProtocolClass = NSClassFromString(@"SPDYURLConnectionProtocol"); 641 | hookM(object_getClass(spdyUrlConnectionProtocolClass), NSSelectorFromString(@"registerOrigin:"), (IMP) &newRegisterOrigin, (IMP *)&oldRegisterOrigin); 642 | 643 | hookM(NSClassFromString(@"NSURLSessionConfiguration"), NSSelectorFromString(@"setprotocolClasses:"), (IMP) &newSetprotocolClasses, (IMP *)&oldSetprotocolClasses); 644 | } 645 | 646 | SSKInfoLog("Hooking Security framework..."); 647 | // Security framework hook 1 648 | hookF(NULL, "SecIsInternalRelease", (void *) replace_SecIsInternalRelease, (void **) &original_SecIsInternalRelease); 649 | 650 | // SecTrustEvaluate iOS 2-13 651 | // SecTrustEvaluateAsync iOS 7-13 652 | hookF(NULL, "SecTrustEvaluate",(void *) replaced_SecTrustEvaluate, (void **) &original_SecTrustEvaluate); 653 | hookF(NULL, "SecTrustEvaluateAsync",(void *) replaced_SecTrustEvaluateAsync, (void **) &original_SecTrustEvaluateAsync); 654 | // SecTrustEvaluateWithError iOS 12- 655 | // SecTrustEvaluateAsyncWithError iOS 13- 656 | // SecTrustEvaluateFastAsync iOS 12- 657 | hookF(NULL, "SecTrustEvaluateWithError",(void *) replaced_SecTrustEvaluateWithError, (void **) &original_SecTrustEvaluateWithError); 658 | hookF(NULL, "SecTrustEvaluateAsyncWithError",(void *) replaced_SecTrustEvaluateAsyncWithError, (void **) &original_SecTrustEvaluateAsyncWithError); 659 | hookF(NULL, "SecTrustEvaluateFastAsync",(void *) replaced_SecTrustEvaluateFastAsync, (void **) &original_SecTrustEvaluateFastAsync); 660 | // SecTrustEvaluateWithError iOS 6- 661 | hookF(NULL, "SecTrustSetPolicies",(void *) replaced_SecTrustSetPolicies, (void **) &original_SecTrustSetPolicies); 662 | // SecKeyVerifySignature iOS 10- 663 | // SecKeyRawVerify iOS 2-15 664 | hookF(NULL, "SecKeyVerifySignature",(void *) replaced_SecKeyVerifySignature, (void **) &original_SecKeyVerifySignature); 665 | hookF(NULL, "SecKeyRawVerify",(void *) replaced_SecKeyRawVerify, (void **) &original_SecKeyRawVerify); 666 | 667 | SSKInfoLog("Hooking URLSession..."); 668 | // hook URLSession:didReceiveChallenge:completionHandler: 669 | if (!hookM(NSClassFromString(@"__NSCFLocalSessionTask"), NSSelectorFromString(@"_onqueue_didReceiveChallenge:request:withCompletion:"), (IMP) &new__NSCFLocalSessionTask__onqueue_didReceiveChallenge, (IMP *)&old__NSCFLocalSessionTask__onqueue_didReceiveChallenge)) { 670 | SSKInfoLog("Cannot find [__NSCFLocalSessionTask _onqueue_didReceiveChallenge:request:withCompletion:]"); 671 | } 672 | if (!hookM(NSClassFromString(@"__NSCFTCPIOStreamTask"), NSSelectorFromString(@"_onqueue_sendSessionChallenge:completionHandler:"), (IMP) &new__NSCFTCPIOStreamTask__onqueue_sendSessionChallenge, (IMP *)&old__NSCFTCPIOStreamTask__onqueue_sendSessionChallenge)) { 673 | SSKInfoLog("Cannot find [__NSCFTCPIOStreamTask _onqueue_sendSessionChallenge:completionHandler:]"); 674 | } 675 | 676 | // AFNetworking hook: https://github.com/sensepost/objection/blob/6c55d7e46292048d629dbe361701e5fe3e02d8d0/agent/src/ios/pinning.ts#L48 677 | Class afSecurifyPolicyClass = NSClassFromString(@"AFSecurityPolicy"); 678 | if (afSecurifyPolicyClass) 679 | { 680 | SSKInfoLog("AFNetworking detected; hooking it..."); 681 | // - setSSLPinningMode: & - setAllowInvalidCertificates: 682 | hookM(afSecurifyPolicyClass, NSSelectorFromString(@"setSSLPinningMode:"), (IMP) &new__AFSecurityPolicy_setSSLPinningMode, (IMP *)&old__AFSecurityPolicy_setSSLPinningMode); 683 | hookM(afSecurifyPolicyClass, NSSelectorFromString(@"setAllowInvalidCertificates:"), (IMP) &new__AFSecurityPolicy_setAllowInvalidCertificates, (IMP *)&old__AFSecurityPolicy_setAllowInvalidCertificates); 684 | // + policyWithPinningMode: & + policyWithPinningMode:withPinnedCertificates: 685 | hookM(object_getClass(afSecurifyPolicyClass), NSSelectorFromString(@"policyWithPinningMode:"), (IMP) &new__AFSecurityPolicy_policyWithPinningMode, (IMP *)&old__AFSecurityPolicy_policyWithPinningMode); 686 | hookM(object_getClass(afSecurifyPolicyClass), NSSelectorFromString(@"policyWithPinningMode:withPinnedCertificates:"), (IMP) &new__AFSecurityPolicy_policyWithPinningMode_withPinnedCertificates, (IMP *)&old__AFSecurityPolicy_policyWithPinningMode_withPinnedCertificates); 687 | } 688 | // TrustKit TSKPinningValidator hook: https://github.com/sensepost/objection/blob/6c55d7e46292048d629dbe361701e5fe3e02d8d0/agent/src/ios/pinning.ts#L254 689 | Class tskPinningValidatorClass = NSClassFromString(@"TSKPinningValidator"); 690 | if (tskPinningValidatorClass) 691 | { 692 | SSKInfoLog("TrustKit TSKPinningValidator detected; hooking it..."); 693 | // - evaluateTrust:forHostname: 694 | hookM(tskPinningValidatorClass, NSSelectorFromString(@"evaluateTrust:forHostname:"), (IMP) &new__TSKPinningValidator_evaluateTrust_forHostname, (IMP *)&old__TSKPinningValidator_evaluateTrust_forHostname); 695 | } 696 | // SSLCertificateChecker-PhoneGap-Plugin CustomURLConnectionDelegate hook: https://github.com/sensepost/objection/blob/6c55d7e46292048d629dbe361701e5fe3e02d8d0/agent/src/ios/pinning.ts#L285 697 | Class customURLConnectionDelegateClass = NSClassFromString(@"CustomURLConnectionDelegate"); 698 | if (customURLConnectionDelegateClass) 699 | { 700 | SSKInfoLog("SSLCertificateChecker-PhoneGap-Plugin CustomURLConnectionDelegate detected; hooking it..."); 701 | // - isFingerprintTrusted: 702 | hookM(customURLConnectionDelegateClass, NSSelectorFromString(@"isFingerprintTrusted:"), (IMP) &new__CustomURLConnectionDelegate_isFingerprintTrusted, (IMP *)&old__CustomURLConnectionDelegate_isFingerprintTrusted); 703 | } 704 | 705 | SSKInfoLog("Finished Hooking!"); 706 | } 707 | else 708 | { 709 | SSKInfoLog("Hook disabled."); 710 | } 711 | } 712 | -------------------------------------------------------------------------------- /SSLKillSwitch2.plist: -------------------------------------------------------------------------------- 1 | { 2 | Filter = { 3 | Bundles = ( 4 | "com.apple.AuthKit", 5 | "com.apple.UIKit", 6 | "com.apple.itunesstored", 7 | ); 8 | }; 9 | } -------------------------------------------------------------------------------- /SSLKillSwitchTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SSLKillSwitchTests/Makefile: -------------------------------------------------------------------------------- 1 | TARGET := macosx:clang:latest 2 | ARCHS := x86_64 3 | 4 | include $(THEOS)/makefiles/common.mk 5 | 6 | TOOL_NAME = SSLKillSwitchTest 7 | SSLKillSwitchTest_FILES = SSLKillSwitchTests.m 8 | SSLKillSwitchTest_CFLAGS = -fobjc-arc 9 | 10 | include $(THEOS_MAKE_PATH)/tool.mk 11 | -------------------------------------------------------------------------------- /SSLKillSwitchTests/SSLKillSwitchTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | // Heavily inspired by TrustKit's test suite 5 | #pragma mark Test NSURLSession delegate 6 | 7 | @interface TestNSURLSessionDelegate : NSObject 8 | { 9 | } 10 | @property NSError *lastError; 11 | @property NSURLResponse *lastResponse; 12 | 13 | @property BOOL wasAuthHandlerCalled; // Used to validate that the delegate's auth handler was called 14 | 15 | 16 | - (void)URLSession:(NSURLSession * _Nonnull)session 17 | task:(NSURLSessionTask * _Nonnull)task 18 | didCompleteWithError:(NSError * _Nullable)error; 19 | 20 | - (void)URLSession:(NSURLSession * _Nonnull)session 21 | dataTask:(NSURLSessionDataTask * _Nonnull)dataTask 22 | didReceiveResponse:(NSURLResponse * _Nonnull)response 23 | completionHandler:(void (^ _Nonnull)(NSURLSessionResponseDisposition disposition))completionHandler; 24 | 25 | - (void)URLSession:(NSURLSession * _Nonnull)session 26 | task:(NSURLSessionTask * _Nonnull)task 27 | didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge 28 | completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, 29 | NSURLCredential * _Nullable credential))completionHandler; 30 | 31 | @end 32 | 33 | 34 | @implementation TestNSURLSessionDelegate 35 | 36 | - (void)URLSession:(NSURLSession * _Nonnull)session 37 | task:(NSURLSessionTask * _Nonnull)task 38 | didCompleteWithError:(NSError * _Nullable)error 39 | { 40 | NSLog(@"Received error, %@", error); 41 | _lastError = error; 42 | NSLog(@"Expectation fulfilled (didCompleteWithError)!"); 43 | } 44 | 45 | - (void)URLSession:(NSURLSession * _Nonnull)session 46 | dataTask:(NSURLSessionDataTask * _Nonnull)dataTask 47 | didReceiveResponse:(NSURLResponse * _Nonnull)response 48 | completionHandler:(void (^ _Nonnull)(NSURLSessionResponseDisposition disposition))completionHandler 49 | { 50 | _lastResponse = response; 51 | NSLog(@"Expectation fulfilled (didReceiveResponse)!"); 52 | } 53 | 54 | - (void)URLSession:(NSURLSession * _Nonnull)session 55 | task:(NSURLSessionTask * _Nonnull)task 56 | didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge 57 | completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, 58 | NSURLCredential * _Nullable credential))completionHandler 59 | { 60 | // Reject all certificates; this replicates what would happen when pinning validation would fail due to traffic interception 61 | completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); 62 | } 63 | 64 | 65 | @end 66 | 67 | 68 | #pragma mark Test suite 69 | @interface SKSEndToEndNSURLSessionTests : NSObject 70 | 71 | @end 72 | 73 | @implementation SKSEndToEndNSURLSessionTests 74 | 75 | - (void)setUp { 76 | [[NSURLCache sharedURLCache] removeAllCachedResponses]; 77 | } 78 | 79 | - (void)tearDown { 80 | } 81 | 82 | - (void)test 83 | { 84 | TestNSURLSessionDelegate* delegate = [[TestNSURLSessionDelegate alloc] init]; 85 | 86 | NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] 87 | delegate:delegate 88 | delegateQueue:nil]; 89 | 90 | NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.google.com/"]]; 91 | [task resume]; 92 | 93 | // Wait for the connection to succeed 94 | usleep(5000000); 95 | 96 | if (!delegate.lastResponse) { 97 | NSLog(@"FAIL: TLS certificate was rejected although all TLS validation was disabled"); 98 | exit(1); 99 | } 100 | if (!!delegate.lastError) { 101 | NSLog(@"FAIL: TLS certificate was rejected although all TLS validation was disabled"); 102 | exit(1); 103 | } 104 | } 105 | 106 | @end 107 | 108 | int main() { 109 | SKSEndToEndNSURLSessionTests *t = [[SKSEndToEndNSURLSessionTests alloc] init]; 110 | [t setUp]; 111 | [t test]; 112 | [t tearDown]; 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /layout/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: moe.misty.sslkillswitch3 2 | Name: SSL Kill Switch 3 3 | Depends: mobilesubstrate, preferenceloader 4 | Conflicts: com.nablac0d3.sslkillswitch2 5 | Version: 1.1 6 | Architecture: iphoneos-arm 7 | Description: Blackbox tool to disable SSL certificate validation - including certificate pinning - within iOS and OS X Apps. 8 | Maintainer: NyaMisty 9 | Author: NyaMisty , Alban Diquet 10 | Section: Tweaks 11 | -------------------------------------------------------------------------------- /layout/Library/PreferenceLoader/Preferences/SSLKillSwitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NyaMisty/ssl-kill-switch3/665ad3aa09ca3066799e93713eea6bf517adcaa8/layout/Library/PreferenceLoader/Preferences/SSLKillSwitch.png -------------------------------------------------------------------------------- /layout/Library/PreferenceLoader/Preferences/SSLKillSwitch_prefs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | cell 8 | PSLinkCell 9 | icon 10 | SSLKillSwitch.png 11 | label 12 | SSL Kill Switch 3 13 | 14 | items 15 | 16 | 17 | cell 18 | PSGroupCell 19 | label 20 | 21 | footerText 22 | SSL Kill Switch 3 23 | 24 | 25 | cell 26 | PSSwitchCell 27 | default 28 | 29 | defaults 30 | com.nablac0d3.SSLKillSwitchSettings 31 | key 32 | shouldDisableCertificateValidation 33 | label 34 | Disable Certificate Validation 35 | 36 | 37 | cell 38 | PSEditTextCell 39 | label 40 | Excluded BundleIDs: 41 | key 42 | excludedBundleIds 43 | default 44 | 45 | defaults 46 | com.nablac0d3.SSLKillSwitchSettings 47 | keyboard 48 | 49 | noAutoCorrect 50 | 51 | 52 | 53 | cell 54 | PSLinkListCell 55 | label 56 | Log Level 57 | key 58 | logLevel 59 | detail 60 | PSListItemsController 61 | default 62 | 10 63 | validTitles 64 | 65 | Verbose 66 | Info 67 | Warning 68 | Disable Output 69 | 70 | validValues 71 | 72 | 10 73 | 6 74 | 4 75 | 1 76 | 77 | defaults 78 | com.nablac0d3.SSLKillSwitchSettings 79 | 80 | 81 | title 82 | SSL Kill Switch 3 83 | 84 | 85 | --------------------------------------------------------------------------------