├── dyldBypass ├── dummyFile ├── dyldBypass.h └── dyldBypass.m ├── locateTarget.xm └── README.md /dyldBypass/dummyFile: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dyldBypass/dyldBypass.h: -------------------------------------------------------------------------------- 1 | // 2 | // dyldBypass.h 3 | // 4 | // Created by Kyle Nicol on 9/20/19. 5 | // Copyright © 2019 Hydra. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface dyldBypass : NSObject 13 | +(void)init; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /locateTarget.xm: -------------------------------------------------------------------------------- 1 | // TRGoCPftF 2 | // Tweak to Identify location of pages in memory needed for dyld Integrity 3 | // bypass. 4 | 5 | // Compile to a dylib or use in a Theos Jailed Tweak and inject 6 | // Watch the Sys log and grab what you need 7 | 8 | #import 9 | #import 10 | #import 11 | 12 | #pragma mark original Imp pointers 13 | 14 | static ssize_t (*orig_read)(int, void*, size_t); 15 | 16 | ssize_t dis_read(int fildes, void *buf, size_t nbyte){ 17 | 18 | // Uncomment This if you want to inspect the size of all reads 19 | // If you add a hook and swap imp for open, you can log out what 20 | // image has the file descriptor passed here, ensure you're looking 21 | // at your target binary's image. 22 | 23 | /* NSLog(@"[getData] - file: %i\nsize: %lX",fildes,nbyte); */ 24 | 25 | if(nbyte==0x1000){ 26 | uint64_t data; 27 | ssize_t ret = 0x1000; 28 | orig_read(fildes,buf,nbyte); 29 | memcpy(&data,buf,sizeof(data)); 30 | NSLog(@"[getData] - Go Look For This in Binary => %llx",data); 31 | NSLog(@"[getData] - Don't forget its little endian so those chunks of bytes are backawards btw"); 32 | return ret; 33 | } 34 | return orig_read(fildes,buf,nbyte); 35 | } 36 | 37 | 38 | #pragma mark GetAllUpInThere 39 | %ctor { 40 | rebind_symbols((struct rebinding[1]){ 41 | {"read", (void *)dis_read, (void **)&orig_read} 42 | }, 1); 43 | } 44 | -------------------------------------------------------------------------------- /dyldBypass/dyldBypass.m: -------------------------------------------------------------------------------- 1 | // 2 | // dyldBypass.m 3 | // 4 | // Created by Kyle Nicol on 9/20/19. 5 | // Copyright © 2019 Hydra. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "dyldBypass.h" 10 | #import "fishhook.h" 11 | 12 | @implementation dyldBypass 13 | 14 | #pragma mark Shared Variable Declarations 15 | int swapLC = 0; 16 | int swapCS = 0; 17 | 18 | // UPDATE THIS 19 | //uint8_t lcData[0x4000] = {0xcf,0xfa, etc etc Your first 4 memory pages here as a byte array like this..., 0x4D}; 20 | // AND THIS 21 | //uint8_t csData[0x1000] = {0x6D,0x6B, ETC ETC Your Code Signature 1 memory page byte array here...,0x30}; 22 | 23 | #pragma mark Bypass Functions 24 | // Store orig imp to pointer 25 | static ssize_t (*orig_read)(int, void *, ssize_t); 26 | // Custom Replacement Function 27 | ssize_t hacked_read(int fildes, void *buf, ssize_t nbytes){ 28 | if (nbytes == 0x4000 && swapLC == 0){ 29 | ssize_t ret = 0x4000; 30 | void *origLC = &lcData; 31 | memcpy(buf, origLC, ret); 32 | swapLC += 1; 33 | return ret; 34 | } else if (nbytes == 0x1000 && swapCS == 0) { 35 | ssize_t ret = 0x1000; 36 | void *origCS = &csData; 37 | memcpy(buf, origCS, ret); 38 | swapCS += 1; 39 | return ret; 40 | } else { 41 | return orig_read(fildes, buf, nbytes); 42 | } 43 | } 44 | 45 | +(void)init{ 46 | NSLog(@"[dyldBypass] - Init dyldBypass"); 47 | 48 | rebind_symbols((struct rebinding[1]) { 49 | { "read", (void *)hacked_read, (void **)&orig_read } 50 | }, 1); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOSDyldIntegrityBypass 2 | Guide and Tweak on how to bypass applications compiled with Integrity checks. Designed for Jailed ios tweaks. 3 | 4 | Expected tools avialable: 5 | - Xcode 6 | - Theos and Theos-Jailed 7 | - Facebooks Fishhook library (Available on github) 8 | 9 | ## General Overview 10 | Applications compiled with iOS dynamic linker (Dyld) file integrity protection will crash on launch when working with a cracked and/or resigned binary. Since we require a decrypted binary to inject any tweaks, preparation on how to bypass these basic FIP protocols is critical for both Jailbreak and Jailed tweak developers. 11 | 12 | ### Identifying an application is crashing due to Dyld? 13 | You have a cracked/decrypted binary, you injected your tweak, but the application crashes on launch. Check out your devices crash log. If the crash log show's the main thread never makes it past `_dyld_start`, guess what!? Dyld is killing you!? 14 | 15 | ### What is Dyld doing? 16 | Thankfully the source for dyld, like much of Apple's work, is open and available via apple directly at either, https://github.com/opensource-apple or https://opensource.apple.com. 17 | This repo isn't to break down how dyld operates, but it is HIGHLY recommended you become familiar with the process flow for opening and executing a binary of iOS systems if you want to have a proper understanding of how it flags you, and why this bypass works. 18 | 19 | Pay attention to `ImageLoaderMachO.h` and trace back through dyld's source to get a good idea of what's going on. 20 | 21 | ### Let's watch what's happening. 22 | If you were to use fishhook or another method to rebind symbols on runtime, and monitored open(), read(), close() you will find 2 calls that should stand out. On the application i was investigating I received. 23 | 24 | 1) Open() called on original binary image 25 | 2) Read() for 4 Mem Pages (0x4000 Bytes on iOS 64 bit systems) 26 | 3) Read() for 1 Mem Page (0x1000 Bytes) 27 | 4) close() original binary image. 28 | 29 | This initial read pulled *the first* 4 memory pages from the binary. iOS could use this to validate each page based on a hash, and also now has the files header and all load commands. (Think: ensure the filesize, and location of target data is in alignment and valid) This includes checking the LC_ENCRYPTION_INFO_64 load command, to determine whether or not the file needs decrypted from Apple's FairPlay DRM as any official App store application would, and the LC_CODE_SIGNATURE_64 load command, which contains the offset and size of the binaries original code signature. 30 | 31 | Since we know there is the need to validate and abort if code signature is invalid, you may have already been able to guess where this 2nd read is focused. :eyes: 32 | 33 | The 2nd read() is located such that the end of this page worth of data includes the beginning of the binary's code signature to ensure that it is valid, else CrashIfInvalidCodeSignature() will throw a non-zero exit. 34 | 35 | Given that I could now see where the application was being validated, it's time to give dyld what it wants :wink: 36 | 37 | I provided the file locateTarget.xm tweak (Theos tweakfile type) to inject into your target IPA. The output will be the first 8 bytes of the 0x1000 byte read. Use this too look at your original stock binary from the appstore, and get the full 0x1000 unmodified hex data. 38 | Grab the original 0x4000 bytes from the original binary while your're already looking at it and now we can make a bypass. 39 | 40 | ## Crafting your bypass dylib. 41 | For my example, we know that: 42 | 1) A read of the first 4 pages (0x4000 Bytes) will be performed, and expects original unmodified binary's data. 43 | 2) A read of 1 page (0x1000 Bytes) will be performed, and expects again, original unmodified binary's data 44 | 45 | The basic bypass template is available in the bypassDyld folder of the repo. I generalized this into an objectic-c header and .m file so that it could be used with more than a Theos jailed project. 46 | 47 | The bypass is as simple as it sounds. 48 | 1) Hardcode the stock first 4 memory pages into a uint8_t byte array 49 | 2) Hardcode the memory page you found earlier into a uint8_t byte array. 50 | 3) Create a custom read() implementation, and declare a pointer func to hold the original implementation 51 | 4) In the custom read, if the size of the read is 0x4000 bytes, and you have no swapped them yet with a global counter, memcpy the original 4 memory pages into place when read() is invoked. The same logic is applied for the next 0x1000 Byte block, and the global counter upped, such that you don't interfere with any further read() calls of these sizes. If neither of the 2 cases are true, it returns the original implementation with original args. 52 | 5) Create an init call, where you invoke fishhooks symbol rebinding. 53 | To invoke the bypass, you simply need to invoke the Init call in a constructor block (%ctor in logos) and off you go. 54 | 55 | ### How to use it? 56 | However you want!? I've personally used a static framework template project in xcode with an additional buildphase to compile the project output into a dylib, and then inject that how i see fit. You could add a buildphase in that same project where you specify your target IPA, inject your dylib, and resign in a single step. 57 | 58 | The world is your oyster. 59 | 60 | Shout out to Jorg and The silly Canadaian who helped hold my hand as I learned enough C++ to read through source and implement this. <3 61 | --------------------------------------------------------------------------------