├── DyldDeNeuralyzer.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── xpn.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── DyldDeNeuralyzer ├── DyldDeNeuralyzer.entitlements ├── DyldPatch │ ├── dyldpatch.h │ └── dyldpatch.m ├── MachoLoader │ ├── queue.h │ ├── macholoader.h │ ├── queue.m │ └── macholoader.m └── main.m └── README.md /DyldDeNeuralyzer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer.xcodeproj/xcuserdata/xpn.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/DyldDeNeuralyzer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.disable-executable-page-protection 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/DyldPatch/dyldpatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // dyldpatch.h 3 | // DyldDeNeuralyzer 4 | // 5 | // Created by Adam Chester on 17/01/2023. 6 | // 7 | 8 | #ifndef dyldpatch_h 9 | #define dyldpatch_h 10 | #define FILENAME_SEARCH "/usr/lib/libffi-trampolines.dylib" 11 | 12 | void patchDyld(char *path); 13 | 14 | #endif /* dyldpatch_h */ 15 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/MachoLoader/queue.h: -------------------------------------------------------------------------------- 1 | // 2 | // queue.h 3 | // DyldDeNeuralyzer 4 | // 5 | // Created by Adam Chester on 17/01/2023. 6 | // 7 | 8 | #ifndef queue_h 9 | #define queue_h 10 | 11 | @interface NSPointerArray (QueueAdditions) 12 | - (void*) dequeue; 13 | - (void) enqueue:(void*)obj; 14 | @end 15 | 16 | #endif /* queue_h */ 17 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer.xcodeproj/xcuserdata/xpn.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DyldDeNeuralyzer.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/MachoLoader/macholoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // macholoader.h 3 | // DyldDeNeuralyzer 4 | // 5 | // Created by Adam Chester on 17/01/2023. 6 | // 7 | 8 | #ifndef macholoader_h 9 | #define macholoader_h 10 | 11 | typedef void entryfunc(void); 12 | 13 | struct section_info { 14 | uint64 addr; 15 | uint64 size; 16 | }; 17 | 18 | struct segment_info { 19 | uint64 addr; 20 | uint64 size; 21 | }; 22 | 23 | @interface MachoLoader :NSObject 24 | -(void) loadMachoBundle: (NSString *)filename; 25 | -(void) loadMachoBundleFromMemory: (NSData *)memory withEntryPoint: (NSString *)name; 26 | @end 27 | 28 | @interface Symbol :NSObject 29 | @property NSString *section; 30 | 31 | @end 32 | 33 | #endif /* macholoader_h */ 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Dyld-DeNeuralyzer 2 | 3 | A simple set of POCs to demonstrate in-memory loading of Mach-O's. 4 | 5 | * Method 1 - Patch up dyld for in-memory loading of Mach-O bundles. 6 | * Method 2 - Use a custom in-memory loader for loading Mach-O bundles. 7 | 8 | ## Usage 9 | 10 | ``` 11 | # For Method 1 12 | ./dylddeneuralyzer 1 macho_bundle_path 13 | 14 | # For Method 2 15 | ./dylddeneuralyzer 2 macho_bundle_path 16 | ``` 17 | 18 | ## Blog posts 19 | 20 | Restoring Dyld Memory Loading - [https://blog.xpnsec.com/restoring-dyld-memory-loading/](https://blog.xpnsec.com/restoring-dyld-memory-loading/) 21 | Building a Custom Mach-O Memory Loader for macOS - Part 1 - [https://blog.xpnsec.com/building-a-mach-o-memory-loader-part-1/](https://blog.xpnsec.com/building-a-mach-o-memory-loader-part-1/) -------------------------------------------------------------------------------- /DyldDeNeuralyzer/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "macholoader.h" 3 | #include "dyldpatch.h" 4 | 5 | int main(int argc, const char * argv[]) { 6 | @autoreleasepool { 7 | printf("Dyld-DeNeuralyzer POC.. by @_xpn_\n\n"); 8 | 9 | if (argc != 3) { 10 | printf("Usage: %s [METHOD] [BundlePath]\n", argv[0]); 11 | printf("Method 1 - Patch Dyld\n", argv[0]); 12 | printf("Method 2 - Custom Loader\n", argv[0]); 13 | return 2; 14 | } 15 | 16 | if (argv[1][0] == '1') { 17 | // POC 1 - Patch dyld 18 | patchDyld(argv[2]); 19 | } else { 20 | // POC 2 - Custom loader 21 | [[[MachoLoader alloc] init] loadMachoBundle:@(argv[2])]; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/MachoLoader/queue.m: -------------------------------------------------------------------------------- 1 | // 2 | // queue.m 3 | // DyldDeNeuralyzer 4 | // 5 | // Created by Adam Chester on 17/01/2023. 6 | // 7 | 8 | #import 9 | #include "queue.h" 10 | 11 | @implementation NSPointerArray (QueueAdditions) 12 | // Queues are first-in-first-out, so we remove objects from the head 13 | - (void*) dequeue { 14 | if ([self count] == 0) return (void*)0; // to avoid raising exception (Quinn) 15 | void* headObject = [self pointerAtIndex:0]; 16 | if (headObject != nil) { // so it isn't dealloc'ed on remove 17 | [self removePointerAtIndex:0]; 18 | } 19 | return headObject; 20 | } 21 | 22 | // Add to the tail of the queue (no one likes it when people cut in line!) 23 | - (void) enqueue:(void*)anObject { 24 | [self addPointer:anObject]; 25 | } 26 | @end 27 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/DyldPatch/dyldpatch.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dyldpatch.h" 12 | 13 | char *memoryLoadedFile = NULL; 14 | 15 | // ldr x8, value; br x8; value: .ascii "\x41\x42\x43\x44\x45\x46\x47\x48" 16 | char patch[] = {0x88,0x00,0x00,0x58,0x00,0x01,0x1f,0xd6,0x1f,0x20,0x03,0xd5,0x1f,0x20,0x03,0xd5,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41}; 17 | 18 | // Signatures to search for 19 | char mmapSig[] = {0xB0, 0x18, 0x80, 0xD2, 0x01, 0x10, 0x00, 0xD4}; 20 | char preadSig[] = {0x30, 0x13, 0x80, 0xD2, 0x01, 0x10, 0x00, 0xD4}; 21 | char fcntlSig[] = {0x90, 0x0B, 0x80, 0xD2, 0x01, 0x10, 0x00, 0xD4}; 22 | 23 | bool searchAndPatch(char *base, char *signature, int length, void *target) { 24 | 25 | char *patchAddr = NULL; 26 | kern_return_t kret; 27 | 28 | for(int i=0; i < 0x100000; i++) { 29 | if (base[i] == signature[0] && memcmp(base+i, signature, length) == 0) { 30 | patchAddr = base + i; 31 | break; 32 | } 33 | } 34 | 35 | if (patchAddr == NULL) { 36 | return FALSE; 37 | } 38 | 39 | kret = vm_protect(mach_task_self(), (vm_address_t)patchAddr, sizeof(patch), false, PROT_READ | PROT_WRITE | VM_PROT_COPY); 40 | if (kret != KERN_SUCCESS) { 41 | return FALSE; 42 | } 43 | 44 | memcpy(patchAddr, patch, sizeof(patch)); 45 | *(void **)((char*)patchAddr + 16) = target; 46 | 47 | kret = vm_protect(mach_task_self(), (vm_address_t)patchAddr, sizeof(patch), false, PROT_READ | PROT_EXEC); 48 | if (kret != KERN_SUCCESS) { 49 | return FALSE; 50 | } 51 | 52 | return TRUE; 53 | } 54 | 55 | const void* hookedMmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) { 56 | char *alloc; 57 | char filePath[PATH_MAX]; 58 | int newFlags; 59 | 60 | printf("[*] mmap Called: addr=%p len=%d prot=%x flags=%x fd=%d offset=%x\n", addr, len, prot, flags, fd, offset); 61 | 62 | memset(filePath, 0, sizeof(filePath)); 63 | 64 | // Check if the file is our "in-memory" file 65 | if (fcntl(fd, F_GETPATH, filePath) != -1) { 66 | if (strstr(filePath, FILENAME_SEARCH) > 0) { 67 | 68 | printf("[*] mmap fd %d is for [%s]\n", fd, filePath); 69 | printf("[*] Redirecting mmap with memory copy\n"); 70 | 71 | newFlags = MAP_PRIVATE | MAP_ANONYMOUS; 72 | if (addr != 0) { 73 | newFlags |= MAP_FIXED; 74 | } 75 | 76 | alloc = mmap(addr, len, PROT_READ | PROT_WRITE, newFlags, 0, 0); 77 | memcpy(alloc, memoryLoadedFile+offset, len); 78 | vm_protect(mach_task_self(), (vm_address_t)alloc, len, false, prot); 79 | return alloc; 80 | } 81 | } 82 | 83 | // If for another file, we pass through 84 | return mmap(addr, len, prot, flags, fd, offset); 85 | } 86 | 87 | ssize_t hookedPread(int fd, void *buf, size_t nbyte, int offset) { 88 | char filePath[PATH_MAX]; 89 | 90 | printf("[*] pread Called: fd=%d buf=%p nbyte=%x offset=%x\n", fd, buf, nbyte, offset); 91 | 92 | memset(filePath, 0, sizeof(filePath)); 93 | 94 | // Check if the file is our "in-memory" file 95 | if (fcntl(fd, F_GETPATH, filePath) != -1) { 96 | if (strstr(filePath, FILENAME_SEARCH) > 0) { 97 | 98 | printf("[*] pread fd %d is for [%s]\n", fd, filePath); 99 | printf("[*] Redirecting pread with memory copy\n"); 100 | 101 | memcpy(buf, memoryLoadedFile+offset, nbyte); 102 | return nbyte; 103 | } 104 | } 105 | 106 | // If for another file, we pass through 107 | return pread(fd, buf, nbyte, offset); 108 | } 109 | 110 | int hookedFcntl(int fildes, int cmd, void* param) { 111 | 112 | char filePath[PATH_MAX]; 113 | 114 | printf("[*] fcntl Called: fd=%d cmd=%x param=%p\n", fildes, cmd, param); 115 | 116 | memset(filePath, 0, sizeof(filePath)); 117 | 118 | // Check if the file is our "in-memory" file 119 | if (fcntl(fildes, F_GETPATH, filePath) != -1) { 120 | if (strstr(filePath, FILENAME_SEARCH) > 0) { 121 | 122 | printf("[*] fcntl fd %d is for [%s]\n", fildes, filePath); 123 | 124 | if (cmd == F_ADDFILESIGS_RETURN) { 125 | 126 | printf("[*] fcntl F_ADDFILESIGS_RETURN received, setting 0xFFFFFFFF\n"); 127 | 128 | fsignatures_t *fsig = (fsignatures_t*)param; 129 | 130 | // called to check that cert covers file.. so we'll make it cover everything ;) 131 | fsig->fs_file_start = 0xFFFFFFFF; 132 | return 0; 133 | } 134 | 135 | // Signature sanity check by dyld 136 | if (cmd == F_CHECK_LV) { 137 | 138 | printf("[*] fcntl F_CHECK_LV received, telling dyld everything is fine\n"); 139 | 140 | // Just say everything is fine 141 | return 0; 142 | } 143 | } 144 | } 145 | 146 | return fcntl(fildes, cmd, param); 147 | } 148 | 149 | void *getDyldBase(void) { 150 | struct task_dyld_info dyld_info; 151 | mach_vm_address_t image_infos; 152 | struct dyld_all_image_infos *infos; 153 | 154 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 155 | kern_return_t ret; 156 | 157 | ret = task_info(mach_task_self_, 158 | TASK_DYLD_INFO, 159 | (task_info_t)&dyld_info, 160 | &count); 161 | 162 | if (ret != KERN_SUCCESS) { 163 | return NULL; 164 | } 165 | 166 | image_infos = dyld_info.all_image_info_addr; 167 | 168 | infos = (struct dyld_all_image_infos *)image_infos; 169 | return infos->dyldImageLoadAddress; 170 | } 171 | 172 | int readFile(char *path, char **data) { 173 | int fd; 174 | struct stat st; 175 | int bytesRead; 176 | 177 | fd = open(path, O_RDONLY); 178 | if (fd < 0) { 179 | return 0; 180 | } 181 | 182 | fstat(fd, &st); 183 | *data = malloc(st.st_size); 184 | 185 | bytesRead = read(fd, *data, st.st_size); 186 | close(fd); 187 | return bytesRead; 188 | } 189 | 190 | void patchDyld(char *path) { 191 | char *dyldBase; 192 | int fd; 193 | int size; 194 | void (*function)(void); 195 | NSObjectFileImage fileImage; 196 | 197 | // Read in our dyld we want to memory load... obviously swap this in prod with memory, otherwise we've just recreated dlopen :/ 198 | size = readFile(path, &memoryLoadedFile); 199 | 200 | dyldBase = getDyldBase(); 201 | searchAndPatch(dyldBase, mmapSig, sizeof(mmapSig), hookedMmap); 202 | searchAndPatch(dyldBase, preadSig, sizeof(preadSig), hookedPread); 203 | searchAndPatch(dyldBase, fcntlSig, sizeof(fcntlSig), hookedFcntl); 204 | 205 | // // Set up blank content, same size as our Mach-O 206 | // char *fakeImage = (char *)malloc(size); 207 | // memset(fakeImage, 0x41, size); 208 | // 209 | // // Small hack to get around NSCreateObjectFileImageFromMemory validating our fake image 210 | // fileImage = (NSObjectFileImage)malloc(1024); 211 | // *(void **)(((char*)fileImage+0x8)) = fakeImage; 212 | // *(void **)(((char*)fileImage+0x10)) = size; 213 | // 214 | // void *module = NSLinkModule(fileImage, "test", NSLINKMODULE_OPTION_PRIVATE); 215 | // void *symbol = NSLookupSymbolInModule(module, "__Z5runmev"); 216 | // function = NSAddressOfSymbol(symbol); 217 | // function(); 218 | // 219 | void *lib = dlopen("/usr/lib/libffi-trampolines.dylib", RTLD_NOW); 220 | function = dlsym(lib, "_Z5runmev"); 221 | 222 | printf("[*] Invoking loaded function at %p... hold onto your butts....!!\n", function); 223 | function(); 224 | 225 | } 226 | 227 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C214FCAD2971B3B300C33274 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C214FCAC2971B3B300C33274 /* main.m */; }; 11 | C214FCB62976C3D500C33274 /* macholoader.m in Sources */ = {isa = PBXBuildFile; fileRef = C214FCB52976C3D500C33274 /* macholoader.m */; }; 12 | C214FCB92976C5C200C33274 /* queue.m in Sources */ = {isa = PBXBuildFile; fileRef = C214FCB82976C5C200C33274 /* queue.m */; }; 13 | C214FCBD2976C66100C33274 /* dyldpatch.m in Sources */ = {isa = PBXBuildFile; fileRef = C214FCBC2976C66100C33274 /* dyldpatch.m */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | C214FCA72971B3B300C33274 /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = /usr/share/man/man1/; 21 | dstSubfolderSpec = 0; 22 | files = ( 23 | ); 24 | runOnlyForDeploymentPostprocessing = 1; 25 | }; 26 | /* End PBXCopyFilesBuildPhase section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | C214FCA92971B3B300C33274 /* DyldDeNeuralyzer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = DyldDeNeuralyzer; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | C214FCAC2971B3B300C33274 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 31 | C214FCB32971B8F600C33274 /* DyldDeNeuralyzer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DyldDeNeuralyzer.entitlements; sourceTree = ""; }; 32 | C214FCB52976C3D500C33274 /* macholoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macholoader.m; sourceTree = ""; }; 33 | C214FCB72976C55500C33274 /* macholoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macholoader.h; sourceTree = ""; }; 34 | C214FCB82976C5C200C33274 /* queue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = queue.m; sourceTree = ""; }; 35 | C214FCBA2976C5DD00C33274 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = ""; }; 36 | C214FCBC2976C66100C33274 /* dyldpatch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = dyldpatch.m; sourceTree = ""; }; 37 | C214FCBE2976C69E00C33274 /* dyldpatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dyldpatch.h; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | C214FCA62971B3B300C33274 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | C214FCA02971B3B300C33274 = { 52 | isa = PBXGroup; 53 | children = ( 54 | C214FCAB2971B3B300C33274 /* DyldDeNeuralyzer */, 55 | C214FCAA2971B3B300C33274 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | C214FCAA2971B3B300C33274 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | C214FCA92971B3B300C33274 /* DyldDeNeuralyzer */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | C214FCAB2971B3B300C33274 /* DyldDeNeuralyzer */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | C214FCBB2976C63900C33274 /* DyldPatch */, 71 | C214FCB42976C3B700C33274 /* MachoLoader */, 72 | C214FCB32971B8F600C33274 /* DyldDeNeuralyzer.entitlements */, 73 | C214FCAC2971B3B300C33274 /* main.m */, 74 | ); 75 | path = DyldDeNeuralyzer; 76 | sourceTree = ""; 77 | }; 78 | C214FCB42976C3B700C33274 /* MachoLoader */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | C214FCB52976C3D500C33274 /* macholoader.m */, 82 | C214FCB72976C55500C33274 /* macholoader.h */, 83 | C214FCB82976C5C200C33274 /* queue.m */, 84 | C214FCBA2976C5DD00C33274 /* queue.h */, 85 | ); 86 | path = MachoLoader; 87 | sourceTree = ""; 88 | }; 89 | C214FCBB2976C63900C33274 /* DyldPatch */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | C214FCBC2976C66100C33274 /* dyldpatch.m */, 93 | C214FCBE2976C69E00C33274 /* dyldpatch.h */, 94 | ); 95 | path = DyldPatch; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | C214FCA82971B3B300C33274 /* DyldDeNeuralyzer */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = C214FCB02971B3B300C33274 /* Build configuration list for PBXNativeTarget "DyldDeNeuralyzer" */; 104 | buildPhases = ( 105 | C214FCA52971B3B300C33274 /* Sources */, 106 | C214FCA62971B3B300C33274 /* Frameworks */, 107 | C214FCA72971B3B300C33274 /* CopyFiles */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = DyldDeNeuralyzer; 114 | productName = DyldDeNeuralyzer; 115 | productReference = C214FCA92971B3B300C33274 /* DyldDeNeuralyzer */; 116 | productType = "com.apple.product-type.tool"; 117 | }; 118 | /* End PBXNativeTarget section */ 119 | 120 | /* Begin PBXProject section */ 121 | C214FCA12971B3B300C33274 /* Project object */ = { 122 | isa = PBXProject; 123 | attributes = { 124 | BuildIndependentTargetsInParallel = 1; 125 | LastUpgradeCheck = 1420; 126 | TargetAttributes = { 127 | C214FCA82971B3B300C33274 = { 128 | CreatedOnToolsVersion = 14.2; 129 | }; 130 | }; 131 | }; 132 | buildConfigurationList = C214FCA42971B3B300C33274 /* Build configuration list for PBXProject "DyldDeNeuralyzer" */; 133 | compatibilityVersion = "Xcode 14.0"; 134 | developmentRegion = en; 135 | hasScannedForEncodings = 0; 136 | knownRegions = ( 137 | en, 138 | Base, 139 | ); 140 | mainGroup = C214FCA02971B3B300C33274; 141 | productRefGroup = C214FCAA2971B3B300C33274 /* Products */; 142 | projectDirPath = ""; 143 | projectRoot = ""; 144 | targets = ( 145 | C214FCA82971B3B300C33274 /* DyldDeNeuralyzer */, 146 | ); 147 | }; 148 | /* End PBXProject section */ 149 | 150 | /* Begin PBXSourcesBuildPhase section */ 151 | C214FCA52971B3B300C33274 /* Sources */ = { 152 | isa = PBXSourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | C214FCB92976C5C200C33274 /* queue.m in Sources */, 156 | C214FCAD2971B3B300C33274 /* main.m in Sources */, 157 | C214FCB62976C3D500C33274 /* macholoader.m in Sources */, 158 | C214FCBD2976C66100C33274 /* dyldpatch.m in Sources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXSourcesBuildPhase section */ 163 | 164 | /* Begin XCBuildConfiguration section */ 165 | C214FCAE2971B3B300C33274 /* Debug */ = { 166 | isa = XCBuildConfiguration; 167 | buildSettings = { 168 | ALWAYS_SEARCH_USER_PATHS = NO; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_ENABLE_OBJC_WEAK = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INFINITE_RECURSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 190 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEBUG_INFORMATION_FORMAT = dwarf; 199 | ENABLE_STRICT_OBJC_MSGSEND = YES; 200 | ENABLE_TESTABILITY = YES; 201 | GCC_C_LANGUAGE_STANDARD = gnu11; 202 | GCC_DYNAMIC_NO_PIC = NO; 203 | GCC_NO_COMMON_BLOCKS = YES; 204 | GCC_OPTIMIZATION_LEVEL = 0; 205 | GCC_PREPROCESSOR_DEFINITIONS = ( 206 | "DEBUG=1", 207 | "$(inherited)", 208 | ); 209 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 210 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 211 | GCC_WARN_UNDECLARED_SELECTOR = YES; 212 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 213 | GCC_WARN_UNUSED_FUNCTION = YES; 214 | GCC_WARN_UNUSED_VARIABLE = YES; 215 | MACOSX_DEPLOYMENT_TARGET = 13.1; 216 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 217 | MTL_FAST_MATH = YES; 218 | ONLY_ACTIVE_ARCH = YES; 219 | SDKROOT = macosx; 220 | }; 221 | name = Debug; 222 | }; 223 | C214FCAF2971B3B300C33274 /* Release */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | CLANG_ANALYZER_NONNULL = YES; 228 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 230 | CLANG_ENABLE_MODULES = YES; 231 | CLANG_ENABLE_OBJC_ARC = YES; 232 | CLANG_ENABLE_OBJC_WEAK = YES; 233 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_COMMA = YES; 236 | CLANG_WARN_CONSTANT_CONVERSION = YES; 237 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 239 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 240 | CLANG_WARN_EMPTY_BODY = YES; 241 | CLANG_WARN_ENUM_CONVERSION = YES; 242 | CLANG_WARN_INFINITE_RECURSION = YES; 243 | CLANG_WARN_INT_CONVERSION = YES; 244 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 246 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 247 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 248 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 249 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 250 | CLANG_WARN_STRICT_PROTOTYPES = YES; 251 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 252 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 253 | CLANG_WARN_UNREACHABLE_CODE = YES; 254 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 255 | COPY_PHASE_STRIP = NO; 256 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 257 | ENABLE_NS_ASSERTIONS = NO; 258 | ENABLE_STRICT_OBJC_MSGSEND = YES; 259 | GCC_C_LANGUAGE_STANDARD = gnu11; 260 | GCC_NO_COMMON_BLOCKS = YES; 261 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 263 | GCC_WARN_UNDECLARED_SELECTOR = YES; 264 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 265 | GCC_WARN_UNUSED_FUNCTION = YES; 266 | GCC_WARN_UNUSED_VARIABLE = YES; 267 | MACOSX_DEPLOYMENT_TARGET = 13.1; 268 | MTL_ENABLE_DEBUG_INFO = NO; 269 | MTL_FAST_MATH = YES; 270 | SDKROOT = macosx; 271 | }; 272 | name = Release; 273 | }; 274 | C214FCB12971B3B300C33274 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | CODE_SIGN_ENTITLEMENTS = DyldDeNeuralyzer/DyldDeNeuralyzer.entitlements; 278 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 279 | CODE_SIGN_STYLE = Automatic; 280 | DEVELOPMENT_TEAM = 66GENWGHQ7; 281 | ENABLE_HARDENED_RUNTIME = YES; 282 | PRODUCT_NAME = "$(TARGET_NAME)"; 283 | }; 284 | name = Debug; 285 | }; 286 | C214FCB22971B3B300C33274 /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | CODE_SIGN_ENTITLEMENTS = DyldDeNeuralyzer/DyldDeNeuralyzer.entitlements; 290 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 291 | CODE_SIGN_STYLE = Automatic; 292 | DEVELOPMENT_TEAM = 66GENWGHQ7; 293 | ENABLE_HARDENED_RUNTIME = YES; 294 | PRODUCT_NAME = "$(TARGET_NAME)"; 295 | }; 296 | name = Release; 297 | }; 298 | /* End XCBuildConfiguration section */ 299 | 300 | /* Begin XCConfigurationList section */ 301 | C214FCA42971B3B300C33274 /* Build configuration list for PBXProject "DyldDeNeuralyzer" */ = { 302 | isa = XCConfigurationList; 303 | buildConfigurations = ( 304 | C214FCAE2971B3B300C33274 /* Debug */, 305 | C214FCAF2971B3B300C33274 /* Release */, 306 | ); 307 | defaultConfigurationIsVisible = 0; 308 | defaultConfigurationName = Release; 309 | }; 310 | C214FCB02971B3B300C33274 /* Build configuration list for PBXNativeTarget "DyldDeNeuralyzer" */ = { 311 | isa = XCConfigurationList; 312 | buildConfigurations = ( 313 | C214FCB12971B3B300C33274 /* Debug */, 314 | C214FCB22971B3B300C33274 /* Release */, 315 | ); 316 | defaultConfigurationIsVisible = 0; 317 | defaultConfigurationName = Release; 318 | }; 319 | /* End XCConfigurationList section */ 320 | }; 321 | rootObject = C214FCA12971B3B300C33274 /* Project object */; 322 | } 323 | -------------------------------------------------------------------------------- /DyldDeNeuralyzer/MachoLoader/macholoader.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "macholoader.h" 13 | #include "queue.h" 14 | 15 | #define VMADDR(x) (baseAlloc + x) 16 | 17 | @implementation MachoLoader 18 | 19 | // Base vmaddr we will load the mach-o into 20 | void *baseAlloc; 21 | void *indirectSymbols; 22 | 23 | NSPointerArray *segments; 24 | NSMutableDictionary *sections; 25 | NSPointerArray *commands; 26 | NSMutableArray *dylds; 27 | NSMutableDictionary *symbols; 28 | NSPointerArray *orderedSymbols; 29 | 30 | /// Store section information for lookup later 31 | - (void) storeSectionInfo: (const char *)name withStart: (int64_t)start andSize: (int64_t)size { 32 | struct section_info *info; 33 | char nameTruncated[17]; 34 | 35 | // Fix names which take up the full 16 bytes 36 | memset(nameTruncated, 0, sizeof(nameTruncated)); 37 | memcpy(nameTruncated, name, 16); 38 | 39 | info = (struct section_info *)malloc(sizeof(struct section_info)); 40 | info->size = size; 41 | info->addr = start; 42 | sections[@(nameTruncated)] = [NSValue valueWithPointer:info]; 43 | } 44 | 45 | /// Allows iteration of each section 46 | - (void) foreachSection:(void (^)(NSString *name, struct section_info *info)) sectionCallback { 47 | [sections enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) { 48 | sectionCallback(key, [obj pointerValue]); 49 | }]; 50 | } 51 | 52 | /// Process a section64 load command 53 | - (void) handleSection64: (struct section_64 *)section fromSegment: (struct segment_command_64 *)segment withBase: (unsigned char *)base { 54 | kern_return_t ret; 55 | void *sectionLoadAddr = 0; 56 | 57 | printf("\t[*] Section Name: %s\n", section->sectname); 58 | 59 | switch(section->flags & 0xFF) { 60 | case S_SYMBOL_STUBS: 61 | printf("\t[*] Section contains stubs\n"); 62 | break; 63 | } 64 | 65 | [self storeSectionInfo:section->sectname withStart:section->addr andSize:section->size]; 66 | 67 | sectionLoadAddr = VMADDR(section->addr); 68 | 69 | // Update the memory protection so we can copy over the data for this section 70 | ret = vm_protect(mach_task_self(), (vm_address_t)sectionLoadAddr, section->size, false, PROT_READ | PROT_WRITE); 71 | if (ret != 0) { 72 | printf("\t[!] Error during vm_protect: %d\n", ret); 73 | } 74 | 75 | memcpy(sectionLoadAddr, base + section->offset, section->size); 76 | 77 | // Reset memory protection to the segment 78 | ret = vm_protect(mach_task_self(), (vm_address_t)sectionLoadAddr, section->size, false, segment->initprot); 79 | if (ret != 0) { 80 | printf("\t[!] Error during vm_protect: %d\n", ret); 81 | } 82 | } 83 | 84 | /// Process a segment64 load command 85 | - (void) handleLoadCommandSegment64: (struct segment_command_64 *)segment withBase: (unsigned char *)base { 86 | kern_return_t ret; 87 | void *loadAddr = 0; 88 | struct section_64 *section_64; 89 | 90 | printf("\t[*] Segment Name: %s\n", segment->segname); 91 | printf("\t[*] Address: %llx\n", segment->vmaddr); 92 | printf("\t[*] Size: %llx\n", segment->vmsize); 93 | printf("\t[*] Number of sections: %d\n", segment->nsects); 94 | 95 | loadAddr = VMADDR(segment->vmaddr); 96 | 97 | memcpy(loadAddr, base + segment->fileoff, segment->filesize); 98 | 99 | ret = vm_protect(mach_task_self(), (vm_address_t)VMADDR(segment->vmaddr), segment->vmsize, false, segment->initprot); 100 | if (ret != 0) { 101 | printf("\t[!] Error during vm_protect: %d\n", ret); 102 | } 103 | 104 | printf("\t[*] Loaded segment to address: %p\n", loadAddr); 105 | 106 | // Now process each section appended to the segment 107 | section_64 = (struct section_64 *)((char *)segment + sizeof(struct segment_command_64)); 108 | 109 | for(int i=0; i < segment->nsects; i++) { 110 | [self handleSection64:§ion_64[i] fromSegment: segment withBase:base]; 111 | } 112 | } 113 | 114 | /// Process a dylib load command 115 | - (void) handleLoadCommandLoadDylib: (struct dylib_command *)dylib withBase: (unsigned char *)base { 116 | printf("\t[*] Dylib Path: %s\n", (char *)dylib + dylib->dylib.name.offset); 117 | 118 | char *dyldName = (char *)dylib + dylib->dylib.name.offset; 119 | 120 | [dylds addObject:@(dyldName)]; 121 | } 122 | 123 | /// Process a symtab load command 124 | - (void) handleLoadCommandSymTab: (struct symtab_command *)symtab withBase: (unsigned char *)base { 125 | char *stringTable; 126 | struct nlist_64 *nl; 127 | 128 | printf("\t[*] Number of symbols: %d\n", symtab->nsyms); 129 | 130 | stringTable = (char *)base + symtab->stroff; 131 | 132 | nl = (struct nlist_64 *)((char *)base + symtab->symoff); 133 | for(int i=0; i < symtab->nsyms; i++) { 134 | switch(nl[i].n_type) { 135 | case N_SECT: 136 | printf("\t[*] Section Index: %d\n", nl[i].n_sect); 137 | symbols[@(stringTable + nl[i].n_un.n_strx)] = [NSValue valueWithPointer:(void*)nl[i].n_value]; 138 | break; 139 | case N_EXT: 140 | printf("\t[*] External Symbol\n"); 141 | symbols[@(stringTable + nl[i].n_un.n_strx)] = [NSValue valueWithPointer:(void*)nl[i].n_value]; 142 | break; 143 | default: 144 | symbols[@(stringTable + nl[i].n_un.n_strx)] = [NSValue valueWithPointer:(void*)nl[i].n_value]; 145 | break; 146 | } 147 | [orderedSymbols addPointer:(void*)nl[i].n_value]; 148 | printf("\t[*] Symbol: %s\n", stringTable + nl[i].n_un.n_strx); 149 | } 150 | } 151 | 152 | /// Process a dysymtab load command 153 | - (void) handleLoadCommandDySymTab: (struct dysymtab_command *)dysymtab withBase: (unsigned char *)base { 154 | printf("\t[*] Number of local symbols: %d\n", dysymtab->nlocalsym); 155 | printf("\t[*] Number of indirect symbols: %d\n", dysymtab->nindirectsyms); 156 | indirectSymbols = (void*)malloc(sizeof(int) * dysymtab->nindirectsyms); 157 | memcpy(indirectSymbols, base + dysymtab->indirectsymoff,sizeof(int) * dysymtab->nindirectsyms); 158 | } 159 | 160 | /// Process the UUID load command 161 | - (void) handleLoadCommandUUID: (struct uuid_command *)uuid withBase: (unsigned char *)base { 162 | struct uuid_command *uuid_command; 163 | 164 | uuid_command = (struct uuid_command *)uuid; 165 | 166 | printf("\t[*] UUID: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", 167 | uuid_command->uuid[0], 168 | uuid_command->uuid[1], 169 | uuid_command->uuid[2], 170 | uuid_command->uuid[3], 171 | uuid_command->uuid[4], 172 | uuid_command->uuid[5], 173 | uuid_command->uuid[6], 174 | uuid_command->uuid[7], 175 | uuid_command->uuid[8], 176 | uuid_command->uuid[9], 177 | uuid_command->uuid[10], 178 | uuid_command->uuid[11], 179 | uuid_command->uuid[12], 180 | uuid_command->uuid[13], 181 | uuid_command->uuid[14], 182 | uuid_command->uuid[15] 183 | ); 184 | } 185 | 186 | /// Process chained fixups load command 187 | - (void) handleLoadCommandDyldChainedFixups: (struct linkedit_data_command *)linkedit withBase: (unsigned char *)base { 188 | 189 | void* lib = NULL; 190 | void* func = NULL; 191 | struct dyld_chained_fixups_header *fixupsHeader; 192 | struct dyld_chained_import *chainedImports; 193 | struct dyld_chained_starts_in_image *chainedStarts; 194 | NSPointerArray *imports; 195 | char *symbolNames = NULL; 196 | 197 | // Check if dyld load command has been processed yet, as this command is dependant on this 198 | if ([dylds count] == 0) { 199 | printf("\t[-] Requeing as no dylibs available\n"); 200 | [commands enqueue:linkedit]; 201 | return; 202 | } 203 | 204 | fixupsHeader = (struct dyld_chained_fixups_header *)((char *)base + linkedit->dataoff); 205 | 206 | printf("\t[*] Fixups Version: %d\n", fixupsHeader->fixups_version); 207 | printf("\t[*] Fixups Imports Count: %d\n", fixupsHeader->imports_count); 208 | printf("\t[*] Fixups Symbol strings offset: %d\n", fixupsHeader->symbols_offset); 209 | printf("\t[*] Fixups Chained Starts offset: %d\n", fixupsHeader->starts_offset); 210 | 211 | chainedImports = (struct dyld_chained_import *)((char *)fixupsHeader + fixupsHeader->imports_offset); 212 | chainedStarts = (struct dyld_chained_starts_in_image *)((char *)fixupsHeader + fixupsHeader->starts_offset); 213 | symbolNames = (char *)((char *)fixupsHeader + fixupsHeader->symbols_offset); 214 | 215 | switch (fixupsHeader->imports_format) { 216 | case DYLD_CHAINED_IMPORT: 217 | printf("\t[*] Fixups Format: DYLD_CHAINED_IMPORT\n"); 218 | break; 219 | case DYLD_CHAINED_IMPORT_ADDEND64: 220 | printf("\t[*] Fixups Format: DYLD_CHAINED_IMPORT_ADDEND64\n"); 221 | break; 222 | default: 223 | printf("\t[!] Unknown Fixups Format\n"); 224 | break; 225 | } 226 | 227 | imports = [[NSPointerArray alloc] initWithOptions: NSPointerFunctionsOpaqueMemory]; 228 | 229 | // First we need to gather a list of symbols that will be referenced later 230 | for(int i=0; i < fixupsHeader->imports_count; i++) { 231 | int ordinal = chainedImports[i].lib_ordinal; 232 | printf("\t[*] Symbol fixup string: %s\n", symbolNames + chainedImports[i].name_offset); 233 | 234 | if (ordinal == 253 || ordinal == 0) { 235 | // this-image 236 | printf("\t[*] Library name: this-image\n"); 237 | func = [symbols[@(symbolNames + chainedImports[i].name_offset)] pointerValue]; 238 | func = VMADDR((unsigned long long)func); 239 | } else { 240 | const char *dyldName = [dylds[ordinal-1] UTF8String]; 241 | printf("\t[*] Library name: %s\n", dyldName); 242 | lib = dlopen(dyldName, RTLD_NOW); 243 | if (lib == NULL) { 244 | printf("[!] Could not load dylib: %s\n", dyldName); 245 | } 246 | 247 | // Symbol imports start with _ which we need to remove before searching 248 | char *name = symbolNames + chainedImports[i].name_offset; 249 | if (name[0] == '_') { 250 | name += 1; 251 | } 252 | func = dlsym(lib, name); 253 | } 254 | 255 | if (func == NULL) { 256 | printf("\t[!] Cannot load dynamic library function!\n"); 257 | } 258 | 259 | // Build an ordered list for later reference 260 | [imports enqueue:func]; 261 | } 262 | 263 | // Now we can process chained fixups 264 | 265 | for(int i=0; i < chainedStarts->seg_count; i++) { 266 | printf("\t[*] Chained Start Offset: %d\n", chainedStarts->seg_info_offset[i]); 267 | 268 | // Not sure why, but we get `0` in here quite often which doesn't make sense. 269 | // Looking at dyld src, this is just ignored 270 | if (chainedStarts->seg_info_offset[i] == 0) { 271 | continue; 272 | } 273 | 274 | struct dyld_chained_starts_in_segment *chainedStartsSegment = (struct dyld_chained_starts_in_segment *)((char *)chainedStarts + chainedStarts->seg_info_offset[i]); 275 | printf("\t[*] Chained Start Segment Offset: %llx\n", chainedStartsSegment->segment_offset); 276 | printf("\t[*] Chained Start Page Size: %hx\n", chainedStartsSegment->page_size); 277 | printf("\t[*] Chained Start Page Count: %hx\n", chainedStartsSegment->page_count); 278 | printf("\t[*] Chained Start Size: %x\n", chainedStartsSegment->size); 279 | 280 | for(int j=0; j < chainedStartsSegment->page_count; j++) { 281 | if (chainedStartsSegment->page_start[j] == DYLD_CHAINED_PTR_START_NONE) { 282 | continue; 283 | } 284 | printf("\t[*] Chained Start Page Start: %d\n", (j * chainedStartsSegment->page_size) + chainedStartsSegment->page_start[j]); 285 | 286 | struct dyld_chained_ptr_64_rebase curRebase; 287 | struct dyld_chained_ptr_64_bind *bind, *prevBind, curBind; 288 | 289 | bind = (struct dyld_chained_ptr_64_bind *)VMADDR(chainedStartsSegment->segment_offset + (j * chainedStartsSegment->page_size) + chainedStartsSegment->page_start[j]); 290 | prevBind = NULL; 291 | 292 | while(bind != prevBind) { 293 | prevBind = bind; 294 | 295 | if (bind->bind == 1) { 296 | // This is a bind, so update from the imports we built and the ordinal 297 | memcpy(&curBind, bind, sizeof(struct dyld_chained_ptr_64_bind)); 298 | *(void **)bind = [imports pointerAtIndex:curBind.ordinal]; 299 | bind = (struct dyld_chained_ptr_64_bind *)((char *)bind + (4 * curBind.next)); 300 | } else { 301 | // This is a rebase 302 | memcpy(&curRebase, bind, sizeof(struct dyld_chained_ptr_64_rebase)); 303 | *(void **)bind = baseAlloc + curRebase.target; 304 | bind = (struct dyld_chained_ptr_64_bind *)((char *)bind + (4 * curRebase.next)); 305 | } 306 | } 307 | } 308 | } 309 | } 310 | 311 | /// Handles the loading of the provided load_command pointed at by memory 312 | -(void) processLoadCommand: (unsigned char *)loadCommandMem withBase: (unsigned char *)base { 313 | struct load_command *loadCommand; 314 | 315 | loadCommand = (struct load_command *)loadCommandMem; 316 | 317 | switch(loadCommand->cmd) { 318 | case LC_UUID: 319 | printf("[*] LC_UUID Load Command\n"); 320 | [self handleLoadCommandUUID: (struct uuid_command *)loadCommand withBase:base]; 321 | break; 322 | case LC_SEGMENT_64: 323 | printf("[*] LC_SEGMENT_64 Load Command\n"); 324 | [self handleLoadCommandSegment64: (struct segment_command_64 *)loadCommand withBase:base]; 325 | break; 326 | case LC_SYMTAB: 327 | printf("[*] LC_SYMTAB Load Command\n"); 328 | [self handleLoadCommandSymTab: (struct symtab_command *)loadCommand withBase:base]; 329 | break; 330 | case LC_DYSYMTAB: 331 | printf("[*] LC_DYSYMTAB Load Command\n"); 332 | [self handleLoadCommandDySymTab:(struct dysymtab_command *)loadCommand withBase:base]; 333 | break; 334 | case LC_LOAD_DYLIB: 335 | printf("[*] LC_LOAD_DYLIB Load Command\n"); 336 | [self handleLoadCommandLoadDylib: (struct dylib_command *)loadCommand withBase:base]; 337 | break; 338 | case LC_DYLD_CHAINED_FIXUPS: 339 | printf("[*] LC_DYLD_CHAINED_FIXUPS Load Command\n"); 340 | [self handleLoadCommandDyldChainedFixups:(struct linkedit_data_command *)loadCommand withBase:base]; 341 | break; 342 | case LC_THREAD: 343 | case LC_UNIXTHREAD: 344 | printf("[*] LC_THREAD Load Command\n"); 345 | break; 346 | case LC_ID_DYLIB: 347 | printf("[*] LC_ID_DYLIB Load Command\n"); 348 | break; 349 | case LC_PREBOUND_DYLIB: 350 | printf("[*] LC_PREBOUND_DYLIB Load Command\n"); 351 | break; 352 | case LC_LOAD_DYLINKER: 353 | printf("[*] LC_LOAD_DYLINKER Load Command\n"); 354 | break; 355 | case LC_ID_DYLINKER: 356 | printf("[*] LC_ID_DYLINKER Load Command\n"); 357 | break; 358 | case LC_ROUTINES_64: 359 | printf("[*] LC_ROUTINES_64 Load Command\n"); 360 | break; 361 | case LC_TWOLEVEL_HINTS: 362 | printf("[*] LC_TWOLEVEL_HINTS Load Command\n"); 363 | break; 364 | case LC_SUB_FRAMEWORK: 365 | printf("[*] LC_SUB_FRAMEWORK Load Command\n"); 366 | break; 367 | case LC_SUB_UMBRELLA: 368 | printf("[*] LC_SUB_UMBRELLA Load Command\n"); 369 | break; 370 | case LC_SUB_LIBRARY: 371 | printf("[*] LC_SUB_LIBRARY Load Command\n"); 372 | break; 373 | case LC_SUB_CLIENT: 374 | printf("[*] LC_SUB_CLIENT Load Command\n"); 375 | break; 376 | case LC_DYLD_EXPORTS_TRIE: 377 | printf("[*] LC_DYLD_EXPORTS_TRIE Load Command\n"); 378 | break; 379 | case LC_BUILD_VERSION: 380 | printf("[*] LC_BUILD_VERSION Load Command\n"); 381 | break; 382 | case LC_SOURCE_VERSION: 383 | printf("[*] LC_SOURCE_VERSION Load Command\n"); 384 | break; 385 | case LC_FUNCTION_STARTS: 386 | printf("[*] LC_FUNCTION_STARTS Load Command\n"); 387 | break; 388 | case LC_DATA_IN_CODE: 389 | printf("[*] LC_DATA_IN_CODE Load Command\n"); 390 | break; 391 | case LC_CODE_SIGNATURE: 392 | printf("[*] LC_CODE_SIGNATURE Load Command\n"); 393 | break; 394 | default: 395 | printf("[!] Unknown Load Command: %d\n", loadCommand->cmd); 396 | break; 397 | } 398 | } 399 | 400 | /// Calculate the amount of virtual memory we need to load the mach-o file 401 | -(uint64_t) calculateVirtualMemorySize: (void*)contents { 402 | struct mach_header_64 *header; 403 | struct load_command *load_command; 404 | struct segment_command_64 *segment_command; 405 | uint64_t maxAddr = 0; 406 | uint64_t maxLength = 0; 407 | 408 | header = (struct mach_header_64 *)contents; 409 | 410 | load_command = (struct load_command *)(contents + sizeof(struct mach_header_64)); 411 | 412 | for(int i=0; i < header->ncmds; i++) { 413 | if (load_command->cmd == LC_SEGMENT_64) { 414 | segment_command = (struct segment_command_64 *)(load_command); 415 | if (segment_command->vmaddr >= maxAddr + maxLength) { 416 | maxAddr = segment_command->vmaddr; 417 | maxLength = segment_command->vmsize; 418 | } 419 | load_command = (struct load_command *)((char *)load_command + load_command->cmdsize); 420 | } 421 | } 422 | 423 | return maxAddr + maxLength; 424 | } 425 | 426 | /// Load a macho bundle from disk into memory. 427 | /// Mainly used for testing (as we would just use dynamic loading if we wanted this) 428 | -(void) loadMachoBundle: (NSString *)filename { 429 | NSData *data = [NSData dataWithContentsOfFile:filename]; 430 | if (data == NULL) { 431 | printf("[!] Error: Could not open: %s\n", [filename UTF8String]); 432 | return; 433 | } 434 | 435 | [self loadMachoBundleFromMemory:data withEntryPoint:@"__Z5runmev"]; 436 | } 437 | 438 | /// Loads a macho bundle from memory into memory and calls the entry point symbol 439 | -(void) loadMachoBundleFromMemory: (NSData *)memory withEntryPoint: (NSString *)export { 440 | 441 | unsigned char *bytes; 442 | uint64_t maxVirtMemSize; 443 | struct mach_header_64 *header; 444 | struct load_command *load_command; 445 | entryfunc* entry; 446 | NSValue *exportAddr; 447 | 448 | sections = [[NSMutableDictionary alloc] init]; 449 | segments = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsOpaqueMemory]; 450 | commands = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsOpaqueMemory]; 451 | dylds = [[NSMutableArray alloc] init]; 452 | symbols = [[NSMutableDictionary alloc] init]; 453 | orderedSymbols = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsOpaqueMemory]; 454 | 455 | if (memory == NULL || export == NULL) { 456 | return; 457 | } 458 | 459 | bytes = (unsigned char *)[memory bytes]; 460 | if (bytes == NULL) { 461 | return; 462 | } 463 | 464 | header = (struct mach_header_64 *)bytes; 465 | 466 | if (header->magic != MH_MAGIC_64) { 467 | printf("[!] Invalid MAGIC Header value\n"); 468 | return; 469 | } 470 | 471 | if (header->cputype != CPU_TYPE_ARM64) { 472 | printf("[!] Invalid CPU_TYPE\n"); 473 | return; 474 | } 475 | 476 | if (header->filetype != MH_BUNDLE) { 477 | printf("[!] Not a bundle MACHO file\n"); 478 | return; 479 | } 480 | 481 | // Need to work out how much memory we need here 482 | maxVirtMemSize = [self calculateVirtualMemorySize: bytes]; 483 | assert(maxVirtMemSize != 0); 484 | 485 | vm_allocate(mach_task_self(), (vm_address_t*)&baseAlloc, maxVirtMemSize, VM_FLAGS_ANYWHERE); 486 | if (baseAlloc == NULL) { 487 | printf("[!] Error allocating %llx bytes of memory\n", maxVirtMemSize); 488 | return; 489 | } 490 | printf("[*] Loading into base address: %p\n", baseAlloc); 491 | 492 | printf("[*] %d Load Commands\n", header->ncmds); 493 | load_command = (struct load_command *)(bytes + sizeof(struct mach_header_64)); 494 | 495 | for(int i=0; i < header->ncmds; i++) { 496 | [commands enqueue:load_command]; 497 | load_command = (struct load_command *)((char *)load_command + load_command->cmdsize); 498 | } 499 | 500 | // Main processing loop until all commands are clear 501 | // This gives us the ability to re-queue commands if they need to wait on other commands 502 | for(struct load_command *command = [commands dequeue]; command != NULL; command = [commands dequeue]) { 503 | [self processLoadCommand:(unsigned char*)command withBase:bytes]; 504 | } 505 | 506 | // Now do the objective-c initialization (Coming in Part 2...) 507 | 508 | 509 | // Loaded, now jump to the symbol 510 | exportAddr = symbols[export];; 511 | if (exportAddr == NULL) { 512 | printf("[!] Could not find symbol %s for entry\n", [export UTF8String]); 513 | return; 514 | } 515 | 516 | entry = (entryfunc*)[exportAddr pointerValue]; 517 | entry = (entryfunc*)((char *)entry + (unsigned long long)baseAlloc); 518 | 519 | // Hold onto your butts... 520 | printf("==== HOLD ONTO YOUR BUTTS ====\n"); 521 | 522 | int ret = vm_protect(mach_task_self(), (vm_address_t)baseAlloc, maxVirtMemSize, true, PROT_READ | PROT_EXEC); 523 | if (ret != 0) { 524 | printf("\t[!] Error during vm_protect: %d\n", ret); 525 | } 526 | 527 | entry(); 528 | } 529 | 530 | @end 531 | 532 | --------------------------------------------------------------------------------