├── .gitignore ├── HBLogWeak.h ├── HookCompat.h ├── HookCompat.m ├── LICENSE.md ├── Makefile ├── README.md ├── control ├── doc ├── libundirect_doc1.png ├── libundirect_doc2.png └── libundirect_doc3.png ├── install_to_theos.sh ├── libundirect.h ├── libundirect.m ├── libundirect_dynamic.h ├── libundirect_hookoverwrite.h ├── pac.h └── release_build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .theos 3 | packages 4 | .doc 5 | .samples -------------------------------------------------------------------------------- /HBLogWeak.h: -------------------------------------------------------------------------------- 1 | // HBLogDebugWeak is ommited from release builds 2 | 3 | #import 4 | #ifdef __DEBUG__ 5 | #define HBLogDebugWeak(args ...) HBLogDebug(args) 6 | #else 7 | #define HBLogDebugWeak(...) 8 | #endif -------------------------------------------------------------------------------- /HookCompat.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern int HCHookFunctions(const struct LHFunctionHook *hooks, int count); -------------------------------------------------------------------------------- /HookCompat.m: -------------------------------------------------------------------------------- 1 | #import 2 | #include 3 | #import "rootless.h" 4 | #include 5 | #import "HookCompat.h" 6 | 7 | int (*__LHHookFunctions)(const struct LHFunctionHook *hooks, int count); 8 | 9 | int HCHookFunctions(const struct LHFunctionHook *hooks, int count) 10 | { 11 | static dispatch_once_t onceToken; 12 | dispatch_once (&onceToken, ^{ 13 | const char* lhPath = ROOT_PATH("/usr/lib/libhooker.dylib"); 14 | if(access(lhPath, F_OK) == 0) 15 | { 16 | void* lhImage = dlopen(lhPath, RTLD_NOW); 17 | if(lhImage) 18 | { 19 | // this is unsupported according to coolstar but it works 20 | __LHHookFunctions = (void*)dlsym(lhImage, "LHHookFunctions"); 21 | } 22 | } 23 | }); 24 | 25 | // if libhooker is available, use it 26 | if(__LHHookFunctions) 27 | { 28 | return __LHHookFunctions(hooks, count); 29 | } 30 | // otherwise, fall back to substrate 31 | else 32 | { 33 | for(int i = 0; i < count; i++) 34 | { 35 | struct LHFunctionHook hook = hooks[i]; 36 | MSHookFunction(hook.function, hook.replacement, hook.oldptr); 37 | } 38 | return 0; 39 | } 40 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2022 Lars Fröder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 2 | TARGET := iphone:clang:16.2:15.0 3 | ARCHS := arm64 arm64e 4 | else 5 | TARGET := iphone:clang:14.5:7.0 6 | ARCHS := armv7 armv7s arm64 arm64e 7 | endif 8 | 9 | include $(THEOS)/makefiles/common.mk 10 | 11 | LIBRARY_NAME = libundirect 12 | 13 | libundirect_FILES = libundirect.m HookCompat.m 14 | libundirect_CFLAGS = -fobjc-arc 15 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 16 | libundirect_LDFLAGS += -install_name @rpath/libundirect.dylib 17 | endif 18 | libundirect_INSTALL_PATH = /usr/lib 19 | libundirect_EXTRA_FRAMEWORKS = CydiaSubstrate 20 | 21 | include $(THEOS_MAKE_PATH)/library.mk 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libundirect 2 | 3 | objc_direct is a feature introduced with Xcode 12 that, when specified, turns an Objective C method into an unexported C function after compilation. 4 | 5 | This makes it completely impossible to call the method from inside a dylib thats injected into the process, as it is also impossible to know the method name unless you have an earlier version of the binary, in which the method you're looking for was compiled without objc_direct, at hand. 6 | 7 | Apple has started using objc_direct in system applications, daemons and frameworks starting in iOS 14.0. Some examples for affected binaries: MobileSafari, SafariServices.framework, CoreFoundation.framework... 8 | 9 | The dyld shared cache retains all symbols of direct methods (mainly for symbolicating crash logs), this makes it extremely easy to rebind direct methods of every binary that's inside it (Frameworks, Control Center modules, etc...), instructions are [documented below](#dyld-shared-cache). 10 | 11 | Before you can utilize libundirect, you will have to find the unexported function you're looking for via reverse engineering, this process won't be covered here. I have personally had success by searching for xrefs in an earlier binary without objc_direct, finding the methods that call your method in the new binary, hoping they are not also affected by objc_direct and finding the call to your method which will be a call to a sub_* C function now. 12 | 13 | Examples: 14 | 15 | ![example 1](doc/libundirect_doc1.png?raw=true) 16 | 17 | ![example 2](doc/libundirect_doc2.png?raw=true) 18 | 19 | ## Installation 20 | Run [install_to_theos.sh](install_to_theos.sh), then you can import it using `#import `. 21 | Also make sure to add it to your makefile: 22 | ``` 23 | _LIBRARIES = undirect 24 | ``` 25 | If you don't want to link against it, you can also use the dynamic header using `#import ` and avoid adding it to the Makefile. 26 | 27 | ## Patchfinder 28 | 29 | For the patchfinding process, it is important that you know the address of the unexported C function for at least one binary that you have, and that you have at least one other version of the binary where you don't know the address. This is important so you can make sure that the bytes stay the same accross different versions (as references to pointers usually change whenever the source code changes). 30 | 31 | Now open the binary that you know the address of in a hex editor, jump to the address and start searching for unique bytes within the function. (E.g. copy a few bytes, search the binary for them, repeat until you only have one exact match inside your function for the whole binary). Once you have the bytes that may be unique, as mentioned earlier, now search for them in a different version of the binary and make sure you also only got one exact match. If you don't get a match in the other version, your bytes probably contain a sequence that changes between versions and you have to try out different bytes. 32 | 33 | Once you have a unique byte sequence that stays the same between versions, the function [libundirect_find](libundirect.h#L25) can be used to locate your function. The start byte of the function also has to be supplied. 34 | 35 | (Just because the byte sequence stays the same between versions it can still change in a future version if the specific instructions you're searching for are replaced with other instructions) 36 | 37 | ![example 3](doc/libundirect_doc3.png?raw=true) 38 | 39 | (Note: While this patchfinders main intend is to find objc_direct methods, it can also be used to find any undefined C function) 40 | 41 | ## Rebinding methods 42 | 43 | To provide backwards compatibility with older binaries, you can rebind the direct methods to the class using the [libundirect_rebind](libundirect.h#L22) function. Last argument is the [type encoding](https://nshipster.com/type-encodings/) of the method. 44 | 45 | ## Dyld Shared Cache 46 | 47 | Finding and rebinding direct methods that are inside the dyld shared cache requires just the name of the method and the class it is on. [libundirect_dsc_find](libundirect.h#L27) and [libundirect_dsc_rebind](libundirect.h#L30) are available for this. 48 | 49 | Example (Rebinds the direct method `-(void)handleSourceMessage:(id)arg1 replyHandler:(id)arg2;` of the `CFPrefsDaemon` class): 50 | `libundirect_dsc_rebind(@"CoreFoundation", NSClassFromString(@"CFPrefsDaemon"), @selector(handleSourceMessage:replyHandler:), "v@:@@");` 51 | 52 | ## Hooking direct methods 53 | 54 | After rebinding your method using libundirect_rebind, you now need to reroute all calls to MSHookMessageEx to libundirect_MSHookMessasgeEx, the easiest way to do it is by adding `#import ` at the top of the file that has the hooks. libundirect_MSHookMessageEx detects whether the selector it is called with is a rebinded direct method and in that case it uses MSHookFunction instead, because direct methods are functions after being compiled and not methods. 55 | 56 | If you don't need to use libundirect_rebind for backwards compatibility purposes, you can also directly call MSHookFunction yourself with the pointer returned by libundirect_find. 57 | 58 | ## Reimplementing Methods 59 | If you only need to call a method and not hook it, it might be possible to just reimplement a method. libundirect offers two macros to reimplement [getters](libundirect.h#L37) and [setters](libundirect.h#L38). 60 | 61 | ## Usage Example 62 | Check out the [Undirector.xmi file](https://github.com/opa334/SafariPlus/blob/master/MobileSafari/Undirector.xmi) of Safari Plus. (Note that it gets the libundirect functions at runtime so that devices below iOS 14 do not need to have it installed. This assumption is only correct for system applications. If an AppStore application uses objc_direct, it will be used on all iOS versions supported by the application.) -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.opa334.libundirect 2 | Name: libundirect 3 | Version: 1.1.5 4 | Architecture: iphoneos-arm 5 | Description: Patchfinder and rebinder for objc_direct methods 6 | Maintainer: opa334 7 | Author: opa334 8 | Section: System 9 | Tag: role::developer 10 | -------------------------------------------------------------------------------- /doc/libundirect_doc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/libundirect/35234fc8004e756772a12e22dee90b0b8a536143/doc/libundirect_doc1.png -------------------------------------------------------------------------------- /doc/libundirect_doc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/libundirect/35234fc8004e756772a12e22dee90b0b8a536143/doc/libundirect_doc2.png -------------------------------------------------------------------------------- /doc/libundirect_doc3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/libundirect/35234fc8004e756772a12e22dee90b0b8a536143/doc/libundirect_doc3.png -------------------------------------------------------------------------------- /install_to_theos.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | set -e 3 | 4 | make clean 5 | make FINALPACKAGE=1 6 | cp -v "./.theos/obj/libundirect.dylib" "$THEOS/lib" 7 | 8 | make clean 9 | make FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless 10 | cp -v "./.theos/obj/libundirect.dylib" "$THEOS/lib/iphone/rootless" 11 | 12 | mkdir -p "$THEOS/include/libundirect" 13 | cp -v "libundirect.h" "$THEOS/include/libundirect" 14 | cp -v "libundirect_dynamic.h" "$THEOS/include/libundirect" 15 | cp -v "libundirect_hookoverwrite.h" "$THEOS/include/libundirect" 16 | echo "successfully installed libundirect" -------------------------------------------------------------------------------- /libundirect.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2022 Lars Fröder 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | typedef enum 18 | { 19 | OPTION_DO_NOT_SEEK_BACK = 1 << 0 20 | } libundirect_find_options_t; 21 | 22 | // wrapper around to MSHookMessageEx to support hooking applied methods, accessed by theos directly if libundirect_hookoverwrite.h is included 23 | void libundirect_MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old); 24 | 25 | // readds a direct method back to the class, requiring the pointer to it 26 | void libundirect_rebind(void* directPtr, Class _class, SEL selector, const char* format); 27 | 28 | // seek back to byte 29 | void* libundirect_seek_back(void* startPtr, unsigned char toByte, unsigned int maxSearch); 30 | 31 | // find a direct method by searching for unique memory bytes 32 | void* libundirect_find_with_options_and_mask(NSString* imageName, unsigned char* bytesToSearch, unsigned char* byteMask, size_t byteCount, unsigned char startByte, unsigned int seekbackMax, libundirect_find_options_t options); 33 | void* libundirect_find_with_options(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte, unsigned int seekbackMax, libundirect_find_options_t options); 34 | void* libundirect_find(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte); 35 | 36 | // find a direct method inside dyld_shared_cache by it's name 37 | void* libundirect_dsc_find(NSString* imageName, Class _class, SEL selector); 38 | 39 | // find a direct method inside dyld_shared_cache by it's name and rebind it (convenience) 40 | void libundirect_dsc_rebind(NSString* imageName, Class _class, SEL selector, const char* format); 41 | 42 | // instead of directly hooking, make all libundirect_MSHookMessageEx calls add to a batch queue 43 | void libundirect_startBatchHooks(void); 44 | 45 | // apply all hooks in batch queue in one shot using libhooker API if it exists 46 | void libundirect_applyBatchHooks(void); 47 | 48 | // apply all hooks in batch queue in one shot together with passed additional ones using libhooker API if it exists 49 | void libundirect_applyBatchHooksAndAdditional(const struct LHFunctionHook* additionalHooks, NSUInteger additionalCount); 50 | 51 | // selectors that failed to be added 52 | NSArray* libundirect_failedSelectors(); 53 | 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | 58 | // macros to readd setters and getters for ivars, these can't be hooked as the application still calls the original direct getters and setters 59 | // mainly useful to get existing code to just work without having to change everything to use the ivar instead 60 | // can only be used from xmi files 61 | #define LIBUNDIRECT_CLASS_ADD_GETTER(classname, type, ivarname, gettername) %hook classname %new - (type)gettername { return [self valueForKey:[NSString stringWithUTF8String:#ivarname]]; } %end 62 | #define LIBUNDIRECT_CLASS_ADD_SETTER(classname, type, ivarname, settername) %hook classname %new - (void)settername:(type)toset { [self setValue:toset forKey:[NSString stringWithUTF8String:#ivarname]]; } %end -------------------------------------------------------------------------------- /libundirect.m: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2022 Lars Fröder 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import "pac.h" 19 | #import "HookCompat.h" 20 | #import "HBLogWeak.h" 21 | #import "libundirect.h" 22 | 23 | #define libundirect_EXPORT __attribute__((visibility ("default"))) 24 | 25 | static BOOL libundirect_batchHookEnabled = NO; 26 | static NSMutableArray* libundirect_batchedHooks; 27 | 28 | NSString* _libundirect_getSelectorString(Class _class, SEL selector) 29 | { 30 | NSString* prefix; 31 | 32 | if(class_isMetaClass(_class)) 33 | { 34 | prefix = @"+"; 35 | } 36 | else 37 | { 38 | prefix = @"-"; 39 | } 40 | 41 | return [NSString stringWithFormat:@"%@[%@ %@]", prefix, NSStringFromClass(_class), NSStringFromSelector(selector)]; 42 | } 43 | 44 | //only on ios 45 | 46 | #import "substrate.h" 47 | 48 | NSMutableDictionary* undirectedSelectorsAndValues; 49 | NSMutableArray* failedSelectors; 50 | 51 | libundirect_EXPORT void libundirect_MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old) 52 | { 53 | HBLogDebugWeak(@"libundirect_MSHookMessageEx(%@, %@)", NSStringFromClass(_class), NSStringFromSelector(message)); 54 | if(undirectedSelectorsAndValues) 55 | { 56 | if(message) 57 | { 58 | NSString* selectorString = _libundirect_getSelectorString(_class, message); 59 | 60 | NSValue* symbol = [undirectedSelectorsAndValues objectForKey:selectorString]; 61 | if(symbol) 62 | { 63 | void* symbolPtr = [symbol pointerValue]; 64 | 65 | if(libundirect_batchHookEnabled) 66 | { 67 | HBLogDebugWeak(@"received hook for %@ which is a direct method, batching hook...", selectorString); 68 | static struct LHFunctionHook lhHook; 69 | lhHook = (struct LHFunctionHook){ symbolPtr, (void *)hook, (void **)old }; 70 | NSValue* batchedHookValue = [NSValue valueWithBytes:&lhHook objCType:@encode(struct LHFunctionHook)]; 71 | [libundirect_batchedHooks addObject:batchedHookValue]; 72 | return; 73 | } 74 | else 75 | { 76 | HBLogDebugWeak(@"received hook for %@ which is a direct method, hooking...", selectorString); 77 | MSHookFunction(symbolPtr, (void *)hook, (void **)old); 78 | return; 79 | } 80 | } 81 | } 82 | } 83 | 84 | MSHookMessageEx(_class, message, hook, old); 85 | } 86 | 87 | //only on ios end 88 | 89 | #ifdef __LP64__ 90 | 91 | int _libundirect_dyldIndexForImageName(NSString* imageName) 92 | { 93 | for (uint32_t i = 0; i < _dyld_image_count(); i++) 94 | { 95 | const char *pathC = _dyld_get_image_name(i); 96 | NSString* path = [NSString stringWithUTF8String:pathC]; 97 | NSString* cImageName = [path lastPathComponent]; 98 | 99 | if([cImageName isEqualToString:imageName]) 100 | { 101 | return i; 102 | } 103 | } 104 | 105 | return -1; 106 | } 107 | 108 | void _libundirect_addToFailedSelectors(NSString* selectorString) 109 | { 110 | static dispatch_once_t onceToken; 111 | dispatch_once (&onceToken, ^{ 112 | failedSelectors = [NSMutableArray new]; 113 | }); 114 | 115 | [failedSelectors addObject:selectorString]; 116 | } 117 | 118 | libundirect_EXPORT void libundirect_rebind(void* directPtr, Class _class, SEL selector, const char* format) 119 | { 120 | NSString* selectorString = _libundirect_getSelectorString(_class, selector); 121 | 122 | HBLogDebugWeak(@"about to apply %@ with %s to %p", selectorString, format, directPtr); 123 | 124 | static dispatch_once_t onceToken; 125 | dispatch_once (&onceToken, ^{ 126 | undirectedSelectorsAndValues = [NSMutableDictionary new]; 127 | }); 128 | 129 | if([undirectedSelectorsAndValues objectForKey:selectorString]) 130 | { 131 | // check if the selector has already been undirected 132 | // in that case, just return as it may incorrectly get added to failed selectors otherwise 133 | return; 134 | } 135 | 136 | // check whether the direct pointer is actually a valid function pointer 137 | Dl_info info; 138 | int rc = dladdr(directPtr, &info); 139 | 140 | if(rc == 0) 141 | { 142 | HBLogDebugWeak(@"failed, not a valid function pointer"); 143 | _libundirect_addToFailedSelectors(selectorString); 144 | return; 145 | } 146 | 147 | class_addMethod( 148 | _class, 149 | selector, 150 | (IMP)make_sym_callable(directPtr), 151 | format 152 | ); 153 | 154 | NSValue* ptrValue = [NSValue valueWithPointer:directPtr]; 155 | [undirectedSelectorsAndValues setObject:ptrValue forKey:selectorString]; 156 | 157 | HBLogDebugWeak(@"%@ applied", selectorString); 158 | } 159 | 160 | int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n) 161 | { 162 | const unsigned char* p = (const unsigned char*)str1; 163 | const unsigned char* q = (const unsigned char*)str2; 164 | 165 | if(p == q) return 0; 166 | 167 | for(int i = 0; i < n; i++) 168 | { 169 | unsigned char cMask = mask[i]; 170 | if((p[i] & cMask) != (q[i] & cMask)) 171 | { 172 | // we do not care about 1 / -1 173 | return -1; 174 | } 175 | } 176 | 177 | return 0; 178 | } 179 | 180 | void* _libundirect_find_in_region(vm_address_t startAddr, vm_offset_t regionLength, unsigned char* bytesToSearch, unsigned char* byteMask, size_t byteCount) 181 | { 182 | if(byteCount < 1) 183 | { 184 | return NULL; 185 | } 186 | 187 | unsigned int firstByteIndex = 0; 188 | if(byteMask != NULL) 189 | { 190 | for(size_t i = 0; i < byteCount; i++) 191 | { 192 | if(byteMask[i] == 0xFF) 193 | { 194 | firstByteIndex = i; 195 | break; 196 | } 197 | } 198 | } 199 | 200 | unsigned char firstByte = bytesToSearch[firstByteIndex]; 201 | vm_address_t curAddr = startAddr; 202 | 203 | while(curAddr < startAddr + regionLength) 204 | { 205 | size_t searchSize = (startAddr - curAddr) + regionLength; 206 | void* foundPtr = memchr((void*)curAddr,firstByte,searchSize); 207 | 208 | if(foundPtr == NULL) 209 | { 210 | HBLogDebugWeak(@"foundPtr == NULL return"); 211 | break; 212 | } 213 | 214 | vm_address_t foundAddr = (vm_address_t)foundPtr; 215 | 216 | // correct foundPtr in respect of firstByteIndex 217 | foundPtr = (void*)((intptr_t)foundPtr - firstByteIndex); 218 | 219 | size_t remainingBytes = regionLength - (foundAddr - startAddr); 220 | 221 | if(remainingBytes >= byteCount) 222 | { 223 | int memcmp_res; 224 | if(byteMask != NULL) 225 | { 226 | memcmp_res = memcmp_masked(foundPtr, bytesToSearch, byteMask, byteCount); 227 | } 228 | else 229 | { 230 | memcmp_res = memcmp(foundPtr, bytesToSearch, byteCount); 231 | } 232 | 233 | if(memcmp_res == 0) 234 | { 235 | HBLogDebugWeak(@"foundPtr = %p", foundPtr); 236 | return foundPtr; 237 | } 238 | } 239 | else 240 | { 241 | break; 242 | } 243 | 244 | curAddr = foundAddr + 1; 245 | } 246 | 247 | return NULL; 248 | } 249 | 250 | libundirect_EXPORT void* libundirect_seek_back(void* startPtr, unsigned char toByte, unsigned int maxSearch) 251 | { 252 | vm_address_t startAddr = (vm_address_t)startPtr; 253 | vm_address_t curAddr = startAddr; 254 | 255 | while((startAddr - curAddr) < maxSearch) 256 | { 257 | void* curPtr = (void*)curAddr; 258 | unsigned char curChar = *(unsigned char*)curPtr; 259 | 260 | if(curChar == toByte) 261 | { 262 | return curPtr; 263 | } 264 | 265 | curAddr = curAddr - 1; 266 | } 267 | 268 | return NULL; 269 | } 270 | 271 | libundirect_EXPORT void* libundirect_find_with_options_and_mask(NSString* imageName, unsigned char* bytesToSearch, unsigned char* byteMask, size_t byteCount, unsigned char startByte, unsigned int seekbackMax, libundirect_find_options_t options) 272 | { 273 | int imageIndex = _libundirect_dyldIndexForImageName(imageName); 274 | if(imageIndex == -1) 275 | { 276 | return NULL; 277 | } 278 | 279 | intptr_t baseAddr = _dyld_get_image_vmaddr_slide(imageIndex); 280 | struct mach_header_64* header = (struct mach_header_64*)_dyld_get_image_header(imageIndex); 281 | 282 | const struct segment_command_64* cmd; 283 | 284 | uintptr_t addr = (uintptr_t)(header + 1); 285 | uintptr_t endAddr = addr + header->sizeofcmds; 286 | 287 | for(int ci = 0; ci < header->ncmds && addr <= endAddr; ci++) 288 | { 289 | cmd = (typeof(cmd))addr; 290 | 291 | addr = addr + cmd->cmdsize; 292 | 293 | if(cmd->cmd != LC_SEGMENT_64 || strcmp(cmd->segname, "__TEXT")) 294 | { 295 | continue; 296 | } 297 | 298 | void* result = _libundirect_find_in_region(cmd->vmaddr + baseAddr, cmd->vmsize, bytesToSearch, byteMask, byteCount); 299 | 300 | if(options & OPTION_DO_NOT_SEEK_BACK) 301 | { 302 | return result; 303 | } 304 | 305 | if(result != NULL) 306 | { 307 | if(startByte) 308 | { 309 | void* backResult = libundirect_seek_back(result, startByte, seekbackMax); 310 | if(backResult) 311 | { 312 | return backResult; 313 | } 314 | else 315 | { 316 | return result; 317 | } 318 | } 319 | else 320 | { 321 | return result; 322 | } 323 | } 324 | } 325 | 326 | return NULL; 327 | } 328 | 329 | libundirect_EXPORT void* libundirect_find_with_options(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte, unsigned int seekbackMax, libundirect_find_options_t options) 330 | { 331 | return libundirect_find_with_options_and_mask(imageName, bytesToSearch, NULL, byteCount, startByte, seekbackMax, options); 332 | } 333 | 334 | libundirect_EXPORT void* libundirect_find(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte) 335 | { 336 | return libundirect_find_with_options(imageName, bytesToSearch, byteCount, startByte, 64, 0); 337 | } 338 | 339 | libundirect_EXPORT void* libundirect_dsc_find(NSString* imageName, Class _class, SEL selector) 340 | { 341 | NSString* symbol = _libundirect_getSelectorString(_class, selector); 342 | NSString* imagePath = nil; 343 | 344 | HBLogDebugWeak(@"searching dyldSharedCache for symbol: %@", symbol); 345 | 346 | int imageIndex = -1; 347 | if(imageName) 348 | { 349 | imageIndex = _libundirect_dyldIndexForImageName(imageName); 350 | } 351 | 352 | if(imageIndex != -1) 353 | { 354 | const char *name = _dyld_get_image_name(imageIndex); 355 | imagePath = [NSString stringWithUTF8String:name]; 356 | } 357 | 358 | MSImageRef image = NULL; 359 | if(imagePath) 360 | { 361 | image = MSGetImageByName(imagePath.UTF8String); 362 | } 363 | 364 | void *symbolPtr = MSFindSymbol(image, symbol.UTF8String); 365 | /*HBLogDebugWeak(@"libundirect found pointer %p for symbol %@", symbolPtr, symbol); 366 | Dl_info info; 367 | dladdr(symbolPtr, &info); 368 | HBLogDebugWeak(@"%p: %s + 0x%llX", symbolPtr, info.dli_fname, (uint64_t)symbolPtr - (uint64_t)info.dli_fbase);*/ 369 | return symbolPtr; 370 | } 371 | 372 | libundirect_EXPORT void libundirect_dsc_rebind(NSString* imageName, Class _class, SEL selector, const char* format) 373 | { 374 | void* ptr = libundirect_dsc_find(imageName, _class, selector); 375 | libundirect_rebind(ptr, _class, selector, format); 376 | } 377 | 378 | libundirect_EXPORT NSArray* libundirect_failedSelectors() 379 | { 380 | return [failedSelectors copy]; 381 | } 382 | 383 | libundirect_EXPORT void libundirect_startBatchHooks(void) 384 | { 385 | libundirect_batchHookEnabled = YES; 386 | libundirect_batchedHooks = [NSMutableArray new]; 387 | } 388 | 389 | libundirect_EXPORT void libundirect_applyBatchHooksAndAdditional(const struct LHFunctionHook* additionalHooks, NSUInteger additionalCount) 390 | { 391 | NSUInteger fullCount = additionalCount + libundirect_batchedHooks.count; 392 | 393 | if(fullCount) 394 | { 395 | struct LHFunctionHook* hooks = malloc(sizeof(struct LHFunctionHook) * fullCount); 396 | 397 | [libundirect_batchedHooks enumerateObjectsUsingBlock:^(NSValue* batchedHookValue, NSUInteger idx, BOOL* stop) 398 | { 399 | [batchedHookValue getValue:&hooks[idx]]; 400 | }]; 401 | 402 | for(NSUInteger i = 0; i < additionalCount; i++) 403 | { 404 | memcpy(&hooks[libundirect_batchedHooks.count+i], &additionalHooks[i], sizeof(struct LHFunctionHook)); 405 | } 406 | 407 | HCHookFunctions(hooks, fullCount); 408 | 409 | free(hooks); 410 | } 411 | 412 | libundirect_batchHookEnabled = NO; 413 | libundirect_batchedHooks = [NSMutableArray new]; 414 | } 415 | 416 | libundirect_EXPORT void libundirect_applyBatchHooks(void) 417 | { 418 | libundirect_applyBatchHooksAndAdditional(NULL, 0); 419 | } 420 | 421 | #else 422 | 423 | // Non 64 bit devices are so ancient that they probably don't need to use this library 424 | 425 | libundirect_EXPORT void libundirect_rebind(void* directPtr, Class _class, SEL selector, const char* format) 426 | { 427 | 428 | } 429 | 430 | libundirect_EXPORT void* libundirect_find(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte) 431 | { 432 | return NULL; 433 | } 434 | 435 | libundirect_EXPORT NSArray* libundirect_failedSelectors() 436 | { 437 | return nil; 438 | } 439 | 440 | libundirect_EXPORT void libundirect_startBatchHooks(void) { } 441 | 442 | libundirect_EXPORT void libundirect_applyBatchHooksAndAdditional(const struct LHFunctionHook* additionalHooks, NSUInteger additionalCount) { } 443 | 444 | libundirect_EXPORT void libundirect_applyBatchHooks(void) { } 445 | 446 | #endif -------------------------------------------------------------------------------- /libundirect_dynamic.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2022 Lars Fröder 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | #import 14 | #import "substrate.h" 15 | #import 16 | #import 17 | #define LIBUNDIRECT_PATH ROOT_PATH("/usr/lib/libundirect.dylib") 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | // dynamic header for when you don't want to link against libundirect 24 | // for documentation, check out the non-dynamic header 25 | 26 | typedef enum 27 | { 28 | OPTION_DO_NOT_SEEK_BACK = 1 << 0 29 | } libundirect_find_options_t; 30 | 31 | __attribute__((unused)) 32 | static void libundirect_MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old) 33 | { 34 | static void (*impl_libundirect_MSHookMessageEx)(Class, SEL, IMP, IMP *); 35 | if(!impl_libundirect_MSHookMessageEx) 36 | { 37 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 38 | impl_libundirect_MSHookMessageEx = (void (*)(Class, SEL, IMP, IMP *))dlsym(handle, "libundirect_MSHookMessageEx"); 39 | } 40 | if(impl_libundirect_MSHookMessageEx) 41 | { 42 | impl_libundirect_MSHookMessageEx(_class, message, hook, old); 43 | } 44 | else 45 | { 46 | MSHookMessageEx(_class, message, hook, old); 47 | } 48 | } 49 | 50 | __attribute__((unused)) 51 | static void libundirect_rebind(void* directPtr, Class _class, SEL selector, const char* format) 52 | { 53 | static void (*impl_libundirect_rebind)(void*, Class, SEL, const char*); 54 | if(!impl_libundirect_rebind) 55 | { 56 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 57 | impl_libundirect_rebind = (void (*)(void*, Class, SEL, const char*))dlsym(handle, "libundirect_rebind"); 58 | } 59 | if(impl_libundirect_rebind) 60 | { 61 | impl_libundirect_rebind(directPtr, _class, selector, format); 62 | } 63 | } 64 | 65 | __attribute__((unused)) 66 | static void* libundirect_seek_back(void* startPtr, unsigned char toByte, unsigned int maxSearch) 67 | { 68 | static void* (*impl_libundirect_seek_back)(void*, unsigned char, unsigned int); 69 | if(!impl_libundirect_seek_back) 70 | { 71 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 72 | impl_libundirect_seek_back = (void* (*)(void*, unsigned char, unsigned int))dlsym(handle, "libundirect_seek_back"); 73 | } 74 | if(impl_libundirect_seek_back) 75 | { 76 | return impl_libundirect_seek_back(startPtr, toByte, maxSearch); 77 | } 78 | return NULL; 79 | } 80 | 81 | __attribute__((unused)) 82 | static void* libundirect_find_with_options_and_mask(NSString* imageName, unsigned char* bytesToSearch, unsigned char* byteMask, size_t byteCount, unsigned char startByte, unsigned int seekbackMax, libundirect_find_options_t options) 83 | { 84 | static void* (*impl_libundirect_find_with_options_and_mask)(NSString*, unsigned char*, unsigned char*, size_t, unsigned char, unsigned int, libundirect_find_options_t); 85 | if(!impl_libundirect_find_with_options_and_mask) 86 | { 87 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 88 | impl_libundirect_find_with_options_and_mask = (void* (*)(NSString*, unsigned char*, unsigned char*, size_t, unsigned char, unsigned int, libundirect_find_options_t))dlsym(handle, "libundirect_find_with_options_and_mask"); 89 | } 90 | if(impl_libundirect_find_with_options_and_mask) 91 | { 92 | return impl_libundirect_find_with_options_and_mask(imageName, bytesToSearch, byteMask, byteCount, startByte, seekbackMax, options); 93 | } 94 | return NULL; 95 | } 96 | 97 | __attribute__((unused)) 98 | static void* libundirect_find_with_options(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte, unsigned int seekbackMax, libundirect_find_options_t options) 99 | { 100 | static void* (*impl_libundirect_find_with_options)(NSString*, unsigned char*, size_t, unsigned char, unsigned int, libundirect_find_options_t); 101 | if(!impl_libundirect_find_with_options) 102 | { 103 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 104 | impl_libundirect_find_with_options = (void* (*)(NSString*, unsigned char*, size_t, unsigned char, unsigned int, libundirect_find_options_t))dlsym(handle, "libundirect_find_with_options"); 105 | } 106 | if(impl_libundirect_find_with_options) 107 | { 108 | return impl_libundirect_find_with_options(imageName, bytesToSearch, byteCount, startByte, seekbackMax, options); 109 | } 110 | return NULL; 111 | } 112 | 113 | __attribute__((unused)) 114 | static void* libundirect_find(NSString* imageName, unsigned char* bytesToSearch, size_t byteCount, unsigned char startByte) 115 | { 116 | static void* (*impl_libundirect_find)(NSString*, unsigned char*, size_t, unsigned char); 117 | if(!impl_libundirect_find) 118 | { 119 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 120 | impl_libundirect_find = (void* (*)(NSString*, unsigned char*, size_t, unsigned char))dlsym(handle, "libundirect_find"); 121 | } 122 | if(impl_libundirect_find) 123 | { 124 | return impl_libundirect_find(imageName, bytesToSearch, byteCount, startByte); 125 | } 126 | return NULL; 127 | } 128 | 129 | __attribute__((unused)) 130 | static void* libundirect_dsc_find(NSString* imageName, Class _class, SEL selector) 131 | { 132 | static void* (*impl_libundirect_dsc_find)(NSString*, Class, SEL); 133 | if(!impl_libundirect_dsc_find) 134 | { 135 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 136 | impl_libundirect_dsc_find = (void* (*)(NSString*, Class, SEL))dlsym(handle, "libundirect_dsc_find"); 137 | } 138 | if(impl_libundirect_dsc_find) 139 | { 140 | return impl_libundirect_dsc_find(imageName, _class, selector); 141 | } 142 | return NULL; 143 | } 144 | 145 | __attribute__((unused)) 146 | static void libundirect_dsc_rebind(NSString* imageName, Class _class, SEL selector, const char* format) 147 | { 148 | static void (*impl_libundirect_dsc_rebind)(NSString*, Class, SEL, const char*); 149 | if(!impl_libundirect_dsc_rebind) 150 | { 151 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 152 | impl_libundirect_dsc_rebind = (void (*)(NSString*, Class, SEL, const char*))dlsym(handle, "libundirect_dsc_rebind"); 153 | } 154 | if(impl_libundirect_dsc_rebind) 155 | { 156 | return impl_libundirect_dsc_rebind(imageName, _class, selector, format); 157 | } 158 | } 159 | 160 | __attribute__((unused)) 161 | static NSArray* libundirect_failedSelectors() 162 | { 163 | static NSArray* (*impl_libundirect_failedSelectors)(); 164 | if(!impl_libundirect_failedSelectors) 165 | { 166 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 167 | impl_libundirect_failedSelectors = (NSArray* (*)(void))dlsym(handle, "libundirect_failedSelectors"); 168 | } 169 | if(impl_libundirect_failedSelectors) 170 | { 171 | return impl_libundirect_failedSelectors(); 172 | } 173 | return (NSArray*)nil; 174 | } 175 | 176 | __attribute__((unused)) 177 | static void libundirect_startBatchHooks(void) 178 | { 179 | static void (*impl_libundirect_startBatchHooks)(); 180 | if(!impl_libundirect_startBatchHooks) 181 | { 182 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 183 | impl_libundirect_startBatchHooks = (void (*)(void))dlsym(handle, "libundirect_startBatchHooks"); 184 | } 185 | if(impl_libundirect_startBatchHooks) 186 | { 187 | impl_libundirect_startBatchHooks(); 188 | } 189 | } 190 | 191 | __attribute__((unused)) 192 | static void libundirect_applyBatchHooksAndAdditional(const struct LHFunctionHook* additionalHooks, NSUInteger additionalCount) 193 | { 194 | static void (*impl_libundirect_applyBatchHooksAndAdditional)(const struct LHFunctionHook*, NSUInteger); 195 | if(!impl_libundirect_applyBatchHooksAndAdditional) 196 | { 197 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 198 | impl_libundirect_applyBatchHooksAndAdditional = (void (*)(const struct LHFunctionHook*, NSUInteger))dlsym(handle, "libundirect_applyBatchHooksAndAdditional"); 199 | } 200 | if(impl_libundirect_applyBatchHooksAndAdditional) 201 | { 202 | impl_libundirect_applyBatchHooksAndAdditional(additionalHooks, additionalCount); 203 | } 204 | } 205 | 206 | __attribute__((unused)) 207 | static void libundirect_applyBatchHooks() 208 | { 209 | static void (*impl_libundirect_applyBatchHooks)(); 210 | if(!impl_libundirect_applyBatchHooks) 211 | { 212 | void* handle = dlopen(LIBUNDIRECT_PATH, RTLD_LAZY); 213 | impl_libundirect_applyBatchHooks = (void (*)(void))dlsym(handle, "libundirect_failedSelectors"); 214 | } 215 | if(impl_libundirect_applyBatchHooks) 216 | { 217 | return impl_libundirect_applyBatchHooks(); 218 | } 219 | } 220 | 221 | #ifdef __cplusplus 222 | } 223 | #endif 224 | 225 | // macros to readd setters and getters for ivars, these can't be hooked as the application still calls the original direct getters and setters 226 | // mainly useful to get existing code to just work without having to change everything to use the ivar instead 227 | // can only be used from xmi files 228 | #define LIBUNDIRECT_CLASS_ADD_GETTER(classname, type, ivarname, gettername) %hook classname %new - (type)gettername { return [self valueForKey:[NSString stringWithUTF8String:#ivarname]]; } %end 229 | #define LIBUNDIRECT_CLASS_ADD_SETTER(classname, type, ivarname, settername) %hook classname %new - (void)settername:(type)toset { [self setValue:toset forKey:[NSString stringWithUTF8String:#ivarname]]; } %end -------------------------------------------------------------------------------- /libundirect_hookoverwrite.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | inline static void libundirect_MSHookMessageEx_wrapper(Class _class, SEL message, IMP hook, IMP *old) 5 | { 6 | libundirect_MSHookMessageEx(_class, message, hook, old); 7 | } 8 | #define MSHookMessageEx libundirect_MSHookMessageEx_wrapper 9 | #ifdef __cplusplus 10 | } 11 | #endif -------------------------------------------------------------------------------- /pac.h: -------------------------------------------------------------------------------- 1 | #ifndef PTRAUTH_HELPERS_H 2 | #define PTRAUTH_HELPERS_H 3 | // Helpers for PAC archs. 4 | 5 | // If the compiler understands __arm64e__, assume it's paired with an SDK that has 6 | // ptrauth.h. Otherwise, it'll probably error if we try to include it so don't. 7 | #if __arm64e__ 8 | #include 9 | #endif 10 | 11 | #pragma clang diagnostic push 12 | #pragma clang diagnostic ignored "-Wunused-function" 13 | 14 | // Given a pointer to instructions, sign it so you can call it like a normal fptr. 15 | static void *make_sym_callable(void *ptr) { 16 | #if __arm64e__ 17 | ptr = ptrauth_sign_unauthenticated(ptrauth_strip(ptr, ptrauth_key_function_pointer), ptrauth_key_function_pointer, 0); 18 | #endif 19 | return ptr; 20 | } 21 | 22 | // Given a function pointer, strip the PAC so you can read the instructions. 23 | static void *make_sym_readable(void *ptr) { 24 | #if __arm64e__ 25 | ptr = ptrauth_strip(ptr, ptrauth_key_function_pointer); 26 | #endif 27 | return ptr; 28 | } 29 | 30 | #pragma clang diagnostic pop 31 | #endif -------------------------------------------------------------------------------- /release_build.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | set -e 3 | 4 | export PREFIX=$THEOS/toolchain/Xcode11.xctoolchain/usr/bin/ 5 | 6 | make clean 7 | make package FINALPACKAGE=1 8 | 9 | export -n PREFIX 10 | 11 | make clean 12 | make package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless --------------------------------------------------------------------------------