├── .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 |
--------------------------------------------------------------------------------