Kext patching support and FAT patching support
Cryptiiiic opened this issue · 6 comments
Currently its not possible to do either of these.
I did end up implementing both of these on my own. However the fixes were extremely hacky. My constraints were to only modify this repo itself, no dep patches. It was hard but I still ended up doing it. The problem is the deps such as libinsn have code locked behind protected or private with no getters or setters to call :(
For kext patching, each kext has a different base, different fileoff. But there is currently no code to extract the set fileoff from the vmem object. When its set its added to something else. Without the original fileoff its impossible to patch the kernel on disk.
Proposed solution: Either make stuff public/add getters/setters or my hacky solution, store the fileoff as a hashmap entry.
libpatchfinder/libpatchfinder/machopatchfinder64.cpp
Lines 118 to 121 in a1c11f7
->
segments.push_back({_buf+seg->fileoff,seg->filesize, (patchfinder64::loc_t)seg->vmaddr, (vmprot)(isWeirdPrelinkText ? (kVMPROTEXEC | kVMPROTREAD) : seg->maxprot), seg->segname});
if (!_base){
_base = (patchfinder64::loc_t)seg->vmaddr; //first segment is base. Is this correct??
}
this->_fileoff_map[seg->vmaddr] = seg->fileoff;
//
//
class machopatchfinder64 : public patchfinder64{
...
std::unordered_map<uint64_t, uint64_t> _fileoff_map;
...
std::unordered_map<uint64_t, uint64_t> get_fileoff_map() { return this->_fileoff_map; };
const tihmstar::libinsn::vmem<libinsn::arm64::insn> *get_vmem() { return this->_vmem; };
...
}
//
//
machopatchfinder64::machopatchfinder64(machopatchfinder64 &&mv)
: patchfinder64(std::move(mv)),
__symtabs(mv.__symtabs),
_fileoff_map(mv._fileoff_map),
This allows tools to be able to both get the kext patch and apply it to the real kext in the kernel file.
For the second issue, FAT patching, I proposed a simple pair output for the tryfat lambda:
libpatchfinder/libpatchfinder/machopatchfinder64.cpp
Lines 190 to 236 in a1c11f7
->
class machopatchfinder64 : public patchfinder64{
...
std::pair<uint8_t *, uint32_t> _fat;
...
std::pair<uint8_t *, uint32_t> get_fat() { return this->_fat; };
...
}
//
//
std::pair<uint8_t *, uint32_t> tryfat = [=]() -> std::pair<uint8_t *, uint32_t> {
// just select first slice
uint32_t* kdata32 = (uint32_t*) _buf;
uint32_t narch = kdata32[1];
if (swap) narch = ntohl(narch);
if (narch != 1) {
printf("expected 1 arch in fat file, got %u\n", narch);
return std::make_pair(nullptr, 0);
}
uint32_t offset = kdata32[2 + 2];
if (swap) offset = ntohl(offset);
if (offset != sizeof(uint32_t)*(2 + 5)) {
printf("wat, file offset not sizeof(fat_header) + sizeof(fat_arch)?!\n");
}
uint32_t filesize = kdata32[2 + 3];
if (swap) filesize = ntohl(filesize);
if (!_freeBuf) {
//if we don't own the buffer, then we can simply move by the required offset.
//a higher level instance will take care of properly freeing the buffer so we can avoid reallocation
assure(filesize <= _bufSize - offset);
_bufSize = filesize;
return std::make_pair((uint8_t*)_buf + offset, filesize);
}
uint8_t *ret = (uint8_t*) malloc(filesize);
if (ret != NULL) {
assure(filesize <= _bufSize - offset);
_bufSize = filesize;
memcpy(ret, _buf + offset, filesize);
}
return std::make_pair(ret, filesize);
}();
if (tryfat.first) {
printf("got fat macho with first slice at %u\n", (uint32_t) (tryfat.first - _buf));
if (_freeBuf) {
free((void*)_buf);
}
this->_fat = tryfat;
_buf = tryfat.first; tryfat.first = nullptr;
_bufSize = tryfat.second; tryfat.second = 0;
} else {
printf("got fat macho but failed to parse\n");
}
//
//
machopatchfinder64::machopatchfinder64(machopatchfinder64 &&mv)
: patchfinder64(std::move(mv)),
__symtabs(mv.__symtabs),
_fat(mv._fat)
I also have test code example here:
for (const auto& p2 : patches) {
printf("%s: Applying patch=0x%016llx: ", __FUNCTION__, p2._location);
for (int i=0; i<p2._patchSize; i++) {
printf("%02x",((uint8_t*)p2._patch)[i]);
}
if (p2._patchSize == 4) {
printf(" 0x%08x",*(uint32_t*)p2._patch);
} else if (p2._patchSize == 2) {
printf(" 0x%04x",*(uint16_t*)p2._patch);
}
printf("\n");
uint64_t base = 0;
uint64_t off = 0;
auto *krnl = ((patchfinder::kernelpatchfinder64 *)kpf);
auto *macho = ((patchfinder::kernelpatchfinder64 *)kpf);
bool is_fat = false;
if(macho->get_fat().first) {
decKernel = (char *)macho->get_fat().first;
decKernelSize = macho->get_fat().second;
is_fat = true;
}
auto vmem = krnl->get_vmem();
auto segs = vmem->getSegments();
for (const auto &seg: segs) {
if(vmem->seg(p2._location).isInRange(seg.vaddr)) {
base = seg.vaddr;
break;
}
}
if(!base) {
fprintf(stderr, "%s: Failed to apply patch=0x%016llx, base is zero!\n", __FUNCTION__, p2._location);
continue;
}
auto fileoff_map = macho->get_fileoff_map();
if(fileoff_map.empty()) {
fprintf(stderr, "%s: Failed to apply patch=0x%016llx, fileoff map is empty!\n", __FUNCTION__, p2._location);
continue;
}
auto fileoff = macho->get_fileoff_map()[base];
if(!fileoff) {
fprintf(stderr, "%s: Failed to apply patch=0x%016llx, fileoff entry is zero!\n", __FUNCTION__, p2._location);
continue;
}
off = p2._location - base + fileoff;
memcpy(&decKernel[off], p2._patch, p2._patchSize);
}
printf("%s: Patches applied!\n", __FUNCTION__);
The test code gets the correct buf to patch from the FAT, then finds the correct KEXT segment base to patch for fileoff.
Maybe I'm overlooking something major here... I honestly don't know... This was the best solution I could come up with and the code is confirmed working on my end.
In conclusion:
- Deps such as libinsn have private/protected functions/class variables that should probably be public.
- libpatchfinder itself has some private/protected functions/class variables that should probably also be public.
- libpatchfinder is missing getters for stuff like FAT and fileoff for segment. Also missing vmem getter? odd.
I have a bit of a hard time following to be honest :/
I mean i get the kext-base-fileoffset thing, but surprisingly i never ran into an issue with kext patching yet.
Could you provide an example of what you are trying to patch?
I will then try to write a patchfinder and see if i run into issues.
Maybe also provide your patcher function, so that we can compare it to what i came up with.
Please provide a link to ipsw with the kernel and example address (either virtual or fileoffset) and patch, which you are trying to apply. I will write my own function and see what issues i run into.
I'm patching the cryptex seed function in my fork the kext is the image4 one.
https://github.com/Cryptiiiic/libpatchfinder/blob/b3feca1c0664459d9065d57b0d2c36de893eef17/libpatchfinder/kernelpatchfinder/kernelpatchfinder64_iOS16.cpp#L1555-L1616
https://github.com/Cryptiiiic/libpatchfinder/blob/b3feca1c0664459d9065d57b0d2c36de893eef17/libpatchfinder/kernelpatchfinder/kernelpatchfinder64_iOS16.cpp#L1047-L1071
iPhone X kernelcache.release.iphone10b
https://updates.cdn-apple.com/2024SpringFCS/fullrestores/052-88545/8113F22D-3AE6-4D2B-81DC-E00838AE22F9/iPhone10,3,iPhone10,6_16.7.8_20H343_Restore.ipsw
0xfffffff004e212f7
0xfffffff004e21307
0xfffffff0056ee27c
0xfffffff0056ee2c4
0xfffffff0056ee2c8
0xfffffff0056ee2cc
0xfffffff0056ee2d0
0xfffffff0056ee2d4
So i created my own patcher function which looks like this and prints the same addresses as you provided
std::vector<patch> kernelpatchfinder64_iOS16::get_img4_nonce_manager_generate_seed_patch(uint8_t seed[16]){
UNCACHEPATCHES;
loc_t seed_z_loc = findstr("seed is zero: flags", false);
debug("seed_z_loc=0x%016llx",seed_z_loc);
loc_t seed_z_ref = find_literal_ref(seed_z_loc);
// debug("seed_z_ref=0x%016llx",seed_z_ref);
loc_t img4_nmgs = find_bof(seed_z_ref);
// debug("img4_nmgs=0x%016llx",img4_nmgs);
auto iter = _vmem->getIter(img4_nmgs);
while (++iter != insn::bl)
;
loc_t f2 = iter;
debug("f2=0x%016llx",f2);
while (++iter != insn::bl)
;
loc_t memcmp_call_loc = iter;
debug("p=0x%016llx",memcmp_call_loc-4*3);
debug("p=0x%016llx",memcmp_call_loc-4*2);
debug("p=0x%016llx",memcmp_call_loc-4*1);
debug("p=0x%016llx",memcmp_call_loc-4*0);
debug("p=0x%016llx",memcmp_call_loc+4*1);
/*
0xfffffff004e212f7
0xfffffff004e21307
0xfffffff0056ee27c
0xfffffff0056ee2c4
0xfffffff0056ee2c8
0xfffffff0056ee2cc
0xfffffff0056ee2d0
0xfffffff0056ee2d4
*/
reterror("TODO");
RETCACHEPATCHES;
}
I didn't run into any problems, so i don't really understand what your issue is :/
Though, could it be that this is what you're looking for maybe?
https://github.com/tihmstar/ra1nsn0w/blob/master/ra1nsn0w/ra1nsn0w.cpp#L742-L757
Uh yah I think I recreated that code but overcomplicated it :) I didn't know about that vmem funciton. I will close this when I confirm it works with memoryForLoc.
Confirmed it works. I want to make a suggestion though. Some of the older iOS patchfinders are not as good as the newer ones. For example the iOS 13 boot arg patch seems to break signature validation if you string is too long. But the iOS 14 patch works up to buf max(256). Would it be befinitial to backport the newer patch to older versions?
That should be discussed in a separate issue.
In short though: if the patcher is labeld "iOS 13" it needs to guarantee to work on ever iOS 13.x version.
As long as that is true, i don't mind backporting "better" patchfinders to older version.
Just keep in mind that the whole project was developed as i needed it, thus just because iOS number is higher, doesn't always mean that it's newer.