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