From a67241da7e28a588fd71ea7cb900d9e1d49f34c3 Mon Sep 17 00:00:00 2001 From: Jake James Date: Sat, 6 Apr 2019 21:19:36 +0200 Subject: [PATCH] C0rrTrust --- README.md | 2 +- amfi_utils.h | 54 +----- amfi_utils.m | 107 +++++++++++ cs_blob.h | 54 +++++- downloads/jelbrekLib.a | Bin 169040 -> 195440 bytes downloads/jelbrekLib.h | 48 ++++- jelbrek.h | 18 +- jelbrek.m | 402 +++++++++++++++++++++++++++++++++++++++-- kexecute.c | 7 +- make.sh | 1 - patchfinder64.h | 20 ++ patchfinder64.m | 178 +++++++++++++++++- vnode_utils.h | 84 +++++++++ vnode_utils.m | 26 ++- 14 files changed, 924 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 90ec2a8..fbb62a4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Library with commonly used patches in open-source jailbreaks. Call this a (light # Issues - AMFID patch won't resist after app enters background. Fix would be using a daemon (like amfidebilitate) or injecting a dylib (iOS 11) -# iOS 12 status +# iOS 12 satus - rootFS remount is broken. There is hardening on snapshot_rename() which *can* and *has* been (privately) bypassed, but it for sure isn't as bad as last year with iOS 11.3.1, where they made **major** changes. The only thing we need is figuring out how they check if the snapshot is the rootfs and not something in /var for example where snapshot_rename works fine. - kexecute() is also probably broken on A12. Use bazad's PAC bypass which offers the same thing, so this isn't an issue (fr now) - getting root, unsandboxing, NVRAM lock/unlock, setHSP4(), trustbin(), entitlePid + task_for_pid() are all working fine. The rest that is not on top of my mind should also work fine. diff --git a/amfi_utils.h b/amfi_utils.h index 627fd1a..d6d2fae 100644 --- a/amfi_utils.h +++ b/amfi_utils.h @@ -1,27 +1,20 @@ - -// -// amfi_utils.h -// electra -// -// Created by Jamie on 27/01/2018. -// Copyright © 2018 Electra Team. All rights reserved. -// - - #import #import +#import "cs_blob.h" +#import "jelbrek.h" #define MACHO(p) ((*(unsigned int *)(p) & ~1) == 0xfeedface) +void *load_bytes(FILE *file, off_t offset, size_t size); int strtail(const char *str, const char *tail); void getSHA256inplace(const uint8_t* code_dir, uint8_t *out); uint8_t *getSHA256(const uint8_t* code_dir); uint8_t *getCodeDirectory(const char* name); +uint64_t ubc_cs_blob_allocate(vm_size_t size); +int cs_validate_csblob(const uint8_t *addr, size_t length, CS_CodeDirectory **rcd, CS_GenericBlob **rentitlements); +uint64_t getCodeSignatureLC(FILE *file, int64_t *machOff); +const struct cs_hash *cs_find_md(uint8_t type); -// thx hieplpvip -void inject_trusts(int pathc, const char *paths[]); - -// Trust cache types typedef char hash_t[20]; struct trust_chain { @@ -30,36 +23,3 @@ struct trust_chain { unsigned int count; } __attribute__((packed)); -/* - Note this patch still came from @xerub's KPPless branch, but detailed below is kind of my adventures which I rediscovered most of what he did - - So, as said on twitter by @Morpheus______, iOS 11 now uses SHA256 for code signatures, rather than SHA1 like before. - What confuses me though is that I believe the overall CDHash is SHA1, but each subhash is SHA256. In AMFI.kext, the memcmp - used to check between the current hash and the hashes in the cache seem to be this CDHash. So the question is do I really need - to get every hash, or just the main CDHash and insert that one into the trust chain? - - If we look at the trust chain code checker (0xFFFFFFF00637B3E8 6+ 11.1.2), it is pretty basic. The trust chain is in the format of - the following (struct from xerub, but I've checked with AMFI that it is the case): - - struct trust_mem { - uint64_t next; // +0x00 - the next struct trust_mem - unsigned char uuid[16]; // +0x08 - The uuid of the trust_mem (it doesn't seem important or checked apart from when importing a new trust chain) - unsigned int count; // +0x18 - Number of hashes there are - unsigned char hashes[]; // +0x1C - The hashes - } - - The trust chain checker does the following: - - Find the first struct that has a count > 0 - - Loop through all the hashes in the struct, comparing with the current hash - - Keeps going through each chain, then when next is 0, it finishes - - UPDATE: a) was using an old version of JTool. Now I realised the CDHash is SHA256 - b) For launchd (whose hash resides in the AMFI cache), the first byte is used as an index sort of thing, and the next *19* bytes are used for the check - This probably means that only the first 20 bytes of the CDHash are used in the trust cache check - - So our execution method is as follows: - - Calculate the CD Hashes for the target resources that we want to play around with - - Create a custom trust chain struct, and insert it into the existing trust chain - only storing the first 20 bytes of each hash - - ??? PROFIT - */ - diff --git a/amfi_utils.m b/amfi_utils.m index f2a9e90..ae73494 100644 --- a/amfi_utils.m +++ b/amfi_utils.m @@ -157,3 +157,110 @@ int strtail(const char *str, const char *tail) str += lstr - ltail; return memcmp(str, tail, ltail); } + +int cs_validate_csblob(const uint8_t *addr, size_t length, CS_CodeDirectory **rcd, CS_GenericBlob **rentitlements) { + uint64_t rcdptr = Kernel_alloc(8); + uint64_t entptr = Kernel_alloc(8); + + int ret = (int)Kernel_Execute(Find_cs_validate_csblob(), (uint64_t)addr, length, rcdptr, entptr, 0, 0, 0); + *rcd = (CS_CodeDirectory *)KernelRead_64bits(rcdptr); + *rentitlements = (CS_GenericBlob *)KernelRead_64bits(entptr); + + Kernel_free(rcdptr, 8); + Kernel_free(entptr, 8); + + return ret; +} + +uint64_t ubc_cs_blob_allocate(vm_size_t size) { + uint64_t size_p = Kernel_alloc(sizeof(vm_size_t)); + if (!size_p) return 0; + KernelWrite(size_p, &size, sizeof(vm_size_t)); + uint64_t alloced = Kernel_Execute(Find_kalloc_canblock(), size_p, 1, Find_cs_blob_allocate_site(), 0, 0, 0, 0); + Kernel_free(size_p, sizeof(vm_size_t)); + if (alloced) alloced = ZmFixAddr(alloced); + return alloced; +} + +const struct cs_hash *cs_find_md(uint8_t type) { + return (struct cs_hash *)KernelRead_64bits(Find_cs_find_md() + ((type - 1) * 8)); +} + +uint64_t getCodeSignatureLC(FILE *file, int64_t *machOff) { + size_t offset = 0; + struct load_command *cmd = NULL; + + // Init at this + *machOff = -1; + + uint32_t *magic = load_bytes(file, offset, sizeof(uint32_t)); + int ncmds = 0; + + // check magic + if (*magic != 0xFEEDFACF && *magic != 0xBEBAFECA) { + printf("[-] File is not an arm64 or FAT macho!\n"); + free(magic); + return 0; + } + + // FAT + if(*magic == 0xBEBAFECA) { + + uint32_t arch_off = sizeof(struct fat_header); + struct fat_header *fat = (struct fat_header*)load_bytes(file, 0, sizeof(struct fat_header)); + bool foundarm64 = false; + + int n = ntohl(fat->nfat_arch); + printf("[*] Binary is FAT with %d architectures\n", n); + + while (n-- > 0) { + struct fat_arch *arch = (struct fat_arch *)load_bytes(file, arch_off, sizeof(struct fat_arch)); + + if (ntohl(arch->cputype) == 0x100000c) { + printf("[*] Found arm64\n"); + offset = ntohl(arch->offset); + foundarm64 = true; + free(fat); + free(arch); + break; + } + free(arch); + arch_off += sizeof(struct fat_arch); + } + + if (!foundarm64) { + printf("[-] Binary does not have any arm64 slice\n"); + free(fat); + free(magic); + return 0; + } + } + + free(magic); + + *machOff = offset; + + // get macho header + struct mach_header_64 *mh64 = load_bytes(file, offset, sizeof(struct mach_header_64)); + ncmds = mh64->ncmds; + free(mh64); + + // next + offset += sizeof(struct mach_header_64); + + for (int i = 0; i < ncmds; i++) { + cmd = load_bytes(file, offset, sizeof(struct load_command)); + + // this! + if (cmd->cmd == LC_CODE_SIGNATURE) { + free(cmd); + return offset; + } + + // next + offset += cmd->cmdsize; + free(cmd); + } + + return 0; +} diff --git a/cs_blob.h b/cs_blob.h index 4b64da3..0cccd05 100644 --- a/cs_blob.h +++ b/cs_blob.h @@ -1,5 +1,7 @@ //from: xnu osfmk/kern/cs_blobs.h +#import + typedef struct __attribute__((packed)) { uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ uint32_t length; /* total length of CodeDirectory blob */ @@ -51,6 +53,13 @@ typedef struct __attribute__((packed)) { /* followed by Blobs in no particular order as indicated by offsets in index */ } CS_SuperBlob; +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 */ @@ -137,11 +146,35 @@ typedef struct __CodeDirectory { 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 spare1; /* unused (must be zero) */ - uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ + 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]; + /* followed by dynamic content as located by offset fields above */ -} CS_CodeDirectory; +} CS_CodeDirectory +__attribute__ ((aligned(1))); + #define CS_OPS_ENTITLEMENTS_BLOB 7 /* get entitlements blob */ int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize); @@ -168,7 +201,22 @@ struct cs_blob { void * csb_entitlements; /* The entitlements as an OSDictionary */ unsigned int csb_signer_type; + unsigned int csb_reconstituted; // iOS 12 only + /* The following two will be replaced by the csb_signer_type. */ unsigned int csb_platform_binary:1; unsigned int csb_platform_path:1; }; + +typedef void (*cs_md_init)(void *ctx); +typedef void (*cs_md_update)(void *ctx, const void *data, size_t size); +typedef void (*cs_md_final)(void *hash, void *ctx); + +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 */ + cs_md_init cs_init; + cs_md_update cs_update; + cs_md_final cs_final; +}; diff --git a/downloads/jelbrekLib.a b/downloads/jelbrekLib.a index fb9b530d5d9ac3123b4f377d1a6dd7425129f126..f7053e2da7f69bd3d6383c4c8743093240ad94fb 100644 GIT binary patch delta 53524 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 diff --git a/downloads/jelbrekLib.h b/downloads/jelbrekLib.h index 3090c25..5ebadf1 100644 --- a/downloads/jelbrekLib.h +++ b/downloads/jelbrekLib.h @@ -1,7 +1,10 @@ +typedef int (*kexecFunc)(uint64_t function, size_t argument_count, ...); + extern uint32_t KASLR_Slide; extern uint64_t KernelBase; extern mach_port_t TFP0; +extern kexecFunc kernel_exec; /* Purpose: Initialize jelbrekLib (first thing you have to call) @@ -39,6 +42,18 @@ void term_jelbrek(void); */ int trustbin(const char *path); +/* + Purpose: + Bypass all codesign checks for a macho. Binary can be signed with SHA1 or SHA256 + Parameters: + A path to a macho + Return values: + -1: error + 0: success + */ + +int bypassCodeSign(const char *macho); + /* Purpose: Unsandboxes a process @@ -236,6 +251,16 @@ int remountRootFS(void); */ uint64_t getVnodeAtPath(const char *path); +/* + Purpose: + Same effect as "ln -sf replacement original" but not persistent through reboots + Parameters: + "original": path you wish to patch + "replacement": where to link the original path + vnode1 & vnode2: pointers where addresses of the two files vnodes will be stored OR NULL + */ +void copyFileInMemory(char *original, char *replacement, uint64_t *vnode1, uint64_t *vnode2); + /* Purpose: Do a hex dump I guess @@ -340,7 +365,7 @@ int snapshot_check(const char *vol, const char *name); /* Purpose: - Patchfinding (by xerub & ninjaprawn) + Patchfinding (by xerub & ninjaprawn & me) */ uint64_t Find_allproc(void); uint64_t Find_add_x0_x0_0x40_ret(void); @@ -350,6 +375,7 @@ uint64_t Find_bcopy(void); uint64_t Find_rootvnode(void); uint64_t Find_trustcache(void); uint64_t Find_amficache(void); +uint64_t Find_pmap_load_trust_cache_ppl(void); uint64_t Find_OSBoolean_True(void); uint64_t Find_OSBoolean_False(void); uint64_t Find_zone_map_ref(void); @@ -360,6 +386,25 @@ uint64_t Find_bootargs(void); uint64_t Find_vfs_context_current(void); uint64_t Find_vnode_lookup(void); uint64_t Find_vnode_put(void); +uint64_t Find_cs_gen_count(void); +uint64_t Find_cs_validate_csblob(void); +uint64_t Find_kalloc_canblock(void); +uint64_t Find_cs_blob_allocate_site(void); +uint64_t Find_kfree(void); +uint64_t Find_cs_find_md(void); +// PAC +uint64_t Find_l2tp_domain_module_start(void); +uint64_t Find_l2tp_domain_module_stop(void); +uint64_t Find_l2tp_domain_inited(void); +uint64_t Find_sysctl_net_ppp_l2tp(void); +uint64_t Find_sysctl_unregister_oid(void); +uint64_t Find_mov_x0_x4__br_x5(void); +uint64_t Find_mov_x9_x0__br_x1(void); +uint64_t Find_mov_x10_x3__br_x6(void); +uint64_t Find_kernel_forge_pacia_gadget(void); +uint64_t Find_kernel_forge_pacda_gadget(void); +uint64_t Find_IOUserClient_vtable(void); +uint64_t Find_IORegistryEntry__getRegistryEntryID(void); /* Purpose: @@ -377,6 +422,7 @@ void MakePortFakeTaskPort(mach_port_t port, uint64_t task_kaddr); For reading & writing & copying & allocating & freeing kernel memory */ size_t KernelRead(uint64_t where, void *p, size_t size); +size_t KernelWrite(uint64_t where, void *p, size_t size); uint32_t KernelRead_32bits(uint64_t where); uint64_t KernelRead_64bits(uint64_t where); diff --git a/jelbrek.h b/jelbrek.h index 43dfba9..23aab16 100644 --- a/jelbrek.h +++ b/jelbrek.h @@ -31,9 +31,13 @@ //---Obj-c stuff---// #import + +typedef int (*kexecFunc)(uint64_t function, size_t argument_count, ...); + extern uint32_t KASLR_Slide; extern uint64_t KernelBase; extern mach_port_t TFP0; +extern kexecFunc kernel_exec; /* Purpose: Initialize jelbrekLib (first thing you have to call) @@ -58,7 +62,7 @@ void term_jelbrek(void); Purpose: Add a macho binary on the AMFI trustcache Parameters: - A path to single macho or a directory for recursive patching + A path to single macho or a directory for recursive patching. Binaries must be signed with SHA256 Return values: -1: path doesn't exist -2: Couldn't find valid macho in directory @@ -71,6 +75,18 @@ void term_jelbrek(void); */ int trustbin(const char *path); +/* + Purpose: + Bypass all codesign checks for a macho. Binary can be signed with SHA1 or SHA256 + Parameters: + A path to a macho + Return values: + -1: error + 0: success + */ + +int bypassCodeSign(const char *macho); + /* Purpose: Unsandboxes a process diff --git a/jelbrek.m b/jelbrek.m index 945ad2c..4b70e2d 100644 --- a/jelbrek.m +++ b/jelbrek.m @@ -1,4 +1,5 @@ #import "jelbrek.h" +#import "amfi_utils.h" uint32_t KASLR_Slide; uint64_t KernelBase; @@ -142,7 +143,8 @@ int init_with_kbase(mach_port_t tfpzero, uint64_t kernelBase, kexecFunc kexec) { } printf("[+] Initialized patchfinder\n"); - if (!kexec) init_Kernel_Execute(); //kernel execution + kernel_exec = kexec; + if (!kernel_exec) init_Kernel_Execute(); //kernel execution return 0; } @@ -302,9 +304,11 @@ int trustbin(const char *path) { struct trust_chain fake_chain; fake_chain.next = KernelRead_64bits(trust_chain); - *(uint64_t *)&fake_chain.uuid[0] = 0xabadbabeabadbabe; - *(uint64_t *)&fake_chain.uuid[8] = 0xabadbabeabadbabe; + ((uint64_t*)fake_chain.uuid)[0] = 0xbadbabeabadbabe; + ((uint64_t*)fake_chain.uuid)[1] = 0xbadbabeabadbabe; + //arc4random_buf(fake_chain.uuid, 16); + int cnt = 0; uint8_t hash[CC_SHA256_DIGEST_LENGTH]; hash_t *allhash = malloc(sizeof(hash_t) * [paths count]); @@ -330,17 +334,391 @@ int trustbin(const char *path) { KernelWrite(kernel_trust, &fake_chain, sizeof(fake_chain)); KernelWrite(kernel_trust + sizeof(fake_chain), allhash, cnt * sizeof(hash_t)); - extern uint64_t PPLText_base; + extern uint64_t PPLText_size; - if (PPLText_base) { +#if __arm64e__ Kernel_Execute(Find_pmap_load_trust_cache_ppl(), kernel_trust, length, 0, 0, 0, 0, 0); +#else + KernelWrite_64bits(trust_chain, kernel_trust); +#endif + + free(allhash); + + return 0; +} + +static const char *csblob_parse_teamid(struct cs_blob *csblob) { + const CS_CodeDirectory *cd; + + cd = csblob->csb_cd; + + if (ntohl(KernelRead_32bits((uint64_t)cd + offsetof(CS_CodeDirectory, version))) < CS_SUPPORTSTEAMID) return 0; + if (KernelRead_32bits((uint64_t)cd + offsetof(CS_CodeDirectory, teamOffset)) == 0) return 0; + + const char *name = ((const char *)cd) + ntohl(KernelRead_32bits((uint64_t)cd + offsetof(CS_CodeDirectory, teamOffset))); + return name; +} + + +int bypassCodeSign(const char *macho) { + uint64_t vnode = 0, addr = 0; + size_t blob_size = 0; + FILE *file = NULL; + CS_GenericBlob *buf_blob = NULL; + struct cs_blob *blob = NULL; + CS_CodeDirectory *rcd = NULL; + CS_GenericBlob *rentitlements = NULL; + + // open + if ((file = fopen(macho, "rb")) == NULL) { + printf("[-] Failed to open file '%s'\n", macho); + goto error; + } + + // get vnode + vnode = getVnodeAtPath(macho); + if (!vnode) { + printf("[-] Can't get vnode for file '%s'\n", macho); + goto error; + } + + // get ubc_info + uint64_t ubc_info = KernelRead_64bits(vnode + off_v_ubcinfo); + if (!vnode) { + printf("[-] Can't get ubc_info for file '%s'\n", macho); + goto error; + } + + // check if a cs_blob is already loaded, in which case there would be no need to do this + uint64_t cs_blob = KernelRead_64bits(ubc_info + off_ubcinfo_csblobs); + if (cs_blob) { + printf("[*] File '%s' already has a blob! Updating gen_count\n", macho); + KernelWrite_32bits(ubc_info + 44, KernelRead_32bits(Find_cs_gen_count())); + goto success; + } + + //------ magic start here ------// + + // see load_code_signature() + + int64_t machOffset; + uint64_t lc_cmd = getCodeSignatureLC(file, &machOffset); + if (!lc_cmd || machOffset < 0) { + printf("[-] Can't find LC_CODE_SIGNATURE or binary is not arm64!\n"); + goto error; + } + + struct linkedit_data_command *lcp = load_bytes(file, lc_cmd, sizeof(struct linkedit_data_command)); + lcp->dataoff += machOffset; + + blob_size = lcp->datasize; + addr = Kernel_alloc(blob_size); + if (!addr) { + printf("[-] Failed to allocate\n"); + goto error; + } + + buf_blob = load_bytes(file, lcp->dataoff, lcp->datasize); + if (!buf_blob) { + printf("[-] Can't load blob\n"); + goto error; + } + + if (KernelWrite(addr, buf_blob, lcp->datasize) != lcp->datasize) { + printf("[-] Can't write!\n"); + goto error; + } + + // ubc_cs_blob_add: + // cs_blob_create_validated: + + blob = malloc(sizeof(struct cs_blob)); + blob->csb_mem_size = lcp->datasize; + blob->csb_mem_offset = 0; + blob->csb_mem_kaddr = addr; + blob->csb_flags = 0; + blob->csb_signer_type = CS_SIGNER_TYPE_UNKNOWN; + blob->csb_platform_binary = 0; + blob->csb_platform_path = 0; + blob->csb_teamid = NULL; + blob->csb_entitlements_blob = NULL; + blob->csb_entitlements = NULL; + blob->csb_reconstituted = false; + + size_t length = lcp->datasize; + + if (cs_validate_csblob((const uint8_t *)addr, length, &rcd, &rentitlements)) { + printf("[-] Invalid blob\n"); + goto error; + } + + const unsigned char *md_base; + uint8_t hash[CS_HASH_MAX_SIZE]; + int md_size; + + uint64_t cd = (uint64_t)rcd; + rcd = malloc(sizeof(CS_CodeDirectory)); + KernelRead(cd, rcd, sizeof(CS_CodeDirectory)); + + uint64_t entitlements = 0; + + if (rentitlements) { + entitlements = (uint64_t)rentitlements; + rentitlements = malloc(sizeof(CS_GenericBlob)); + KernelRead(entitlements, rentitlements, sizeof(CS_GenericBlob)); + } + + blob->csb_cd = (const CS_CodeDirectory *)cd; + blob->csb_entitlements_blob = (const CS_GenericBlob *)entitlements; + blob->csb_hashtype = cs_find_md(rcd->hashType); + + if (blob->csb_hashtype == NULL || KernelRead_64bits((uint64_t)blob->csb_hashtype + offsetof(struct cs_hash, cs_digest_size)) > sizeof(hash)) { + printf("[-] UNSUPPORTED TYPE. AM I SUPPOSED TO PANIC? Hmm...\n"); + sleep(2); + printf("nah..."); + goto error; + } + + blob->csb_hash_pageshift = rcd->pageSize; + blob->csb_hash_pagesize = (1U << rcd->pageSize); + blob->csb_hash_pagemask = blob->csb_hash_pagesize - 1; + blob->csb_hash_firstlevel_pagesize = 0; + blob->csb_flags = (ntohl(rcd->flags) & CS_ALLOWED_MACHO) | CS_VALID; + blob->csb_end_offset = (((vm_offset_t)ntohl(rcd->codeLimit) + blob->csb_hash_pagemask) & ~((vm_offset_t)blob->csb_hash_pagemask)); + if((ntohl(rcd->version) >= CS_SUPPORTSSCATTER) && (ntohl(rcd->scatterOffset))) { + const SC_Scatter *scatter = (const SC_Scatter*) + ((const char*)rcd + ntohl(rcd->scatterOffset)); + blob->csb_start_offset = ((off_t)ntohl(scatter->base)) * blob->csb_hash_pagesize; + } else { + blob->csb_start_offset = 0; + } + + md_base = (const unsigned char *)cd; + md_size = ntohl(rcd->length); + + // BAAAAAH + /*blob->csb_hashtype->cs_init(&mdctx); + blob->csb_hashtype->cs_update(&mdctx, md_base, md_size); + blob->csb_hashtype->cs_final(hash, &mdctx);*/ + + getSHA256inplace((uint8_t *)getCodeDirectory(macho), hash); // hash is not checked. it'll work with SHA1 as well + memcpy(blob->csb_cdhash, hash, CS_CDHASH_LEN); + + // end cs_blob_create_validated + + blob->csb_cpu_type = 0x0100000c; // assume arm64 + blob->csb_base_offset = machOffset; + + // vnode_check_signature: + blob->csb_signer_type = 0; + blob->csb_flags = 0x24000005; + blob->csb_platform_binary = 1; + + // CoreTrustCheckThisBinaryPls(): + // NAAAAH HAHA BYE BYE + // amfidCheckPls(): screw you too + + // end fake vnode_check_signature() + + // CoreTrust & amfid both returned success as you can see ^ \ssssss + + vm_address_t new_mem_kaddr = 0; + vm_size_t new_mem_size = 0; + + CS_CodeDirectory *new_cd = NULL; + CS_GenericBlob const *new_entitlements = NULL; + + // ubc_cs_reconstitute_code_signature: + // Apple come on, why are these funcs not separate in the kernelcache but separate on XNU sources. it would have saved me so much time... + + vm_offset_t new_blob_addr; + vm_size_t new_blob_size; + vm_size_t new_cdsize; + + const CS_CodeDirectory *old_cd = blob->csb_cd; + new_cdsize = htonl(KernelRead_32bits((uint64_t)old_cd + offsetof(CS_CodeDirectory, length))); + + new_blob_size = sizeof(CS_SuperBlob); + new_blob_size += sizeof(CS_BlobIndex); + new_blob_size += new_cdsize; + + if (blob->csb_entitlements_blob) { + new_blob_size += sizeof(CS_BlobIndex); + new_blob_size += ntohl(KernelRead_32bits((uint64_t)blob->csb_entitlements_blob + offsetof(CS_GenericBlob, length))); + } + + new_blob_addr = ubc_cs_blob_allocate(new_blob_size); + if (!new_blob_addr) { + printf("[-] Can't alloc\n"); + goto error; + } + + CS_SuperBlob *new_superblob = (CS_SuperBlob *)new_blob_addr; + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, magic), htonl(CSMAGIC_EMBEDDED_SIGNATURE)); + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, length), htonl((uint32_t)new_blob_size)); + + if (blob->csb_entitlements_blob) { + vm_size_t ent_offset, cd_offset; + + cd_offset = sizeof(CS_SuperBlob) + 2 * sizeof(CS_BlobIndex); + ent_offset = cd_offset + new_cdsize; + + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, count), htonl(2)); + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, index[0].type), htonl(CSSLOT_CODEDIRECTORY)); + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, index[0].offset), htonl((uint32_t)cd_offset)); + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, index[1].type), htonl(CSSLOT_ENTITLEMENTS)); + KernelWrite_32bits((uint64_t)new_superblob + offsetof(CS_SuperBlob, index[1].offset), htonl((uint32_t)ent_offset)); + + void *buf = malloc(ntohl(KernelRead_32bits((uint64_t)blob->csb_entitlements_blob + offsetof(CS_GenericBlob, length)))); + KernelRead((uint64_t)blob->csb_entitlements_blob, buf, ntohl(KernelRead_32bits((uint64_t)blob->csb_entitlements_blob + offsetof(CS_GenericBlob, length)))); + KernelWrite((uint64_t)(new_blob_addr + ent_offset), buf, ntohl(KernelRead_32bits((uint64_t)blob->csb_entitlements_blob + offsetof(CS_GenericBlob, length)))); + free(buf); + + new_cd = (CS_CodeDirectory *)(new_blob_addr + cd_offset); + } else { + new_cd = (CS_CodeDirectory *)new_blob_addr; + } + + void *buf = malloc(new_cdsize); + KernelRead((uint64_t)old_cd, buf, new_cdsize); + KernelWrite((uint64_t)new_cd, buf, new_cdsize); + free(buf); + + vm_size_t len = new_blob_size; + + CS_CodeDirectory *_cd = NULL; + CS_GenericBlob *_entitlements = NULL; + + if (cs_validate_csblob((const uint8_t *)new_blob_addr, len, &_cd, &_entitlements)) { + printf("[-] Invalid blob\n"); + Kernel_Execute(Find_kfree(), new_blob_addr, new_blob_size, 0, 0, 0, 0, 0); + goto error; + } + + new_entitlements = _entitlements; + new_mem_size = new_blob_size; + new_mem_kaddr = new_blob_addr; + + // end ubc_cs_reconstitute_code_signature + + Kernel_free(blob->csb_mem_kaddr, blob->csb_mem_size); + addr = 0; + + blob->csb_mem_kaddr = new_mem_kaddr; + blob->csb_mem_size = new_mem_size; + blob->csb_cd = new_cd; + + if (!new_entitlements) { + const char *newEntitlements = "" + "" + "" + "" + "platform-application" // we're apple made :) + "" + "com.apple.private.security.no-container" // no container + "" + //"com.apple.private.security.container-required" // containermanagerd no crazy + //"" + "get-task-allow" // allow us to task_for_pid + "" + "com.apple.private.skip-library-validation" // allow invalid libs + "" + "" + ""; + + CS_GenericBlob *newBlob = malloc(sizeof(CS_GenericBlob) + strlen(newEntitlements) + 1); + if (!newBlob) { + printf("[-] Can't alloc new entitlements\n"); + goto error; + } + + newBlob->magic = ntohl(CSMAGIC_EMBEDDED_ENTITLEMENTS); + newBlob->length = ntohl(strlen(newEntitlements) + 1); + memcpy(newBlob->data, newEntitlements, strlen(newEntitlements) + 1); + new_entitlements = (CS_GenericBlob *)ubc_cs_blob_allocate(sizeof(CS_GenericBlob) + strlen(newEntitlements) + 1); + + if (!new_entitlements) { + printf("[-] Can't alloc new entitlements on kernel\n"); + free(blob); + goto error; + } + + KernelWrite((uint64_t)new_entitlements, newBlob, sizeof(CS_GenericBlob) + strlen(newEntitlements) + 1); + free(newBlob); + } + + blob->csb_entitlements_blob = new_entitlements; + + uint64_t ents = Kernel_Execute(Find_osunserializexml(), (uint64_t)new_entitlements + offsetof(CS_GenericBlob, data), 0, 0, 0, 0, 0, 0); + if (ents) { + ents = ZmFixAddr(ents); + blob->csb_entitlements = (void *)ents; + + uint64_t OSBoolTrue = Find_OSBoolean_True(); + OSDictionary_SetItem(ents, "platform-application", OSBoolTrue); + OSDictionary_SetItem(ents, "com.apple.private.security.no-container", OSBoolTrue); + OSDictionary_SetItem(ents, "get-task-allow", OSBoolTrue); + OSDictionary_SetItem(ents, "com.apple.private.skip-library-validation", OSBoolTrue); } else { - KernelWrite_64bits(trust_chain, kernel_trust); + printf("[?] Invalid entitlement blob??\n"); + goto error; } + blob->csb_reconstituted = true; + blob->csb_teamid = csblob_parse_teamid(blob); - free(allhash); + off_t blob_start_offset = blob->csb_base_offset + blob->csb_start_offset; + off_t blob_end_offset = blob->csb_base_offset + blob->csb_end_offset; + + if (blob_start_offset >= blob_end_offset || blob_start_offset < 0 || blob_end_offset <= 0) { + printf("[-] Invalid blob\n"); + goto error; + } + + // memory_object_signed() + uint64_t ui_control = KernelRead_64bits(ubc_info + 8); + uint64_t moc_object = KernelRead_64bits(ui_control + 8); + KernelWrite_32bits(moc_object + 168, (KernelRead_32bits(moc_object + 168) & 0xFFFFFEFF) | (1 << 8)); + KernelWrite_32bits(ubc_info + 44, KernelRead_32bits(Find_cs_gen_count())); + blob->csb_next = 0; + + // write it! + uint64_t kblob = ubc_cs_blob_allocate(sizeof(struct cs_blob)); + KernelWrite(kblob, blob, sizeof(struct cs_blob)); + KernelWrite_64bits(ubc_info + off_ubcinfo_csblobs, kblob); + + if (strstr(macho, ".dylib")) { + uint32_t v_flags = KernelRead_32bits(vnode + off_v_flags); + KernelWrite_32bits(vnode + off_v_flags, v_flags | 0x200); // VSHARED_DYLD + } + printf("[?] Am I still alive?\n"); + goto success; + + //------ magic end here ------// + +error:; + if (file) fclose(file); + if (vnode) vnode_put(vnode); + if (addr) Kernel_Execute(Find_kfree(), addr, blob_size, 0, 0, 0, 0, 0); + if (blob) free(blob); + if (buf_blob) free(buf_blob); + if (rcd) free(rcd); + if (rentitlements) free(rentitlements); + + printf("[-] Blob creation failed!\n"); + return -1; + +success:; + if (file) fclose(file); + if (vnode) vnode_put(vnode); + if (addr) Kernel_Execute(Find_kfree(), addr, blob_size, 0, 0, 0, 0, 0); + if (blob) free(blob); + if (buf_blob) free(buf_blob); + if (rcd) free(rcd); + if (rentitlements) free(rentitlements); + + printf("[+] Seems like we succeeded!\n"); return 0; } @@ -689,7 +1067,7 @@ int launchSuspended(char *binary, char *arg1, char *arg2, char *arg3, char *arg4 posix_spawnattr_t attr; posix_spawnattr_init(&attr); - posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED); //this flag will make the created process stay frozen until we send the CONT signal. This so we can platformize it before it launches. + posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED); //this flag will make the created process stay frozen until we send the CONT signal. int rv = posix_spawn(&pd, binary, NULL, &attr, (char **)&args, env); @@ -704,10 +1082,12 @@ int launch(char *binary, char *arg1, char *arg2, char *arg3, char *arg4, char *a int rv = posix_spawn(&pd, binary, NULL, NULL, (char **)&args, env); if (rv) return rv; - int a = 0; - waitpid(pd, &a, 0); + return 0; - return WEXITSTATUS(a); + //int a = 0; + //waitpid(pd, &a, 0); + + //return WEXITSTATUS(a); } BOOL remount1126() { diff --git a/kexecute.c b/kexecute.c index 6df7828..cbbcc04 100644 --- a/kexecute.c +++ b/kexecute.c @@ -6,6 +6,10 @@ #import "offsetof.h" #import + +typedef int (*kexecFunc)(uint64_t function, size_t argument_count, ...); +kexecFunc kernel_exec; + mach_port_t PrepareUserClient(void) { kern_return_t err; mach_port_t UserClient; @@ -37,9 +41,6 @@ static uint64_t FakeVtable = 0; static uint64_t FakeClient = 0; const int fake_Kernel_alloc_size = 0x1000; -typedef int (*kexecFunc)(uint64_t function, size_t argument_count, ...); -static kexecFunc kernel_exec = 0; - void init_Kernel_Execute(void) { UserClient = PrepareUserClient(); diff --git a/make.sh b/make.sh index 4cb1f0a..2e0cce9 100644 --- a/make.sh +++ b/make.sh @@ -1,4 +1,3 @@ #!/bin/bash xcrun -sdk iphoneos clang -c -arch arm64 -Iinclude -fobjc-arc *.c *.m *.cpp && ar rcu downloads/jelbrekLib.a *.o && rm *.o - diff --git a/patchfinder64.h b/patchfinder64.h index 9b48030..daea22f 100644 --- a/patchfinder64.h +++ b/patchfinder64.h @@ -21,3 +21,23 @@ uint64_t Find_bootargs(void); uint64_t Find_vfs_context_current(void); uint64_t Find_vnode_lookup(void); uint64_t Find_vnode_put(void); +uint64_t Find_cs_gen_count(void); +uint64_t Find_cs_validate_csblob(void); +uint64_t Find_kalloc_canblock(void); +uint64_t Find_cs_blob_allocate_site(void); +uint64_t Find_kfree(void); +uint64_t Find_cs_find_md(void); + +// PAC +uint64_t Find_l2tp_domain_module_start(void); +uint64_t Find_l2tp_domain_module_stop(void); +uint64_t Find_l2tp_domain_inited(void); +uint64_t Find_sysctl_net_ppp_l2tp(void); +uint64_t Find_sysctl_unregister_oid(void); +uint64_t Find_mov_x0_x4__br_x5(void); +uint64_t Find_mov_x9_x0__br_x1(void); +uint64_t Find_mov_x10_x3__br_x6(void); +uint64_t Find_kernel_forge_pacia_gadget(void); +uint64_t Find_kernel_forge_pacda_gadget(void); +uint64_t Find_IOUserClient_vtable(void); +uint64_t Find_IORegistryEntry__getRegistryEntryID(void); diff --git a/patchfinder64.m b/patchfinder64.m index a8c08ea..0fe74a3 100644 --- a/patchfinder64.m +++ b/patchfinder64.m @@ -1101,7 +1101,7 @@ addr_t Find_trustcache(void) { if (!val) { // iOS 12 - if (PPLText_base) { + if (PPLText_size) { // A12 ref = Find_strref("\"loadable trust cache buffer too small (%ld) for entries claimed (%d)\"", 1, 4, false); @@ -1116,7 +1116,7 @@ addr_t Find_trustcache(void) { return 0; } - return val + KernDumpBase; + return val + KernDumpBase + KASLR_Slide; } else { ref = Find_strref("\"loadable trust cache buffer too small (%ld) for entries claimed (%d)\"", 1, 0, false); @@ -1131,18 +1131,15 @@ addr_t Find_trustcache(void) { if (!val) { return 0; } - return val + KernDumpBase; + return val + KernDumpBase + KASLR_Slide; } return val + KernDumpBase + KASLR_Slide; } addr_t Find_pmap_load_trust_cache_ppl() { - uint64_t ref = Find_strref("%s: trust cache already loaded, ignoring", 2, 0, false); + uint64_t ref = Find_strref("%s: trust cache already loaded, ignoring", 1, 0, false); if (!ref) { - ref = Find_strref("%s: trust cache already loaded, ignoring", 1, 0, false); - if (!ref) { - return 0; - } + return 0; } ref -= KernDumpBase; @@ -1165,6 +1162,7 @@ addr_t Find_pmap_load_trust_cache_ppl() { return func + KernDumpBase + KASLR_Slide; } + addr_t Find_amficache() { uint64_t cbz, call, func, val; uint64_t ref = Find_strref("amfi_prevent_old_entitled_platform_binaries", 1, 1, false); @@ -1682,3 +1680,167 @@ addr_t Find_IORegistryEntry__getRegistryEntryID() { return addr + KernDumpBase - (uint64_t)Kernel + KASLR_Slide; } + +addr_t Find_cs_gen_count() { + uint64_t ref = Find_strref("CS Platform Exec Logging: Executing platform signed binary '%s'", 1, 2, false); + if (!ref) { + return 0; + } + ref -= KernDumpBase; + + uint64_t addr = Step64(Kernel, ref, 200, INSN_ADRP); + if (!addr) { + return 0; + } + + addr = Calc64(Kernel, addr, addr + 12, 25); + if (!addr) { + return 0; + } + + return addr + KernDumpBase + KASLR_Slide; +} + + +addr_t Find_cs_validate_csblob() { + + uint32_t bytes[] = { + 0x52818049, // mov w9, #0xC02 + 0x72bf5bc9, // movk w9, #0xfade, lsl#16 + 0x6b09011f // cmp w8, w9 + }; + + uint64_t addr = (uint64_t)Boyermoore_horspool_memmem((unsigned char *)((uint64_t)Kernel + XNUCore_Base), XNUCore_Size, (const unsigned char *)bytes, sizeof(bytes)); + if (!addr) { + return 0; + } + + addr -= (uint64_t)Kernel; + addr = BOF64(Kernel, XNUCore_Base, addr); + if (!addr) { + return 0; + } + + return addr + KernDumpBase + KASLR_Slide; +} + +addr_t Find_kalloc_canblock() { + + uint32_t bytes[] = { + 0xaa0003f3, // mov x19, x0 + 0xf9400274, // ldr x20, [x19] + 0xf11fbe9f // cmp x20, #0x7ef + }; + + uint64_t addr = (uint64_t)Boyermoore_horspool_memmem((unsigned char *)((uint64_t)Kernel + XNUCore_Base), XNUCore_Size, (const unsigned char *)bytes, sizeof(bytes)); + if (!addr) { + return 0; + } + addr -= (uint64_t)Kernel; + + addr = BOF64(Kernel, XNUCore_Base, addr); + if (!addr) { + return 0; + } + + return addr + KernDumpBase + KASLR_Slide; +} + +addr_t Find_cs_blob_allocate_site() { + + uint32_t bytes[] = { + 0xf9001ea8, // str x8, [x21, #0x38] + 0xb9000ebf, // str wzr, [x21, #0xc] + 0x3942a2a8, // ldrb 28, [x21, #0xa8] + 0x121e1508, // and w8, w8, #0xfc + }; + + uint64_t addr = (uint64_t)Boyermoore_horspool_memmem((unsigned char *)((uint64_t)Kernel + XNUCore_Base), XNUCore_Size, (const unsigned char *)bytes, sizeof(bytes)); + if (!addr) { + return 0; + } + addr -= (uint64_t)Kernel; + + addr = Step64_back(Kernel, addr, 200, INSN_ADRP); + if (!addr) { + return 0; + } + + addr = Calc64(Kernel, addr, addr + 8, 2); + if (!addr) { + return 0; + } + + return addr + KernDumpBase + KASLR_Slide; +} + +addr_t Find_kfree() { + + uint32_t bytes[] = { + 0xf9001ea8, // str x8, [x21, #0x38] + 0xb9000ebf, // str wzr, [x21, #0xc] + 0x3942a2a8, // ldrb 28, [x21, #0xa8] + 0x121e1508, // and w8, w8, #0xfc + }; + + uint64_t addr = (uint64_t)Boyermoore_horspool_memmem((unsigned char *)((uint64_t)Kernel + XNUCore_Base), XNUCore_Size, (const unsigned char *)bytes, sizeof(bytes)); + if (!addr) { + return 0; + } + addr -= (uint64_t)Kernel; + + addr = Step64(Kernel, addr, 200, INSN_CALL); + if (!addr) { + return 0; + } + + addr += 4; + + addr = Step64(Kernel, addr, 200, INSN_CALL); + if (!addr) { + return 0; + } + + addr = Follow_call64(Kernel, addr); + if (!addr) { + return 0; + } + + return addr + KernDumpBase + KASLR_Slide; +} + +addr_t Find_cs_find_md() { + + uint32_t bytes[] = { + 0xb9400008, // ldr w8, [x0] + 0x529bdf49, // mov w9, #0xdefa + 0x72a04189, // movk w9, #0x20c, lsl#16 + 0x6b09011f // cmp w8, w9 + }; + + uint64_t addr = (uint64_t)Boyermoore_horspool_memmem((unsigned char *)((uint64_t)Kernel + XNUCore_Base), XNUCore_Size, (const unsigned char *)bytes, sizeof(bytes)); + if (!addr) { + return 0; + } + + addr -= (uint64_t)Kernel; + + uint64_t adrp = Step64(Kernel, addr, 200, INSN_ADRP); + if (!adrp) { + return 0; + } + + adrp += 4; + + uint64_t adrp2 = Step64(Kernel, adrp, 200, INSN_ADRP); + if (adrp2) { + adrp = adrp2; // non-A12 + } + + addr = Calc64(Kernel, adrp - 4, adrp + 8, 9); + if (!addr) { + return 0; + } + + return addr + KernDumpBase + KASLR_Slide; +} diff --git a/vnode_utils.h b/vnode_utils.h index 40ad44d..b20f3f2 100644 --- a/vnode_utils.h +++ b/vnode_utils.h @@ -1,3 +1,87 @@ int vnode_lookup(const char *path, int flags, uint64_t *vnode, uint64_t vfs_context); uint64_t get_vfs_context(void); int vnode_put(uint64_t vnode); + +#import +#import + +typedef struct { + union { + uint64_t lck_mtx_data; + uint64_t lck_mtx_tag; + }; + union { + struct { + uint16_t lck_mtx_waiters; + uint8_t lck_mtx_pri; + uint8_t lck_mtx_type; + }; + struct { + struct _lck_mtx_ext_ *lck_mtx_ptr; + }; + }; +} lck_mtx_t; + +typedef struct vnode_resolve* vnode_resolve_t; + +typedef uint32_t kauth_action_t; +LIST_HEAD(buflists, buf); + +struct vnode { + lck_mtx_t v_lock; /* vnode mutex */ + TAILQ_ENTRY(vnode) v_freelist; /* vnode freelist */ + TAILQ_ENTRY(vnode) v_mntvnodes; /* vnodes for mount point */ + TAILQ_HEAD(, namecache) v_ncchildren; /* name cache entries that regard us as their parent */ + LIST_HEAD(, namecache) v_nclinks; /* name cache entries that name this vnode */ + 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; + struct buflists v_cleanblkhd; /* clean blocklist head */ + struct buflists v_dirtyblkhd; /* dirty blocklist head */ + struct klist v_knotes; /* knotes attached to this vnode */ + /* + * the following 4 fields are protected + * by the name_cache_lock held in + * excluive mode + */ + kauth_cred_t v_cred; /* last authorized credential */ + kauth_action_t v_authorized_actions; /* current authorized actions for v_cred */ + int v_cred_timestamp; /* determine if entry is stale for MNTK_AUTH_OPAQUE */ + int v_nc_generation; /* changes when nodes are removed from the name cache */ + /* + * back to the vnode lock for protection + */ + int32_t v_numoutput; /* num of writes in progress */ + int32_t v_writecount; /* reference count of writers */ + const char *v_name; /* name component of the vnode */ + vnode_t v_parent; /* pointer to parent vnode */ + struct lockf *v_lockf; /* advisory lock list head */ + int (**v_op)(void *); /* vnode operations vector */ + mount_t v_mount; /* ptr to vfs we are in */ + void * v_data; /* private data for fs */ + + struct label *v_label; /* MAC security label */ + + //#if CONFIG_TRIGGERS + vnode_resolve_t v_resolve; /* trigger vnode resolve info (VDIR only) */ + //#endif /* CONFIG_TRIGGERS */ +}; + +void copyFileInMemory(char *original, char *replacement, uint64_t *vnode1, uint64_t *vnode2); diff --git a/vnode_utils.m b/vnode_utils.m index 31ac398..18fb563 100644 --- a/vnode_utils.m +++ b/vnode_utils.m @@ -12,9 +12,10 @@ #import "offsets.h" #import "kexecute.h" #import "kernelSymbolFinder.h" +#import "jelbrek.h" + #import -extern uint64_t KASLR_Slide; static uint64_t _vnode_lookup = 0; static uint64_t _vnode_put = 0; static uint64_t _vfs_context_current = 0; @@ -57,3 +58,26 @@ int vnode_put(uint64_t vnode) { return (int)Kernel_Execute(_vnode_put, vnode, 0, 0, 0, 0, 0, 0); } + +void copyFileInMemory(char *original, char *replacement, uint64_t *vnode1, uint64_t *vnode2) { + uint64_t orig = getVnodeAtPath(original); + uint64_t fake = getVnodeAtPath(replacement); + + if (vnode1) *vnode1 = orig; + if (vnode2) *vnode2 = fake; + + struct vnode rvp, fvp; + KernelRead(orig, &rvp, sizeof(struct vnode)); + KernelRead(fake, &fvp, sizeof(struct vnode)); + + fvp.v_usecount = rvp.v_usecount; + fvp.v_kusecount = rvp.v_kusecount; + fvp.v_parent = rvp.v_parent; + fvp.v_freelist = rvp.v_freelist; + fvp.v_mntvnodes = rvp.v_mntvnodes; + fvp.v_ncchildren = rvp.v_ncchildren; + fvp.v_nclinks = rvp.v_nclinks; + + KernelWrite(orig, &fvp, sizeof(struct vnode)); +} +