From b90cf7edd4060f1535ba3a5dfff2944ff3723abb Mon Sep 17 00:00:00 2001 From: yan <82288425+yandevelop@users.noreply.github.com> Date: Wed, 7 Feb 2024 19:32:09 +0100 Subject: [PATCH] 1.3.6 - Fixed an issue where BeReal would crash when sideloaded on jailed devices - Fixed a bug that caused BeReal to crash when attempting to delete a BeReal - Resolved a problem where using comment section or realmoji picker was not possible. --- Makefile | 3 +- SideloadFix/SideloadedFixes.h | 7 + SideloadFix/SideloadedFixes.mm | 104 +++++++ Tweak/Bea.h | 6 +- Tweak/Bea.x | 11 +- .../BeaInfoViewController.h | 2 +- control | 2 +- fishhook/CODE_OF_CONDUCT.md | 2 + fishhook/CONTRIBUTING.md | 31 ++ fishhook/LICENSE | 22 ++ fishhook/README.md | 75 +++++ fishhook/fishhook.c | 264 ++++++++++++++++++ fishhook/fishhook.h | 76 +++++ fishhook/fishhook.podspec | 13 + 14 files changed, 613 insertions(+), 5 deletions(-) create mode 100644 SideloadFix/SideloadedFixes.h create mode 100644 SideloadFix/SideloadedFixes.mm create mode 100644 fishhook/CODE_OF_CONDUCT.md create mode 100644 fishhook/CONTRIBUTING.md create mode 100644 fishhook/LICENSE create mode 100644 fishhook/README.md create mode 100644 fishhook/fishhook.c create mode 100644 fishhook/fishhook.h create mode 100644 fishhook/fishhook.podspec diff --git a/Makefile b/Makefile index ac74ddf..81e02d6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ TARGET := iphone:clang:latest:14.0 INSTALL_TARGET_PROCESSES = BeReal ARCHS = arm64 arm64e -PACKAGE_VERSION = 1.3.5 +PACKAGE_VERSION = 1.3.6 include $(THEOS)/makefiles/common.mk @@ -12,6 +12,7 @@ $(TWEAK_NAME)_CFLAGS = -fobjc-arc $(TWEAK_NAME)_FRAMEWORKS = UIKit MapKit ifeq ($(JAILED), 1) +$(TWEAK_NAME)_FILES += fishhook/fishhook.c $(TWEAK_NAME)_CFLAGS += -D JAILED=1 endif diff --git a/SideloadFix/SideloadedFixes.h b/SideloadFix/SideloadedFixes.h new file mode 100644 index 0000000..4e2ddd1 --- /dev/null +++ b/SideloadFix/SideloadedFixes.h @@ -0,0 +1,7 @@ +#import +#import "fishhook/fishhook.h" +#import + +@interface NSFileManager (SideloadedFixes) +- (NSURL*)swizzled_containerURLForSecurityApplicationGroupIdentifier:(NSString*)groupIdentifier; +@end \ No newline at end of file diff --git a/SideloadFix/SideloadedFixes.mm b/SideloadFix/SideloadedFixes.mm new file mode 100644 index 0000000..1b047a0 --- /dev/null +++ b/SideloadFix/SideloadedFixes.mm @@ -0,0 +1,104 @@ +#import "SideloadedFixes.h" + +// All credits go to https://github.com/level3tjg/RedditSideloadFix and https://github.com/opa334/IGSideloadFix + +NSString* keychainAccessGroup; +NSURL* fakeGroupContainerURL; + +void createDirectoryIfNotExists(NSURL* URL) { + if (![URL checkResourceIsReachableAndReturnError:nil]) { + [[NSFileManager defaultManager] createDirectoryAtURL:URL withIntermediateDirectories:YES attributes:nil error:nil]; + } +} + +@implementation NSFileManager (SideloadedFixes) + +- (NSURL*)swizzled_containerURLForSecurityApplicationGroupIdentifier:(NSString*)groupIdentifier { + NSURL* fakeURL = [fakeGroupContainerURL URLByAppendingPathComponent:groupIdentifier]; + + createDirectoryIfNotExists(fakeURL); + createDirectoryIfNotExists([fakeURL URLByAppendingPathComponent:@"Library"]); + createDirectoryIfNotExists([fakeURL URLByAppendingPathComponent:@"Library/Caches"]); + + return fakeURL; +} + +@end + +static void loadKeychainAccessGroup() { + NSDictionary* dummyItem = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : @"dummyItem", + (__bridge id)kSecAttrService : @"dummyService", + (__bridge id)kSecReturnAttributes : @YES, + }; + + CFTypeRef result; + OSStatus ret = SecItemCopyMatching((__bridge CFDictionaryRef)dummyItem, &result); + if (ret == -25300) { + ret = SecItemAdd((__bridge CFDictionaryRef)dummyItem, &result); + } + + if (ret == 0 && result) { + NSDictionary* resultDict = (__bridge id)result; + keychainAccessGroup = resultDict[(__bridge id)kSecAttrAccessGroup]; + NSLog(@"loaded keychainAccessGroup: %@", keychainAccessGroup); + } +} + +static OSStatus (*orig_SecItemAdd)(CFDictionaryRef, CFTypeRef*); +static OSStatus hook_SecItemAdd(CFDictionaryRef attributes, CFTypeRef* result) { + if (CFDictionaryContainsKey(attributes, kSecAttrAccessGroup)) { + CFMutableDictionaryRef mutableAttributes = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, attributes); + CFDictionarySetValue(mutableAttributes, kSecAttrAccessGroup, (__bridge void*)keychainAccessGroup); + attributes = CFDictionaryCreateCopy(kCFAllocatorDefault, mutableAttributes); + } + return orig_SecItemAdd(attributes, result); +} + +static OSStatus (*orig_SecItemCopyMatching)(CFDictionaryRef, CFTypeRef*); +static OSStatus hook_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef* result) { + if (CFDictionaryContainsKey(query, kSecAttrAccessGroup)) { + CFMutableDictionaryRef mutableQuery = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, query); + CFDictionarySetValue(mutableQuery, kSecAttrAccessGroup, (__bridge void*)keychainAccessGroup); + query = CFDictionaryCreateCopy(kCFAllocatorDefault, mutableQuery); + } + return orig_SecItemCopyMatching(query, result); +} + +static OSStatus (*orig_SecItemUpdate)(CFDictionaryRef, CFDictionaryRef); +static OSStatus hook_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { + if (CFDictionaryContainsKey(query, kSecAttrAccessGroup)) { + CFMutableDictionaryRef mutableQuery = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, query); + CFDictionarySetValue(mutableQuery, kSecAttrAccessGroup, (__bridge void*)keychainAccessGroup); + query = CFDictionaryCreateCopy(kCFAllocatorDefault, mutableQuery); + } + return orig_SecItemUpdate(query, attributesToUpdate); +} + +static OSStatus (*orig_SecItemDelete)(CFDictionaryRef); +static OSStatus hook_SecItemDelete(CFDictionaryRef query) { + if (CFDictionaryContainsKey(query, kSecAttrAccessGroup)) { + CFMutableDictionaryRef mutableQuery = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, query); + CFDictionarySetValue(mutableQuery, kSecAttrAccessGroup, (__bridge void*)keychainAccessGroup); + query = CFDictionaryCreateCopy(kCFAllocatorDefault, mutableQuery); + } + return orig_SecItemDelete(query); +} + +static void initSideloadedFixes() { + fakeGroupContainerURL = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/FakeGroupContainers"] isDirectory:YES]; + loadKeychainAccessGroup(); + rebind_symbols( + (struct rebinding[]){ + {"SecItemAdd", (void*)hook_SecItemAdd, (void**)&orig_SecItemAdd}, + {"SecItemCopyMatching", (void*)hook_SecItemCopyMatching, + (void**)&orig_SecItemCopyMatching}, + {"SecItemUpdate", (void*)hook_SecItemUpdate, (void**)&orig_SecItemUpdate}, + {"SecItemDelete", (void*)hook_SecItemDelete, (void**)&orig_SecItemDelete}, + }, + 4); + Method originalMethod = class_getInstanceMethod([NSFileManager class], @selector(containerURLForSecurityApplicationGroupIdentifier:)); + Method swizzledMethod = class_getInstanceMethod([NSFileManager class], @selector(swizzled_containerURLForSecurityApplicationGroupIdentifier:)); + method_exchangeImplementations(originalMethod, swizzledMethod); +} \ No newline at end of file diff --git a/Tweak/Bea.h b/Tweak/Bea.h index aae7be4..f74c318 100644 --- a/Tweak/Bea.h +++ b/Tweak/Bea.h @@ -25,4 +25,8 @@ NSDictionary *headers; @end @interface UIHostingView : UIView -@end \ No newline at end of file +@end + +#ifdef JAILED +#import "SideloadFix/SideloadedFixes.mm" +#endif \ No newline at end of file diff --git a/Tweak/Bea.x b/Tweak/Bea.x index 72e9723..3a2e770 100644 --- a/Tweak/Bea.x +++ b/Tweak/Bea.x @@ -21,6 +21,7 @@ [self setDownloadButton:downloadButton]; [self addSubview:downloadButton]; + [NSLayoutConstraint activateConstraints:@[ [[[self downloadButton] trailingAnchor] constraintEqualToAnchor:[self trailingAnchor] constant:-11.6], [[[self downloadButton] bottomAnchor] constraintEqualToAnchor:[self topAnchor] constant:47.333] @@ -56,8 +57,11 @@ %hook UIAlertController - (void)viewWillAppear:(BOOL)arg1 { %orig; + // return early here because otherwise the app will crash on other alert controllers + // trying to access the 2nd index of the actions array which is (probably) not present + if (isUnblurred) return; - if ([self.actions[2].title isEqual:@"👀 Unblur"] && !isUnblurred) { + if ([self.actions[2].title isEqual:@"👀 Unblur"]) { self.view.superview.hidden = YES; UIAlertAction *thirdAction = self.actions[2]; id block = [thirdAction valueForKey:@"_handler"]; @@ -170,6 +174,7 @@ %hook UIHostingView -(void)setUserInteractionEnabled:(BOOL)arg1 { + if (isUnblurred) return %orig(arg1); %orig(YES); } @@ -207,4 +212,8 @@ SettingsViewController = objc_getClass("BeReal.SettingsViewController"), UIHostingView = objc_getClass("_TtC7SwiftUIP33_A34643117F00277B93DEBAB70EC0697116_UIInheritedView"), HomeViewController = objc_getClass("BeReal.HomeViewController")); + + #ifdef JAILED + initSideloadedFixes(); + #endif } \ No newline at end of file diff --git a/Utilities/BeFake/ViewControllers/InfoViewController/BeaInfoViewController.h b/Utilities/BeFake/ViewControllers/InfoViewController/BeaInfoViewController.h index 6fa41e8..b22a68a 100644 --- a/Utilities/BeFake/ViewControllers/InfoViewController/BeaInfoViewController.h +++ b/Utilities/BeFake/ViewControllers/InfoViewController/BeaInfoViewController.h @@ -6,4 +6,4 @@ @property (nonatomic, strong) UILabel *versionLabel; @end -#define TWEAK_VERSION @"1.3.5" \ No newline at end of file +#define TWEAK_VERSION @"1.3.6" \ No newline at end of file diff --git a/control b/control index 731884c..6af09ed 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: com.yan.bea Name: Bea -Version: 1.3.5 +Version: 1.3.6 Architecture: iphoneos-arm Description: Lightweight BeReal. enhancement tweak. Maintainer: yan diff --git a/fishhook/CODE_OF_CONDUCT.md b/fishhook/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ac27d8a --- /dev/null +++ b/fishhook/CODE_OF_CONDUCT.md @@ -0,0 +1,2 @@ +# Code of Conduct +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated. \ No newline at end of file diff --git a/fishhook/CONTRIBUTING.md b/fishhook/CONTRIBUTING.md new file mode 100644 index 0000000..444fe37 --- /dev/null +++ b/fishhook/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to fishhook +We want to make contributing to this project as easy and transparent as +possible. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## License +By contributing to fishhook, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. \ No newline at end of file diff --git a/fishhook/LICENSE b/fishhook/LICENSE new file mode 100644 index 0000000..c45bb7c --- /dev/null +++ b/fishhook/LICENSE @@ -0,0 +1,22 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/fishhook/README.md b/fishhook/README.md new file mode 100644 index 0000000..1374fba --- /dev/null +++ b/fishhook/README.md @@ -0,0 +1,75 @@ +# fishhook + +__fishhook__ is a very simple library that enables dynamically rebinding symbols in Mach-O binaries running on iOS in the simulator and on device. This provides functionality that is similar to using [`DYLD_INTERPOSE`][interpose] on OS X. At Facebook, we've found it useful as a way to hook calls in libSystem for debugging/tracing purposes (for example, auditing for double-close issues with file descriptors). + +[interpose]: http://opensource.apple.com/source/dyld/dyld-210.2.3/include/mach-o/dyld-interposing.h "" + +## Usage + +Once you add `fishhook.h`/`fishhook.c` to your project, you can rebind symbols as follows: +```Objective-C +#import + +#import + +#import "AppDelegate.h" +#import "fishhook.h" + +static int (*orig_close)(int); +static int (*orig_open)(const char *, int, ...); + +int my_close(int fd) { + printf("Calling real close(%d)\n", fd); + return orig_close(fd); +} + +int my_open(const char *path, int oflag, ...) { + va_list ap = {0}; + mode_t mode = 0; + + if ((oflag & O_CREAT) != 0) { + // mode only applies to O_CREAT + va_start(ap, oflag); + mode = va_arg(ap, int); + va_end(ap); + printf("Calling real open('%s', %d, %d)\n", path, oflag, mode); + return orig_open(path, oflag, mode); + } else { + printf("Calling real open('%s', %d)\n", path, oflag); + return orig_open(path, oflag, mode); + } +} + +int main(int argc, char * argv[]) +{ + @autoreleasepool { + rebind_symbols((struct rebinding[2]){{"close", my_close, (void *)&orig_close}, {"open", my_open, (void *)&orig_open}}, 2); + + // Open our own binary and print out first 4 bytes (which is the same + // for all Mach-O binaries on a given architecture) + int fd = open(argv[0], O_RDONLY); + uint32_t magic_number = 0; + read(fd, &magic_number, 4); + printf("Mach-O Magic Number: %x \n", magic_number); + close(fd); + + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} +``` +### Sample output +``` +Calling real open('/var/mobile/Applications/161DA598-5B83-41F5-8A44-675491AF6A2C/Test.app/Test', 0) +Mach-O Magic Number: feedface +Calling real close(3) +... +``` + +## How it works + +`dyld` binds lazy and non-lazy symbols by updating pointers in particular sections of the `__DATA` segment of a Mach-O binary. __fishhook__ re-binds these symbols by determining the locations to update for each of the symbol names passed to `rebind_symbols` and then writing out the corresponding replacements. + +For a given image, the `__DATA` segment may contain two sections that are relevant for dynamic symbol bindings: `__nl_symbol_ptr` and `__la_symbol_ptr`. `__nl_symbol_ptr` is an array of pointers to non-lazily bound data (these are bound at the time a library is loaded) and `__la_symbol_ptr` is an array of pointers to imported functions that is generally filled by a routine called `dyld_stub_binder` during the first call to that symbol (it's also possible to tell `dyld` to bind these at launch). In order to find the name of the symbol that corresponds to a particular location in one of these sections, we have to jump through several layers of indirection. For the two relevant sections, the section headers (`struct section`s from ``) provide an offset (in the `reserved1` field) into what is known as the indirect symbol table. The indirect symbol table, which is located in the `__LINKEDIT` segment of the binary, is just an array of indexes into the symbol table (also in `__LINKEDIT`) whose order is identical to that of the pointers in the non-lazy and lazy symbol sections. So, given `struct section nl_symbol_ptr`, the corresponding index in the symbol table of the first address in that section is `indirect_symbol_table[nl_symbol_ptr->reserved1]`. The symbol table itself is an array of `struct nlist`s (see ``), and each `nlist` contains an index into the string table in `__LINKEDIT` which where the actual symbol names are stored. So, for each pointer `__nl_symbol_ptr` and `__la_symbol_ptr`, we are able to find the corresponding symbol and then the corresponding string to compare against the requested symbol names, and if there is a match, we replace the pointer in the section with the replacement. + +The process of looking up the name of a given entry in the lazy or non-lazy pointer tables looks like this: +![Visual explanation](http://i.imgur.com/HVXqHCz.png) \ No newline at end of file diff --git a/fishhook/fishhook.c b/fishhook/fishhook.c new file mode 100644 index 0000000..fb41e8e --- /dev/null +++ b/fishhook/fishhook.c @@ -0,0 +1,264 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "fishhook.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +#ifndef SEG_DATA_CONST +#define SEG_DATA_CONST "__DATA_CONST" +#endif + +struct rebindings_entry { + struct rebinding *rebindings; + size_t rebindings_nel; + struct rebindings_entry *next; +}; + +static struct rebindings_entry *_rebindings_head; + +static int prepend_rebindings(struct rebindings_entry **rebindings_head, + struct rebinding rebindings[], + size_t nel) { + struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); + if (!new_entry) { + return -1; + } + new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); + if (!new_entry->rebindings) { + free(new_entry); + return -1; + } + memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); + new_entry->rebindings_nel = nel; + new_entry->next = *rebindings_head; + *rebindings_head = new_entry; + return 0; +} + +#if 0 +static int get_protection(void *addr, vm_prot_t *prot, vm_prot_t *max_prot) { + mach_port_t task = mach_task_self(); + vm_size_t size = 0; + vm_address_t address = (vm_address_t)addr; + memory_object_name_t object; +#ifdef __LP64__ + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + vm_region_basic_info_data_64_t info; + kern_return_t info_ret = vm_region_64( + task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object); +#else + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; + vm_region_basic_info_data_t info; + kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object); +#endif + if (info_ret == KERN_SUCCESS) { + if (prot != NULL) + *prot = info.protection; + + if (max_prot != NULL) + *max_prot = info.max_protection; + + return 0; + } + + return -1; +} +#endif + +static void perform_rebinding_with_section(struct rebindings_entry *rebindings, + section_t *section, + intptr_t slide, + nlist_t *symtab, + char *strtab, + uint32_t *indirect_symtab) { + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1]; + struct rebindings_entry *cur = rebindings; + while (cur) { + for (uint j = 0; j < cur->rebindings_nel; j++) { + if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { + kern_return_t err; + + if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) + *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; + + /** + * 1. Moved the vm protection modifying codes to here to reduce the + * changing scope. + * 2. Adding VM_PROT_WRITE mode unconditionally because vm_region + * API on some iOS/Mac reports mismatch vm protection attributes. + * -- Lianfu Hao Jun 16th, 2021 + **/ + err = vm_protect (mach_task_self (), (uintptr_t)indirect_symbol_bindings, section->size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (err == KERN_SUCCESS) { + /** + * Once we failed to change the vm protection, we + * MUST NOT continue the following write actions! + * iOS 15 has corrected the const segments prot. + * -- Lionfore Hao Jun 11th, 2021 + **/ + indirect_symbol_bindings[i] = cur->rebindings[j].replacement; + } + goto symbol_loop; + } + } + cur = cur->next; + } + symbol_loop:; + } +} + +static void rebind_symbols_for_image(struct rebindings_entry *rebindings, + const struct mach_header *header, + intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && + strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _rebind_symbols_for_image(const struct mach_header *header, + intptr_t slide) { + rebind_symbols_for_image(_rebindings_head, header, slide); +} + +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel) { + struct rebindings_entry *rebindings_head = NULL; + int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); + rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); + if (rebindings_head) { + free(rebindings_head->rebindings); + } + free(rebindings_head); + return retval; +} + +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { + int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); + if (retval < 0) { + return retval; + } + // If this was the first call, register callback for image additions (which is also invoked for + // existing images, otherwise, just run on existing images + if (!_rebindings_head->next) { + _dyld_register_func_for_add_image(_rebind_symbols_for_image); + } else { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } + } + return retval; +} diff --git a/fishhook/fishhook.h b/fishhook/fishhook.h new file mode 100644 index 0000000..0d8e36a --- /dev/null +++ b/fishhook/fishhook.h @@ -0,0 +1,76 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef fishhook_h +#define fishhook_h + +#include +#include + +#if !defined(FISHHOOK_EXPORT) +#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) +#else +#define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +/* + * A structure representing a particular intended rebinding from a symbol + * name to its replacement + */ +struct rebinding { + const char *name; + void *replacement; + void **replaced; +}; + +/* + * For each rebinding in rebindings, rebinds references to external, indirect + * symbols with the specified name to instead point at replacement for each + * image in the calling process as well as for all future images that are loaded + * by the process. If rebind_functions is called more than once, the symbols to + * rebind are added to the existing list of rebindings, and if a given symbol + * is rebound more than once, the later rebinding will take precedence. + */ +FISHHOOK_VISIBILITY +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); + +/* + * Rebinds as above, but only in the specified image. The header should point + * to the mach-o header, the slide should be the slide offset. Others as above. + */ +FISHHOOK_VISIBILITY +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif //fishhook_h + diff --git a/fishhook/fishhook.podspec b/fishhook/fishhook.podspec new file mode 100644 index 0000000..b46b01a --- /dev/null +++ b/fishhook/fishhook.podspec @@ -0,0 +1,13 @@ +Pod::Spec.new do |spec| + spec.name = "fishhook" + spec.version = "0.2" + spec.license = { :type => "BSD", :file => "LICENSE" } + spec.homepage = 'https://github.com/facebook/fishhook' + spec.author = { "Facebook, Inc." => "https://github.com/facebook" } + spec.summary = "A library that enables dynamically rebinding symbols in Mach-O binaries running on iOS." + spec.source = { :git => "https://github.com/facebook/fishhook.git", :tag => '0.2'} + spec.source_files = "fishhook.{h,c}" + spec.social_media_url = 'https://twitter.com/fbOpenSource' + + spec.ios.deployment_target = '6.0' +end