├── .github └── workflows │ └── build-deb.yml ├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── analysis ├── README.md └── main_fullmap.m ├── control.template ├── entitlements.plist ├── flexwrapper.cpp ├── foulmain.cpp ├── foulwrapper.m ├── include └── MobileContainerManager │ ├── MCMContainer.h │ └── MCMContainerManager.h ├── layout └── DEBIAN │ └── control ├── link_theos.sh └── main.cpp /.github/workflows/build-deb.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | #tags: 6 | # - 'v*' 7 | 8 | #branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | runs-on: macos-latest 13 | strategy: 14 | matrix: 15 | provider: [TFP0, LIBKRW, LIBKERNRW] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | 22 | - name: Prepare Theos 23 | uses: Randomblock1/theos-action@v1 24 | 25 | - name: Build package 26 | run: | 27 | rm -f packages/* 28 | cp control.template control 29 | if [[ ${{matrix.provider}} == TFP0 ]]; then 30 | USE_TFP0=1 make package FINALPACKAGE=1 31 | elif [[ ${{matrix.provider}} == LIBKRW ]]; then 32 | sed -i '' 's/{{.depends}}/libkrw/g' control 33 | USE_LIBKRW=1 make package FINALPACKAGE=1 34 | elif [[ ${{matrix.provider}} == LIBKERNRW ]]; then 35 | sed -i '' 's/{{.depends}}/libkernrw0/g' control 36 | USE_LIBKERNRW=1 make package FINALPACKAGE=1 37 | fi 38 | - name: Publish artifact 39 | uses: actions/upload-artifact@v2 40 | with: 41 | name: fouldecrypt-${{matrix.provider}} 42 | path: ${{ github.workspace }}/packages/*.deb 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .theos 2 | packages 3 | other's proj 4 | priv_include 5 | CMakeLists.txt 6 | compile_commands.json 7 | /.idea/ 8 | /.cache/ 9 | /cmake-build-*/ 10 | 11 | ### macOS ### 12 | # General 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | 17 | # Icon must end with two \r 18 | Icon 19 | 20 | 21 | # Thumbnails 22 | ._* 23 | 24 | # Files that might appear in the root of a volume 25 | .DocumentRevisions-V100 26 | .fseventsd 27 | .Spotlight-V100 28 | .TemporaryItems 29 | .Trashes 30 | .VolumeIcon.icns 31 | .com.apple.timemachine.donotpresent 32 | 33 | # Directories potentially created on remote AFP share 34 | .AppleDB 35 | .AppleDesktop 36 | Network Trash Folder 37 | Temporary Items 38 | .apdisk -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "kerninfra"] 2 | path = kerninfra 3 | url = https://github.com/NyaMisty/KernInfra 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := iphone:clang:14.5:13.0 2 | ARCHS = arm64 arm64e 3 | export ADDITIONAL_CFLAGS = -DTHEOS_LEAN_AND_MEAN -fobjc-arc 4 | 5 | include $(THEOS)/makefiles/common.mk 6 | 7 | TOOL_NAME = fouldecrypt flexdecrypt2 foulwrapper 8 | 9 | # export USE_TFP0 = 1 10 | export USE_LIBKRW = 1 11 | # export USE_LIBKERNRW = 1 12 | 13 | fouldecrypt_FILES = main.cpp foulmain.cpp 14 | fouldecrypt_CFLAGS = -fobjc-arc -Wno-unused-variable # -Ipriv_include 15 | fouldecrypt_CCFLAGS = $(fouldecrypt_CFLAGS) 16 | fouldecrypt_CODESIGN_FLAGS = -Sentitlements.plist 17 | fouldecrypt_INSTALL_PATH = /usr/local/bin 18 | fouldecrypt_SUBPROJECTS = kerninfra 19 | fouldecrypt_LDFLAGS += -Lkerninfra/libs 20 | fouldecrypt_CCFLAGS += -std=c++2a 21 | 22 | flexdecrypt2_FILES = main.cpp flexwrapper.cpp 23 | flexdecrypt2_CFLAGS = -fobjc-arc -Wno-unused-variable # -Ipriv_include 24 | flexdecrypt2_CCFLAGS = $(flexdecrypt2_CFLAGS) 25 | flexdecrypt2_CODESIGN_FLAGS = -Sentitlements.plist 26 | flexdecrypt2_INSTALL_PATH = /usr/local/bin 27 | flexdecrypt2_SUBPROJECTS = kerninfra 28 | flexdecrypt2_LDFLAGS += -Lkerninfra/libs 29 | flexdecrypt2_CCFLAGS += -std=c++2a 30 | 31 | foulwrapper_FILES = foulwrapper.m 32 | foulwrapper_CFLAGS = -fobjc-arc -Wno-unused-variable -Iinclude 33 | foulwrapper_CCFLAGS = $(foulwrapper_CFLAGS) 34 | foulwrapper_CODESIGN_FLAGS = -Sentitlements.plist 35 | foulwrapper_INSTALL_PATH = /usr/local/bin 36 | foulwrapper_FRAMEWORKS = Foundation MobileCoreServices 37 | foulwrapper_PRIVATE_FRAMEWORKS = MobileContainerManager 38 | 39 | include $(THEOS_MAKE_PATH)/tool.mk 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FoulDecrypt 2 | 3 | It's also available in my Cydia repo: http://repo.misty.moe. FoulDecrypt supports iOS 13.5 and later, and has been tested on iOS 14.2, 14.3 and 13.5 (both arm64 and arm64e). 4 | 5 | Note: for unsupported versions, it has chances to panic the device, beware ;) 6 | 7 | ## Why FoulDecrypt 8 | 9 | ### 1. Fully static 10 | 11 | Thanks to FlexDecrypt and FoulPlay we know there's a mremap_encrypted syscall, although AAPL already released full source code for this syscall now. 12 | 13 | However, neither of them can actually get mremap_encrypted to work. That's because mremap_encrypted cannot accept non-aligned address, making it useless for most iOS 14 apps. 14 | 15 | I managed to fix with kernel read/writing, so now we can achieve clutch's armv7+arm64 multi-arch decryption again in 2021! 16 | 17 | ### 2. Simplicity 18 | 19 | FlexDecrypt's source code is pretty FAT, bundling the whole swift runtime to just achieve a simple mremap_encrypted. 20 | 21 | And at the same time, foulplay independently found the same approach, and implemented it in a much more simple way. 22 | 23 | I recompiled the foulplay for iOS, and a wrapper `flexdecrypt2` for flexdecrypt. 24 | 25 | ## How to use 26 | 27 | Install the correct version: 28 | - `fouldecrypt-TFP0` for < iOS 14 29 | - `fouldecrypt-LIBKRW` if you are running Unc0ver 30 | - `fouldecrypt-LIBKERNRW` if you are running Taurine 31 | 32 | Run `fouldecrypt` on an encrypted binary. 33 | 34 | ## About `foulwrapper` 35 | 36 | `foulwrapper` will find all Mach-Os in a specific application and decrypt them using `fouldecrypt`: 37 | 38 | `usage: foulwrapper (application name or bundle identifier)` 39 | 40 | ## Credits 41 | @meme: foulplay 42 | @JohnCoates: flexdecrypt 43 | -------------------------------------------------------------------------------- /analysis/README.md: -------------------------------------------------------------------------------- 1 | # Analysis note 2 | 3 | ## What have I done 4 | 5 | I reverse engineered the following componenets: 6 | 7 | 1. dyld itself 8 | 2. libSystem.B.dylib 9 | 3. iOS Kernel itself 10 | 11 | 12 | ## What's the problem 13 | 14 | In fact flexdecrypt is just shooting his own foot. FlexDecrypt is too FAT, I just can't understand why an approach as simple as foulplay's original code would have to write so much code. 15 | 16 | In case you don't know, FlexDecrypt simulated the dyld, mapping all segments into the place, and then rebuilded the whole MachO, although the only place needs to be changed is the encrypted ares (which would always have only one). 17 | 18 | What's worse, his rebuild logic has bug, which makes a slightest malform MachO killing the whole decrypting process. 19 | 20 | And what's the most confusing is that FlexDecrypt will automatically switch to posix_spawn approach when cryptoff is not 16K-aligned, without outputing any logs. 21 | 22 | 23 | ## TL;DR 24 | 25 | 1. mremap_encrypted has always been broken, flexdecrypt only use it when the cryptoff is aligned, or it silently switches to posix_spawn approach. And in fact, dyld is already not using it to map the framework. 26 | 2. The reason you can's simply pass the mapped page into mremap_encrypted is because it requires the input address to be page-aligned (16K aligned). 27 | 3. but mmap also require the input file offset to be page-aligned (16K). so if we don't patch something, we will never successfully remap and decrypt a unaligned crypt area. 28 | 4. by patching the apple_protect_pager setup by mremap_encrypted, we can fix the mremap_encrypted, and then achieve our goal. 29 | 30 | N. NEVER manually rebuild a file: in fact it's totally useless to simulate the dyld, because dyld is actually just simply mapping that segment, and call the decryption. it's even more useless to manually rebuild the whole MachO 31 | 32 | ## How I solved it 33 | 34 | 1. I started by adapting @meme's foulplay to iOS, which failed at unprotect()'s mmap. And that's actually because the size is not page-aligned. I quickly figured it out and fixed, but then the mremap_encrypted failed with "Invalid argument", and there the nightmare began. 35 | 2. I thought it's because dyld has some special call before hand, so I throroughly reverse engineered dyld's mapping process, and found it would append HUGETLB flag and uses fcntl to do speculative read. However that doesnt's work at all. 36 | 3. I wanted to debug the dyld, but it's pretty hard, especially on iOS. Then I remembered that the kdv can prints out all bsd syscall, so I recompiled it and adapted it to arm64e, but I found nothing but many calls to mremap_encrypted with cryptid==0, which is apparently uselss. 37 | 4. Then I found now the MachO mapping is now directly handled by kernel, without going through the dyld. So instead I reviewed the mremap_encrypted's source code, and found out that it should return either OK or EPERM, unless a series of precondition failed. 38 | 5. Interestingly, those preconditions are actually almost the same as proc_pidinfo's PROC_PIDREGIONPATHINFO2, so I manually forged a call to it, and magically, the call succeed. And after comparing two functions, the only differences, is acturally mremap_encrypted checked if the input address are page-aligned, which it doesn't before. 39 | 6. then I thought I successfully adapted the foulplay for iOS. But then I found it's not outputting correct decrypted data. And now the nightmare just begins. 40 | 7. as we can see, the workflow is simply mmap -> mremap_encrypted -> memcpy. The problem is how to introduce a 16K-unaligned offset. I proposed three ways: 41 | - force the mmap to map the file with a 4K aligned address 42 | - force the mremap_encrypted to decrypt the file with a 4K aligned base 43 | - mmap & mremap_encrypted, but use mach_vm_remap to move a 4K-aligned address to 16K-aligned address 44 | 45 | 8. In order to force map a 4K-aligned file offset, we need to change our process's vm_map into a 4K one, which can be achieved by setting page_shift from 14 to 12. It's also possible to change page's vm_map_entry, and replace vme_object's offset. 46 | 9. I first tried to forced mmap to map 0x1000 offset of file, sometimes it succeeded (but gives wrong output), sometimes it hangs, sometimes it panic the device. 47 | 10. after examining the panic report, I thought it's pmap_cs_associate's check caused the problem, and I found it's controlled by vnode's ubc_info->cs_blobs. so I removed csblobs and tried again, still no luck. 48 | 11. actually I combined various options and tested hundreds of cases, you can have a look at the trace in the `find_working` branch: 49 | - mmap related: mmap aligned offset, mmap unaligned offset with patching page_shift, mmap unaligned offset with vme_offset patch 50 | - mremap related: whether to clear cs_blobs, whether to call it with patching page_shift, whether to call it with aligned address 51 | - magic: whether to read the mapped crypt area first, whether to map the crypt area one more time, whether to map segment like dyld 52 | 12. after 4 days of experiment, I realized it's not possible to directly tamper with the page size. The reason is in the kernel most places are hardcoded with a pagesize of 16K, so even if I bypassed the mmap check, bypassed the pmap_cs check, I'll still be killed by checks happening in real read ops, which will check physcial address and virtual address's aligness against hardcoded pagesize. And that means in no way can I really map a 4K pages with some simple patch. 53 | 13. Then I carefully examined mremap_encrypted's implementation. Its main logic is: 54 | - find area's corresponding vnode 55 | - find each vnode pages within the range 56 | - identify crypt_start/crypt_end (the start and end offset in page that actually need to be decrypted (e.g. you want to decrypt 0x5000-0x6000, then you are actually decrypting page 0x4000's 0x1000-0x2000) ), crypto_backing_offset (the offset used to init the cipher) 57 | - finally setup the decrypt pager for that page. 58 | 14. So I realized I can actually mremap_encrypted using aligned address, and then patch the decrypt pager to cover only part of them. And the correct setup can be seen from set_code_unprotec() function. With this approach I finally successfully decrypt the page and got the right output. -------------------------------------------------------------------------------- /analysis/main_fullmap.m: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define PRIVATE 1 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #define MAP_HUGETLB 0x40000 24 | 25 | extern "C" int mremap_encrypted(void*, size_t, uint32_t, uint32_t, uint32_t); 26 | 27 | static int 28 | unprotect(vm_address_t cryptaddr, struct encryption_info_command_64 *info) 29 | { 30 | vm_address_t cryptaddr_align = cryptaddr & ~0x3fff; 31 | /* 32 | struct proc_regionwithpathinfo rwpi; 33 | int buf_used = proc_pidinfo(getpid(), PROC_PIDREGIONPATHINFO2, cryptaddr_align, &rwpi, sizeof(rwpi)); 34 | if (buf_used <= 0) { 35 | perror("proc_pidinfo(unprotect)"); 36 | } 37 | printf("proc_pidinfo: ret %d, rwpi \n", buf_used);*/ 38 | int error = mremap_encrypted((void *)cryptaddr_align, info->cryptsize + (cryptaddr - cryptaddr_align), info->cryptid, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL); 39 | if (error) { 40 | perror("mremap_encrypted(unprotect)"); 41 | printf("cryptaddr: %p, cryptsize: 0x%x\n", (void *)cryptaddr, info->cryptsize); 42 | //printf("cryptaddr: %p, cryptsize: 0x%x, cryptdata: %p\n", (void *)cryptaddr, info->cryptsize, *(void **)cryptaddr); 43 | //munmap(base, info->cryptsize); 44 | return 1; 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | static vm_address_t dylib_map(const char *inputFile, void *base) { // returns slide 51 | int f = open(inputFile, O_RDONLY); 52 | struct stat s; 53 | if (fstat(f, &s) < 0) { 54 | perror("fstat(map)"); 55 | close(f); 56 | return 0; 57 | } 58 | 59 | int fcntlarg[3] = { 0 }; 60 | fcntlarg[0] = 0; 61 | fcntlarg[1] = 0; 62 | fcntlarg[2] = s.st_size; 63 | fcntl(f, F_SPECULATIVE_READ, fcntlarg); 64 | 65 | struct mach_header_64* header = (struct mach_header_64*) base; 66 | assert(header->magic == MH_MAGIC_64); 67 | assert(header->cputype == CPU_TYPE_ARM64); 68 | assert(header->cpusubtype == CPU_SUBTYPE_ARM64_ALL); 69 | 70 | // Enumerate all load commands and check for the encryption header, if found 71 | // start "unprotect"'ing the contents. 72 | // 73 | vm_address_t minAddr = (vm_address_t)-1; 74 | vm_address_t maxAddr = 0; 75 | 76 | for (uint32_t offset = sizeof(struct mach_header_64), i = 0; i < header->ncmds; i++) { 77 | struct load_command* command = (struct load_command*) (base + offset); 78 | offset += command->cmdsize; 79 | 80 | if (command->cmd == LC_SEGMENT_64) { 81 | struct segment_command_64 *seg = 82 | (struct segment_command_64*) command; 83 | printf("Got seg %s\n", seg->segname); 84 | if (!seg->vmaddr || !seg->filesize) continue; 85 | if (seg->vmaddr < minAddr) { 86 | minAddr = seg->vmaddr; 87 | } 88 | if (seg->vmaddr + seg->vmsize > maxAddr) { 89 | maxAddr = seg->vmaddr + seg->vmsize; 90 | } 91 | } 92 | } 93 | printf("maxAddr: %p, minAddr: %p\n", (void *)maxAddr, (void *)minAddr); 94 | size_t vmRangeSize = maxAddr - minAddr; 95 | 96 | vm_address_t allocAddr = 0; 97 | kern_return_t err = vm_allocate(mach_task_self(), &allocAddr, vmRangeSize, VM_FLAGS_ANYWHERE | (VM_MEMORY_DYLIB << 24)); 98 | if (err) { 99 | perror("vm_allocate(map)"); 100 | close(f); 101 | return 0; 102 | } 103 | vm_address_t dybase = (vm_address_t)mmap((void *)allocAddr, vmRangeSize, VM_PROT_READ, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, 0, 0); 104 | vm_address_t slide = dybase - minAddr; 105 | for (uint32_t offset = sizeof(struct mach_header_64), i = 0; i < header->ncmds; i++) { 106 | struct load_command* command = (struct load_command*) (base + offset); 107 | offset += command->cmdsize; 108 | 109 | if (command->cmd == LC_SEGMENT_64) { 110 | struct segment_command_64 *seg = 111 | (struct segment_command_64*) command; 112 | if (!seg->vmaddr || !seg->filesize) continue; 113 | printf("mmaping seg %s, addr %p, prot %d, size 0x%llx\n", seg->segname, (void *)(seg->vmaddr + slide), seg->initprot, seg->filesize); 114 | if (mmap((void *)(seg->vmaddr + slide), seg->filesize, seg->initprot, MAP_PRIVATE | MAP_FIXED | MAP_HUGETLB, f, seg->fileoff) == MAP_FAILED) { 115 | perror("mmap(dylib_map)"); 116 | exit(1); 117 | } 118 | } 119 | 120 | } 121 | fcntlarg[0] = 0; 122 | fcntlarg[1] = 0; 123 | fcntlarg[2] = s.st_size; 124 | fcntl(f, F_SPECULATIVE_READ, fcntlarg); 125 | close(f); 126 | return slide; 127 | } 128 | 129 | 130 | 131 | static uint8_t* 132 | map(const char *path, bool mutable, size_t *size, int *descriptor) 133 | { 134 | int f = -1; 135 | if (mutable) { 136 | f = open(path, O_RDWR | O_CREAT, 0755); 137 | } else { 138 | f = open(path, O_RDONLY, 0755); 139 | } 140 | 141 | if (f < 0) { 142 | perror("open(map)"); 143 | return NULL; 144 | } 145 | if (mutable) { 146 | if (ftruncate(f, *size) < 0) { 147 | perror("ftruncate(map)"); 148 | close(f); 149 | return NULL; 150 | } 151 | } 152 | 153 | struct stat s; 154 | if (fstat(f, &s) < 0) { 155 | perror("fstat(map)"); 156 | close(f); 157 | return NULL; 158 | } 159 | 160 | vm_address_t allocAddr = 0; 161 | uint8_t *base = NULL; 162 | if (!mutable) { 163 | base = mmap((void *)allocAddr, s.st_size, PROT_READ, MAP_PRIVATE, f, 0); 164 | } else { 165 | base = mmap((void *)allocAddr, s.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, f, 0); 166 | } 167 | 168 | if (base == MAP_FAILED) { 169 | perror("mmap(map)"); 170 | close(f); 171 | return NULL; 172 | } 173 | 174 | *size = s.st_size; 175 | if (descriptor) { 176 | *descriptor = f; 177 | } else { 178 | close(f); 179 | } 180 | return base; 181 | } 182 | 183 | int 184 | decrypt_macho(const char *inputFile, const char *outputFile) 185 | { 186 | dlopen(inputFile, RTLD_LOCAL); 187 | printf("mapping input\n"); 188 | // map RO one for decrypt 189 | size_t base_size; 190 | //int f; 191 | uint8_t *base = map(inputFile, false, &base_size, NULL); 192 | if (base == NULL) { 193 | return 1; 194 | } 195 | 196 | printf("mapping output\n"); 197 | // map RW one for modify 198 | size_t dupe_size = base_size; 199 | uint8_t *dupe = map(outputFile, true, &dupe_size, NULL); 200 | if (dupe == NULL) { 201 | munmap(base, base_size); 202 | return 1; 203 | } 204 | 205 | // If the files are not of the same size, then they are not duplicates of 206 | // each other, which is an error. 207 | // 208 | if (base_size != dupe_size) { 209 | munmap(base, base_size); 210 | munmap(dupe, dupe_size); 211 | return 1; 212 | } 213 | 214 | struct mach_header_64* header = (struct mach_header_64*) base; 215 | 216 | // Enumerate all load commands and check for the encryption header, if found 217 | // start "unprotect"'ing the contents. 218 | // 219 | struct encryption_info_command_64 *encryption_info = NULL; 220 | for (uint32_t offset = sizeof(struct mach_header_64), i = 0; i < header->ncmds; i++) { 221 | struct load_command* command = (struct load_command*) (base + offset); 222 | 223 | if (command->cmd == LC_ENCRYPTION_INFO_64) { 224 | encryption_info = (struct encryption_info_command_64*) command; 225 | // If "unprotect"'ing is successful, then change the "cryptid" so that 226 | // the loader does not attempt to decrypt decrypted pages. 227 | // 228 | // There should only be ONE header present anyways, so stop after 229 | // the first one. 230 | // 231 | break; 232 | } 233 | 234 | offset += command->cmdsize; 235 | } 236 | 237 | struct segment_command_64 *target_seg = NULL; 238 | for (uint32_t offset = sizeof(struct mach_header_64), i = 0; i < header->ncmds; i++) { 239 | struct load_command* command = (struct load_command*) (base + offset); 240 | 241 | if (command->cmd == LC_SEGMENT_64) { 242 | struct segment_command_64 *seg = (struct segment_command_64*) command; 243 | if (seg->fileoff <= encryption_info->cryptoff && encryption_info->cryptoff + encryption_info->cryptsize <= seg->fileoff + seg->filesize) { 244 | target_seg = seg; 245 | break; 246 | } 247 | } 248 | 249 | offset += command->cmdsize; 250 | } 251 | 252 | if (!encryption_info || !target_seg) { 253 | printf("malformed macho! encryption_info: %p, target_seg: %p\n", encryption_info, target_seg); 254 | exit(2); 255 | } 256 | 257 | printf("mapping dylib\n"); 258 | vm_address_t slide = dylib_map(inputFile, base); 259 | printf("got slide: %p\n", (void *)slide); 260 | printf("firstdata: %p\n", *(void **)(0x100000000 + slide)); 261 | 262 | 263 | vm_address_t cryptaddr = slide + target_seg->vmaddr + (encryption_info->cryptoff - target_seg->fileoff); 264 | if (unprotect(cryptaddr, encryption_info) == 0) { 265 | printf("copying ori data\n"); 266 | memcpy(dupe, base, base_size); 267 | encryption_info = (struct encryption_info_command_64*) ((uint8_t *)encryption_info + (dupe - base)); 268 | encryption_info->cryptid = 0; 269 | printf("copying decrypted data\n"); 270 | memcpy(dupe + encryption_info->cryptoff, (void *)cryptaddr, encryption_info->cryptsize); 271 | } 272 | 273 | munmap(base, base_size); 274 | munmap(dupe, dupe_size); 275 | return 0; 276 | } 277 | 278 | int 279 | main(int argc, char* argv[]) 280 | { 281 | @autoreleasepool { 282 | printf("%d\n", getpid()); 283 | getchar(); 284 | if (argc < 3) { 285 | return 1; 286 | } 287 | return decrypt_macho(argv[1], argv[2]); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /control.template: -------------------------------------------------------------------------------- 1 | Package: moe.misty.fouldecrypt 2 | Name: fouldecrypt 3 | Version: 0.0.4 4 | Architecture: iphoneos-arm 5 | Depends: zip, {{.depends}} 6 | Description: Directly decrypt binaries like flexdecrypt, but also supports iOS 14 7 | Maintainer: misty 8 | Author: misty 9 | Section: System 10 | Tag: role::hacker 11 | -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | application-identifier 6 | - 7 | com.apple.developer.team-identifier 8 | - 9 | com.apple.diagnosticd.diagnostic 10 | 11 | com.apple.frontboard.debugapplications 12 | 13 | com.apple.multitasking.termination 14 | 15 | com.apple.private.cs.debugger 16 | 17 | com.apple.private.security.no-sandbox 18 | 19 | com.apple.private.skip-library-validation 20 | 21 | com.apple.private.MobileContainerManager.allowed 22 | 23 | com.apple.private.MobileContainerManager.lookup 24 | 25 | com.apple.private.MobileContainerManager.otherIdLookup 26 | 27 | com.apple.springboard.launchapplications 28 | 29 | dynamic-codesigning 30 | 31 | get-task-allow 32 | 33 | platform-application 34 | 35 | task_for_pid-allow 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /flexwrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | extern int VERBOSE; 7 | int decrypt_macho(const char *inputFile, const char *outputFile); 8 | 9 | int 10 | main(int argc, char* argv[]) { 11 | int opt; 12 | while((opt = getopt(argc, argv, "v")) != -1) { 13 | switch (opt) { 14 | case 'v': 15 | VERBOSE = 1; 16 | break; 17 | default: 18 | printf("optopt = %c\n", (char)optopt); 19 | printf("opterr = %d\n", opterr); 20 | fprintf(stderr, "usage: %s [-v] encfile\n", argv[0]); 21 | exit(1); 22 | } 23 | } 24 | argc -= optind; 25 | argv += optind; 26 | 27 | if (argc < 1) { 28 | return 1; 29 | } 30 | char outpath[0x200] = { 0 }; 31 | strcpy(outpath, "/tmp/"); 32 | const char *lastPathComponent = strrchr(argv[0], '/'); 33 | if (!lastPathComponent) { 34 | strcat(outpath, argv[0]); 35 | } else { 36 | strcat(outpath, lastPathComponent + 1); 37 | } 38 | int ret = decrypt_macho(argv[0], outpath); 39 | if (!ret) { 40 | printf("Wrote decrypted image to %s\n", outpath); 41 | } 42 | return ret; 43 | } 44 | -------------------------------------------------------------------------------- /foulmain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | extern int VERBOSE; 7 | int decrypt_macho(const char *inputFile, const char *outputFile); 8 | 9 | int 10 | main(int argc, char* argv[]) 11 | { 12 | int opt; 13 | while((opt = getopt(argc, argv, "v")) != -1) { 14 | switch (opt) { 15 | case 'v': 16 | VERBOSE = 1; 17 | break; 18 | default: 19 | printf("optopt = %c\n", (char)optopt); 20 | printf("opterr = %d\n", opterr); 21 | fprintf(stderr, "usage: %s [-v] encfile outfile\n", argv[0]); 22 | exit(1); 23 | } 24 | } 25 | argc -= optind; 26 | argv += optind; 27 | if (argc < 2) { 28 | fprintf(stderr, "usage: fouldecrypt [-v] encfile outfile\n"); 29 | return 1; 30 | } 31 | return decrypt_macho(argv[0], argv[1]); 32 | } 33 | -------------------------------------------------------------------------------- /foulwrapper.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import 6 | 7 | #import 8 | #import 9 | #import 10 | 11 | static int VERBOSE = 0; 12 | 13 | #define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */ 14 | #define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */ 15 | 16 | #define FAT_MAGIC_64 0xcafebabf 17 | #define FAT_CIGAM_64 0xbfbafeca /* NXSwapLong(FAT_MAGIC_64) */ 18 | 19 | extern char **environ; 20 | 21 | static NSString *shared_shell_path(void) 22 | { 23 | static NSString *_sharedShellPath = nil; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ @autoreleasepool { 26 | NSArray *possibleShells = @[ 27 | @"/usr/bin/bash", 28 | @"/bin/bash", 29 | @"/usr/bin/sh", 30 | @"/bin/sh", 31 | @"/usr/bin/zsh", 32 | @"/bin/zsh", 33 | @"/var/jb/usr/bin/bash", 34 | @"/var/jb/bin/bash", 35 | @"/var/jb/usr/bin/sh", 36 | @"/var/jb/bin/sh", 37 | @"/var/jb/usr/bin/zsh", 38 | @"/var/jb/bin/zsh", 39 | ]; 40 | NSFileManager *fileManager = [NSFileManager defaultManager]; 41 | for (NSString *shellPath in possibleShells) { 42 | // check if the shell exists and is regular file (not symbolic link) and executable 43 | NSDictionary *shellAttrs = [fileManager attributesOfItemAtPath:shellPath error:nil]; 44 | if ([shellAttrs[NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { 45 | continue; 46 | } 47 | if (![fileManager isExecutableFileAtPath:shellPath]) { 48 | continue; 49 | } 50 | _sharedShellPath = shellPath; 51 | break; 52 | } 53 | } }); 54 | return _sharedShellPath; 55 | } 56 | 57 | int 58 | my_system(const char *ctx) 59 | { 60 | const char *shell_path = [shared_shell_path() UTF8String]; 61 | const char *args[] = { 62 | shell_path, 63 | "-c", 64 | ctx, 65 | NULL 66 | }; 67 | pid_t pid; 68 | int posix_status = posix_spawn(&pid, shell_path, NULL, NULL, (char **) args, environ); 69 | if (posix_status != 0) 70 | { 71 | errno = posix_status; 72 | fprintf(stderr, "posix_spawn, %s (%d)\n", strerror(errno), errno); 73 | return posix_status; 74 | } 75 | pid_t w; 76 | int status; 77 | do 78 | { 79 | w = waitpid(pid, &status, WUNTRACED | WCONTINUED); 80 | if (w == -1) 81 | { 82 | fprintf(stderr, "waitpid %d, %s (%d)\n", pid, strerror(errno), errno); 83 | return errno; 84 | } 85 | if (WIFEXITED(status)) 86 | { 87 | fprintf(stderr, "pid %d exited, status=%d\n", pid, WEXITSTATUS(status)); 88 | } 89 | else if (WIFSIGNALED(status)) 90 | { 91 | fprintf(stderr, "pid %d killed by signal %d\n", pid, WTERMSIG(status)); 92 | } 93 | else if (WIFSTOPPED(status)) 94 | { 95 | fprintf(stderr, "pid %d stopped by signal %d\n", pid, WSTOPSIG(status)); 96 | } 97 | else if (WIFCONTINUED(status)) 98 | { 99 | fprintf(stderr, "pid %d continued\n", pid); 100 | } 101 | } 102 | while (!WIFEXITED(status) && !WIFSIGNALED(status)); 103 | if (WIFSIGNALED(status)) 104 | { 105 | return WTERMSIG(status); 106 | } 107 | return WEXITSTATUS(status); 108 | } 109 | 110 | NSString * 111 | escape_arg(NSString *arg) 112 | { 113 | return [arg stringByReplacingOccurrencesOfString:@"\'" withString:@"'\\\''"]; 114 | } 115 | 116 | @interface LSApplicationProxy () 117 | - (NSString *)shortVersionString; 118 | @end 119 | 120 | int 121 | main(int argc, char *argv[]) 122 | { 123 | if (argc < 2) 124 | { 125 | fprintf(stderr, "usage: foulwrapper (application name or application bundle identifier)\n"); 126 | return 1; 127 | } 128 | 129 | /* Use APIs in `LSApplicationWorkspace`. */ 130 | NSMutableDictionary *appMaps = [NSMutableDictionary dictionary]; 131 | LSApplicationWorkspace *workspace = [LSApplicationWorkspace defaultWorkspace]; 132 | for (LSApplicationProxy *appProxy in [workspace allApplications]) { 133 | NSString *appId = [appProxy applicationIdentifier]; 134 | NSString *appName = [appProxy localizedName]; 135 | if (appId && appName) { 136 | appMaps[appId] = appName; 137 | } 138 | } 139 | 140 | NSString *targetIdOrName = [NSString stringWithUTF8String:argv[1]]; 141 | NSString *targetId = nil; 142 | for (NSString *appId in appMaps) 143 | { 144 | if ([appId isEqualToString:targetIdOrName] || [appMaps[appId] isEqualToString:targetIdOrName]) 145 | { 146 | targetId = appId; 147 | break; 148 | } 149 | } 150 | 151 | if (!targetId) 152 | { 153 | fprintf(stderr, "application \"%s\" not found\n", argv[1]); 154 | return 1; 155 | } 156 | 157 | 158 | /* MobileContainerManager: locate app bundle container path */ 159 | /* `LSApplicationProxy` cannot provide correct values of container URLs since iOS 12. */ 160 | NSError *error = nil; 161 | id aClass = objc_getClass("MCMAppContainer"); 162 | assert([aClass respondsToSelector:@selector(containerWithIdentifier:error:)]); 163 | 164 | MCMContainer *container = [aClass containerWithIdentifier:targetId error:&error]; 165 | NSString *targetPath = [[container url] path]; 166 | if (!targetPath) 167 | { 168 | fprintf(stderr, 169 | "application \"%s\" does not have a bundle container: %s\n", 170 | argv[1], 171 | [[error localizedDescription] UTF8String]); 172 | return 1; 173 | } 174 | NSLog(@"%@", targetPath); 175 | 176 | 177 | /* Make a copy of app bundle. */ 178 | NSURL *tempURL = [[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory 179 | inDomain:NSUserDomainMask 180 | appropriateForURL:[NSURL fileURLWithPath:[[NSFileManager defaultManager] currentDirectoryPath]] 181 | create:YES error:&error]; 182 | if (!tempURL) 183 | { 184 | fprintf(stderr, 185 | "cannot create appropriate item replacement directory: %s\n", 186 | [[error localizedDescription] UTF8String]); 187 | return 1; 188 | } 189 | 190 | NSString *tempPath = [[tempURL path] stringByAppendingPathComponent:@"Payload"]; 191 | BOOL didCopy = [[NSFileManager defaultManager] copyItemAtPath:targetPath toPath:tempPath error:&error]; 192 | if (!didCopy) 193 | { 194 | fprintf(stderr, "cannot copy app bundle: %s\n", [[error localizedDescription] UTF8String]); 195 | return 1; 196 | } 197 | 198 | 199 | /* Enumerate entire app bundle to find all Mach-Os. */ 200 | NSEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:tempPath]; 201 | NSString *objectPath = nil; 202 | while (objectPath = [enumerator nextObject]) 203 | { 204 | NSString *objectFullPath = [tempPath stringByAppendingPathComponent:objectPath]; 205 | FILE *fp = fopen(objectFullPath.UTF8String, "rb"); 206 | if (!fp) 207 | { 208 | perror("fopen"); 209 | continue; 210 | } 211 | 212 | int num = getw(fp); 213 | if (num == EOF) 214 | { 215 | fclose(fp); 216 | continue; 217 | } 218 | 219 | if (num == MH_MAGIC_64 || num == FAT_MAGIC_64) 220 | { 221 | NSString *objectRawPath = [targetPath stringByAppendingPathComponent:objectPath]; 222 | 223 | int decryptStatus = 224 | my_system([[NSString stringWithFormat:@"fouldecrypt -v '%@' '%@'", escape_arg(objectRawPath), escape_arg( 225 | objectFullPath)] UTF8String]); 226 | if (decryptStatus != 0) { 227 | break; 228 | } 229 | } 230 | 231 | fclose(fp); 232 | } 233 | 234 | 235 | /* LSApplicationProxy: get app info */ 236 | LSApplicationProxy *appProxy = [LSApplicationProxy applicationProxyForIdentifier:targetId]; 237 | assert(appProxy); 238 | 239 | 240 | /* zip: archive */ 241 | NSString *archiveName = 242 | [NSString stringWithFormat:@"%@_%@_dumped.ipa", [appProxy localizedName], [appProxy shortVersionString]]; 243 | NSString *archivePath = 244 | [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:archiveName]; 245 | BOOL didClean = [[NSFileManager defaultManager] removeItemAtPath:archivePath error:nil]; 246 | // assert(didClean); 247 | int zipStatus = 248 | my_system([[NSString stringWithFormat:@"set -e; shopt -s dotglob; cd '%@'; zip -r '%@' .; shopt -u dotglob;", escape_arg([tempURL path]), escape_arg( 249 | archivePath)] UTF8String]); 250 | 251 | return zipStatus; 252 | } 253 | -------------------------------------------------------------------------------- /include/MobileContainerManager/MCMContainer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCMContainer.h 3 | // edgbackup 4 | // 5 | // Created by Mason Rachel on 10/30/19. 6 | // 7 | 8 | #ifndef MCMContainer_h 9 | #define MCMContainer_h 10 | 11 | #import 12 | 13 | @interface MCMContainer : NSObject { 14 | long long _containerClass; 15 | NSString *_identifier; 16 | unsigned int _userId; 17 | NSUUID *_uuid; 18 | } 19 | 20 | @property (nonatomic, readonly) long long containerClass; 21 | @property (nonatomic, readonly) NSString *identifier; 22 | @property (nonatomic, readonly) NSDictionary *info; 23 | @property (getter=isTemporary, nonatomic, readonly) bool temporary; 24 | @property (nonatomic, readonly) NSURL *url; 25 | @property (nonatomic, readonly) NSUUID *uuid; 26 | 27 | + (id)containerWithIdentifier:(id)arg1 createIfNecessary:(bool)arg2 existed:(bool*)arg3 error:(id*)arg4; 28 | + (id)containerWithIdentifier:(id)arg1 error:(id*)arg2; 29 | + (id)temporaryContainerWithIdentifier:(id)arg1 existed:(bool*)arg2 error:(id*)arg3; 30 | + (long long)typeContainerClass; 31 | 32 | - (void)_errorOccurred; 33 | - (long long)containerClass; 34 | - (void)dealloc; 35 | - (id)description; 36 | - (id)destroyContainerWithCompletion:(id /* block */)arg1; 37 | - (unsigned long long)diskUsageWithError:(id*)arg1; 38 | - (unsigned long long)hash; 39 | - (id)identifier; 40 | - (id)info; 41 | - (id)infoValueForKey:(id)arg1 error:(id*)arg2; 42 | - (id)init; 43 | - (id)initWithIdentifier:(id)arg1 createIfNecessary:(bool)arg2 existed:(bool*)arg3 temp:(bool)arg4 error:(id*)arg5; 44 | - (id)initWithIdentifier:(id)arg1 userId:(unsigned int)arg2 uuid:(id)arg3 error:(id*)arg4; 45 | - (bool)isEqual:(id)arg1; 46 | - (bool)isTemporary; 47 | - (void)markDeleted; 48 | - (bool)recreateDefaultStructureWithError:(id*)arg1; 49 | - (bool)regenerateDirectoryUUIDWithError:(id*)arg1; 50 | - (bool)setInfoValue:(id)arg1 forKey:(id)arg2 error:(id*)arg3; 51 | - (id)url; 52 | - (id)uuid; 53 | 54 | @end 55 | 56 | #endif /* MCMContainer_h */ 57 | -------------------------------------------------------------------------------- /include/MobileContainerManager/MCMContainerManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCMContainerManager.h 3 | // edgbackup 4 | // 5 | // Created by Mason Rachel on 10/31/19. 6 | // 7 | 8 | #ifndef MCMContainerManager_h 9 | #define MCMContainerManager_h 10 | 11 | @interface MCMContainerManager : NSObject 12 | 13 | + (id)defaultManager; 14 | 15 | - (id)_containersWithClass:(long long)arg1 temporary:(bool)arg2 error:(id*)arg3; 16 | - (id)containerWithContentClass:(long long)arg1 identifier:(id)arg2 createIfNecessary:(bool)arg3 existed:(bool*)arg4 error:(id*)arg5; 17 | - (id)containerWithContentClass:(long long)arg1 identifier:(id)arg2 error:(id*)arg3; 18 | - (id)containersWithClass:(long long)arg1 error:(id*)arg2; 19 | - (id)deleteContainers:(id)arg1 withCompletion:(id /* block */)arg2; 20 | - (id)init; 21 | - (bool)replaceContainer:(id)arg1 withContainer:(id)arg2 error:(id*)arg3; 22 | - (bool)replaceContainer:(id)arg1 withContainer:(id)arg2 error:(id*)arg3 withCompletion:(id /* block */)arg4; 23 | - (id)temporaryContainerWithContentClass:(long long)arg1 identifier:(id)arg2 existed:(bool*)arg3 error:(id*)arg4; 24 | - (id)temporaryContainersWithClass:(long long)arg1 error:(id*)arg2; 25 | 26 | @end 27 | 28 | #endif /* MCMContainerManager_h */ 29 | -------------------------------------------------------------------------------- /layout/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: moe.misty.fouldecrypt 2 | Name: fouldecrypt 3 | Version: 0.0.4 4 | Architecture: iphoneos-arm 5 | Depends: zip 6 | Description: Directly decrypt binaries like flexdecrypt, but also supports iOS 14 7 | Maintainer: misty 8 | Author: misty 9 | Section: System 10 | Tag: role::hacker 11 | -------------------------------------------------------------------------------- /link_theos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ln -s $THEOS/makefiles makefiles 4 | exit 0 5 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "kerninfra/kerninfra.hpp" 20 | 21 | #undef PAGE_SIZE 22 | #define PAGE_SIZE 0x4000 23 | #define EXEC_PAGE_SIZE 0x1000 24 | 25 | int VERBOSE = 0; 26 | #define DLOG(f_, ...) \ 27 | { \ 28 | if (VERBOSE) { \ 29 | struct tm _tm123_; \ 30 | struct timeval _xxtv123_; \ 31 | gettimeofday(&_xxtv123_, NULL); \ 32 | localtime_r(&_xxtv123_.tv_sec, &_tm123_); \ 33 | printf("%02d:%02d:%02d.%06d\t", _tm123_.tm_hour, _tm123_.tm_min, _tm123_.tm_sec, _xxtv123_.tv_usec); \ 34 | printf((f_), ##__VA_ARGS__); \ 35 | printf("\n"); \ 36 | } \ 37 | }; 38 | 39 | 40 | static uint8_t* 41 | map(const char *path, bool _mutable, size_t *size, int *descriptor) 42 | { 43 | int f = open(path, _mutable ? O_CREAT | O_TRUNC | O_RDWR : O_RDONLY, 0755); 44 | if (f < 0) { 45 | perror(_mutable ? "open(map-ro)" : "open(map-rw)"); 46 | return NULL; 47 | } 48 | 49 | if (_mutable) { 50 | if (ftruncate(f, *size) < 0) { 51 | perror("ftruncate(map)"); 52 | return NULL; 53 | } 54 | } 55 | 56 | struct stat s; 57 | if (fstat(f, &s) < 0) { 58 | perror("fstat(map)"); 59 | close(f); 60 | return NULL; 61 | } 62 | 63 | uint8_t *base = (uint8_t *)mmap(NULL, s.st_size, _mutable ? PROT_READ | PROT_WRITE : PROT_READ, 64 | _mutable ? MAP_SHARED : MAP_PRIVATE, f, 0); 65 | if (base == MAP_FAILED) { 66 | perror(_mutable ? "mmap(map-ro)" : "mmap(map-rw)"); 67 | close(f); 68 | return NULL; 69 | } 70 | 71 | *size = s.st_size; 72 | if (descriptor) { 73 | *descriptor = f; 74 | } else { 75 | close(f); 76 | } 77 | return base; 78 | } 79 | 80 | bool has_prep_kernel = false; 81 | int prepare_kernel() { 82 | if (!has_prep_kernel) { 83 | int ret = init_kerninfra(KERNLOG_NONE); 84 | if (ret) return ret; 85 | has_prep_kernel = true; 86 | } 87 | return 0; 88 | } 89 | 90 | extern "C" int mremap_encrypted(void*, size_t, uint32_t, uint32_t, uint32_t); 91 | 92 | extern "C" kern_return_t mach_vm_remap(vm_map_t, mach_vm_address_t *, mach_vm_size_t, 93 | mach_vm_offset_t, int, vm_map_t, mach_vm_address_t, 94 | boolean_t, vm_prot_t *, vm_prot_t *, vm_inherit_t); 95 | 96 | void *__mmap(const char *info, void *base, size_t size, int prot, int flags, int fd, size_t off) { 97 | DLOG("-->> %s mmaping(%p, 0x%zx, %d, 0x%x, %d, 0x%zx)", info, base, size, prot, flags, fd, off); 98 | void *ret = mmap(base, size, prot, flags, fd, off); 99 | if (ret == MAP_FAILED) { 100 | perror("mmap"); 101 | } 102 | DLOG("<<-- %s mmaping(%p, 0x%zx, %d, 0x%x, %d, 0x%zx) = %p", info, base, size, prot, flags, fd, off, ret); 103 | return ret; 104 | } 105 | 106 | int __mremap_encrypted(const char *info, void *base, size_t cryptsize, uint32_t cryptid, uint32_t cpuType, uint32_t cpuSubType) { 107 | DLOG("<<-- %s mremap_encrypted(%p, 0x%zx, %d, 0x%x, 0x%x)", info, base, cryptsize, cryptid, cpuType, cpuSubType); 108 | int ret = mremap_encrypted(base, cryptsize, cryptid, cpuType, cpuSubType); 109 | if (ret) { 110 | perror("mremap_encrypted"); 111 | } 112 | DLOG("-->> %s mremap_encrypted(%p, 0x%zx, %d, 0x%x, 0x%x) = %d", info, base, cryptsize, cryptid, cpuType, cpuSubType, ret); 113 | return ret; 114 | } 115 | 116 | #define LOGINDENT " " 117 | void debugprint_vme(addr_t _vmentry) { 118 | auto encVmEntry = _vm_map_entry_p(_vmentry); 119 | DLOG(LOGINDENT"mmaped entry: %p - %p", (void *)encVmEntry.start().load(), (void *)encVmEntry.end().load()); 120 | DLOG(LOGINDENT"mmaped vme_offset: 0x%llx", encVmEntry.vme_offset().load()); 121 | DLOG(LOGINDENT"mmaped vme_flags: 0x%x", encVmEntry.vme_flags().load()); 122 | DLOG(LOGINDENT"mmaped vme_object: 0x%llx", encVmEntry.vme_object().load()); 123 | } 124 | 125 | void debugprint_vmobj(addr_t _vmobj) { 126 | auto vmobj = vm_object_t_p(_vmobj); 127 | DLOG(LOGINDENT"mmaped vmobj *shadow: %p **shadow: %p", (void *)vmobj.shadow().load_addr(), (void *)vmobj.shadow().shadow().load_addr()); 128 | DLOG(LOGINDENT"mmaped vmobj pager: %p shadow pager: %p", (void *)vmobj.pager().load_addr(), (void *)vmobj.shadow().pager().load_addr()); 129 | DLOG(LOGINDENT"mmaped vmobj shadow pager op: %p", (void *)vmobj.shadow().pager().mo_pager_ops().load_addr()); 130 | } 131 | 132 | void debugprint_pager(addr_t _pager) { 133 | auto applePager = apple_protect_pager_t_p(_pager); 134 | DLOG(LOGINDENT"mmaped vme_object apple protect pager: ", NULL) 135 | DLOG(LOGINDENT" backingOff %llx", applePager.backing_offset().load()) 136 | DLOG(LOGINDENT" cryptoBackingOff %llx", applePager.crypto_backing_offset().load()) 137 | DLOG(LOGINDENT" cryptoStart %llx", applePager.crypto_start().load()) 138 | DLOG(LOGINDENT" cryptoEnd %llx", applePager.crypto_end().load()) 139 | DLOG(LOGINDENT" cryptInfo %p", (void *)applePager.crypt_info().load()) 140 | } 141 | #undef LOGINDENT 142 | 143 | static int 144 | unprotect(int f, uint8_t *dupe, int cpuType, int cpuSubType, struct encryption_info_command *info, size_t macho_off) 145 | { 146 | #define LOGINDENT " " 147 | assert((info->cryptoff & (EXEC_PAGE_SIZE - 1)) == 0); 148 | 149 | DLOG(LOGINDENT"Going to decrypt crypt page: off 0x%x size 0x%x cryptid %d, cpuType %x cpuSubType %x", info->cryptoff, info->cryptsize, info->cryptid, cpuType, cpuSubType); 150 | //getchar(); 151 | 152 | size_t off_aligned = info->cryptoff & ~(PAGE_SIZE - 1); 153 | //size_t size_aligned = info->cryptsize + info->cryptoff - off_aligned; 154 | size_t map_padding = info->cryptoff - off_aligned; 155 | 156 | int err = 0; 157 | void *decryptedBuf = malloc(info->cryptsize); 158 | 159 | if (!(info->cryptoff & (PAGE_SIZE - 1))) { 160 | DLOG(LOGINDENT"Already 16k aligned, directly go ahead :)"); 161 | void *cryptbase = __mmap("16k-aligned", NULL, info->cryptsize, PROT_READ | PROT_EXEC, MAP_PRIVATE, f, info->cryptoff + macho_off); 162 | // old-school mremap_encrypted 163 | if (__mremap_encrypted("unprotect", cryptbase, info->cryptsize, info->cryptid, cpuType, cpuSubType)) { 164 | munmap(cryptbase, info->cryptsize); 165 | return 1; 166 | } 167 | DLOG(LOGINDENT" copying %p to %p, size %x", (char *)decryptedBuf, cryptbase, info->cryptsize); 168 | memmove(decryptedBuf, cryptbase, info->cryptsize); 169 | munmap(cryptbase, info->cryptsize); 170 | } else { 171 | DLOG(LOGINDENT"Not 16k aligned, trying to do the hack :O"); 172 | 173 | if (!!prepare_kernel()) { 174 | fprintf(stderr, "Failed to init kerninfra!!\n"); 175 | exit(1); 176 | } else { 177 | DLOG(LOGINDENT"successfully initialized kerninfra!"); 178 | } 179 | 180 | for (size_t off = off_aligned; off < info->cryptoff + info->cryptsize; off += PAGE_SIZE) { 181 | size_t off_end = MIN(off + PAGE_SIZE, info->cryptoff + info->cryptsize); 182 | size_t curMapLen = (off_end - off) & (PAGE_SIZE - 1); if (!curMapLen) curMapLen = PAGE_SIZE; 183 | size_t inPageStart = off < info->cryptoff ? info->cryptoff - off : 0; 184 | size_t inPageEnd = curMapLen; 185 | size_t cryptOff = off + inPageStart; 186 | DLOG(LOGINDENT" processing file off %lx-%lx, curPage len: %lx, inPageStart: %lx, inPageEnd: %lx", off, off_end, curMapLen, inPageStart, inPageEnd); 187 | char *cryptbase = (char *)__mmap("directly 16k-aligned mmap", NULL, curMapLen, PROT_READ | PROT_EXEC, MAP_PRIVATE, f, off + macho_off); 188 | 189 | if (__mremap_encrypted("unprotect", cryptbase, curMapLen, info->cryptid, cpuType, cpuSubType)) { 190 | munmap(cryptbase, curMapLen); 191 | return 1; 192 | } 193 | 194 | auto curp = proc_t_p(current_proc()); 195 | addr_t _encVmEntry = lookup_vm_map_entry(curp.task()._map().load_addr(), (addr_t)(cryptbase)); 196 | DLOG(LOGINDENT" Got mmaped entry: %p", (void*)_encVmEntry); 197 | debugprint_vme(_encVmEntry); 198 | 199 | auto encVmEntry = _vm_map_entry_p(_encVmEntry); 200 | auto vmobj = encVmEntry.vme_object(); 201 | debugprint_vmobj(vmobj.load_addr()); 202 | auto applePager = apple_protect_pager_t_p(vmobj.shadow().pager().load_addr()); 203 | DLOG(LOGINDENT" mmaped vme_object apple protect pager: ", NULL); 204 | debugprint_pager(applePager.addr()); 205 | 206 | applePager.crypto_backing_offset().store(macho_off + cryptOff); 207 | applePager.crypto_start().store(inPageStart); 208 | 209 | DLOG(LOGINDENT" patched mmaped vme_object apple protect pager: ", NULL) 210 | debugprint_pager(applePager.addr()); 211 | 212 | DLOG(LOGINDENT" copying %p to %p, size %lx", (char *)decryptedBuf + cryptOff - info->cryptoff, cryptbase + inPageStart, curMapLen - inPageStart); 213 | memmove((char *)decryptedBuf + cryptOff - info->cryptoff, cryptbase + inPageStart, curMapLen - inPageStart); 214 | 215 | munmap(cryptbase, curMapLen); 216 | } 217 | } 218 | 219 | if (err) { 220 | return 1; 221 | } 222 | 223 | DLOG(LOGINDENT"copying enc pages, size: 0x%x..", info->cryptsize); 224 | memcpy(dupe + info->cryptoff, decryptedBuf, info->cryptsize); 225 | 226 | DLOG(LOGINDENT"cleaning up..."); 227 | free(decryptedBuf); 228 | return 0; 229 | #undef LOGINDENT 230 | } 231 | 232 | int 233 | decrypt_macho_slide(int f, uint8_t *inputData, uint8_t *outputData, size_t macho_off) { 234 | #define LOGINDENT " " 235 | uint32_t offset = 0; 236 | int cpuType = 0, cpuSubType = 0; 237 | int ncmds = 0; 238 | if (*(uint32_t *)inputData == MH_MAGIC_64) { // 64bit 239 | struct mach_header_64* header = (struct mach_header_64*) inputData; 240 | cpuType = header->cputype; 241 | cpuSubType = header->cpusubtype; 242 | ncmds = header->ncmds; 243 | offset = sizeof(struct mach_header_64); 244 | } else if (*(uint32_t *)inputData == MH_MAGIC) { // 32bit 245 | struct mach_header* header = (struct mach_header*) inputData; 246 | cpuType = header->cputype; 247 | cpuSubType = header->cpusubtype; 248 | ncmds = header->ncmds; 249 | offset = sizeof(struct mach_header); 250 | } 251 | 252 | DLOG(LOGINDENT"finding encryption_info segment in slide..."); 253 | 254 | // Enumerate all load commands and check for the encryption header, if found 255 | // start "unprotect"'ing the contents. 256 | 257 | struct encryption_info_command *encryption_info = NULL; // for both 32bit and 64bit macho, the command layout are the same 258 | for (uint32_t i = 0; i < ncmds; i++) { 259 | struct load_command* command = (struct load_command*) (inputData + offset); 260 | 261 | if (command->cmd == LC_ENCRYPTION_INFO || command->cmd == LC_ENCRYPTION_INFO_64) { 262 | DLOG(LOGINDENT" found encryption_info segment at offset %x", offset); 263 | encryption_info = (struct encryption_info_command*) command; 264 | // There should only be ONE header present anyways, so stop after 265 | // the first one. 266 | // 267 | break; 268 | } 269 | 270 | offset += command->cmdsize; 271 | } 272 | if (!encryption_info || !encryption_info->cryptid) { 273 | DLOG(LOGINDENT"this slide is not encrypted!"); 274 | return 0; 275 | } 276 | 277 | // If "unprotect"'ing is successful, then change the "cryptid" so that 278 | // the loader does not attempt to decrypt decrypted pages. 279 | // 280 | 281 | DLOG(LOGINDENT"decrypting encrypted data..."); 282 | if (unprotect(f, outputData, cpuType, cpuSubType, encryption_info, macho_off) == 0) { 283 | encryption_info = (struct encryption_info_command*) (outputData + offset); 284 | encryption_info->cryptid = 0; 285 | } else { 286 | return 1; 287 | } 288 | 289 | return 0; 290 | #undef LOGINDENT 291 | } 292 | 293 | int 294 | decrypt_macho(const char *inputFile, const char *outputFile) 295 | { 296 | DLOG("mapping input file: %s", inputFile); 297 | size_t base_size; 298 | int f; 299 | uint8_t *base = map(inputFile, false, &base_size, &f); 300 | if (base == NULL) { 301 | return 1; 302 | } 303 | 304 | DLOG("mapping output file: %s", outputFile); 305 | size_t dupe_size = base_size; 306 | uint8_t *dupe = map(outputFile, true, &dupe_size, NULL); 307 | if (dupe == NULL) { 308 | munmap(base, base_size); 309 | return 1; 310 | } 311 | 312 | DLOG("copying original data of size 0x%zx...", base_size); 313 | memcpy(dupe, base, base_size); 314 | 315 | if (*(uint32_t *)base == FAT_CIGAM || *(uint32_t *)base == FAT_MAGIC) { 316 | bool isBe = *(uint32_t *)base == FAT_CIGAM; 317 | struct fat_header *fat_header = (struct fat_header *) base; 318 | struct fat_arch *fatarches = (struct fat_arch *) (fat_header + 1); 319 | auto fatInt = [isBe](int t) -> int {return isBe ? OSSwapInt32(t) : t;}; 320 | 321 | DLOG("handling %d fat arches...", fatInt(fat_header->nfat_arch)); 322 | for (int fat_i = 0; fat_i < fatInt(fat_header->nfat_arch); fat_i++) { 323 | auto curFatArch = &fatarches[fat_i]; 324 | DLOG(" handling fat arch %d, cpuType 0x%x, cpuSubType 0x%x, fileOff 0x%x, size 0x%x, align 0x%x", fat_i, 325 | fatInt(curFatArch->cputype), fatInt(curFatArch->cpusubtype), fatInt(curFatArch->offset), fatInt(curFatArch->size), fatInt(curFatArch->align)); 326 | decrypt_macho_slide(f, base + fatInt(curFatArch->offset), dupe + fatInt(curFatArch->offset), fatInt(curFatArch->offset)); 327 | } 328 | } else { 329 | DLOG(" not fat binary, directly decrypting it!"); 330 | decrypt_macho_slide(f, base, dupe, 0); 331 | } 332 | 333 | munmap(base, base_size); 334 | munmap(dupe, dupe_size); 335 | return 0; 336 | } 337 | --------------------------------------------------------------------------------