diff --git a/Changelog.md b/Changelog.md index 0cec5393..9eb760be 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Lilu Changelog - Allow loading on macOS 13 without `-lilubetaall` - Added Ventura dyld shared cache pathing - Changed SKL default ig-platform-id to KBL on macOS 13+ +- Added patch with masking support #### v1.6.0 - Dropped internal shared patcher instance grabbing API diff --git a/Lilu/Headers/kern_patcher.hpp b/Lilu/Headers/kern_patcher.hpp index bb1bf2aa..e0588e4f 100644 --- a/Lilu/Headers/kern_patcher.hpp +++ b/Lilu/Headers/kern_patcher.hpp @@ -592,28 +592,31 @@ class KernelPatcher { } /** - * Simple find and replace in kernel memory. + * Find one pattern with optional masking within a block of memory + * + * @param pattern pattern to search + * @param patternMask pattern mask + * @param patternSize size of pattern + * @param data a block of memory + * @param dataSize size of memory + * @param dataOffset data offset, to be set by this function + * + * @return true if pattern is found in data */ - static inline bool findAndReplace(void *data, size_t dataSize, const void *find, size_t findSize, const void *replace, size_t replaceSize) { - void *res; - if (UNLIKELY((res = lilu_os_memmem(data, dataSize, find, findSize)) != nullptr)) { - if (UNLIKELY(MachInfo::setKernelWriting(true, KernelPatcher::kernelWriteLock) != KERN_SUCCESS)) { - SYSLOG("patcher", "failed to obtain write permissions for f/r"); - return false; - } + EXPORT static bool findPattern(const void *pattern, const void *patternMask, size_t patternSize, const void *data, size_t dataSize, size_t *dataOffset); - lilu_os_memcpy(res, replace, replaceSize); - - if (UNLIKELY(MachInfo::setKernelWriting(false, KernelPatcher::kernelWriteLock) != KERN_SUCCESS)) { - SYSLOG("patcher", "failed to restore write permissions for f/r"); - } - - return true; - } + /** + * Simple find and replace with masking in kernel memory. + */ + EXPORT static bool findAndReplaceWithMask(void *data, size_t dataSize, const void *find, size_t findSize, const void *findMask, size_t findMaskSize, const void *replace, size_t replaceSize, const void *replaceMask, size_t replaceMaskSize, size_t count=0, size_t skip=0); - return false; + /** + * Simple find and replace in kernel memory. + */ + static inline bool findAndReplace(void *data, size_t dataSize, const void *find, size_t findSize, const void *replace, size_t replaceSize) { + return findAndReplaceWithMask(data, dataSize, find, findSize, nullptr, 0, replace, replaceSize, nullptr, 0, 0, 0); } - + /** * Simple find and replace in kernel memory but require both `find` and `replace` buffers to have the same length */ @@ -622,6 +625,14 @@ class KernelPatcher { return findAndReplace(data, dataSize, find, N, replace, N); } + /** + * Simple find and replace with masking in kernel memory but require both `find` and `replace` buffers and masking buffers to have the same length + */ + template + static inline bool findAndReplaceWithMask(void *data, size_t dataSize, const uint8_t (&find)[N], const uint8_t (&findMask)[N], const uint8_t (&replace)[N], const uint8_t (&replaceMask)[N], size_t count, size_t skip) { + return findAndReplaceWithMask(data, dataSize, find, N, findMask, N, replace, N, replaceMask, N, count, skip); + } + private: /** * Jump type for routing @@ -707,6 +718,26 @@ class KernelPatcher { */ bool routeMultipleInternal(size_t id, RouteRequest *requests, size_t num, mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false, JumpType jumpType=JumpType::Auto); + /** + * Simple find and replace with masking in kernel memory + * + * @param data kernel memory + * @param dataSize size of kernel memory + * @param find find pattern + * @param findSize size of find pattern + * @param findMask find masking pattern + * @param findMaskSize size of find masking pattern + * @param replace replace pattern + * @param replaceSize size of replace pattern + * @param replaceMask replace masking pattern + * @param replaceMaskSize repalce masking pattern + * @param count maximum times of patching + * @param skip number of skipping times before performing replacement + * + * @return true if the finding and replacing performance is successful + */ + static bool findAndReplaceWithMaskInternal(void *data, size_t dataSize, const void *find, size_t findSize, const void *findMask, size_t findMaskSize, const void *replace, size_t replaceSize, const void *replaceMask, size_t replaceMaskSize, size_t count, size_t skip); + #ifdef LILU_KEXTPATCH_SUPPORT /** * Process loaded kext diff --git a/Lilu/Sources/kern_patcher.cpp b/Lilu/Sources/kern_patcher.cpp index 58c55e82..9d059621 100644 --- a/Lilu/Sources/kern_patcher.cpp +++ b/Lilu/Sources/kern_patcher.cpp @@ -602,6 +602,107 @@ bool KernelPatcher::routeMultipleShort(size_t id, RouteRequest *requests, size_t return routeMultipleInternal(id, requests, num, start, size, kernelRoute, force, JumpType::Short); } +bool KernelPatcher::findPattern(const void *pattern, const void *patternMask, size_t patternSize, const void *data, size_t dataSize, size_t *dataOffset) { + if (patternSize == 0 || dataSize < patternSize) + return false; + + size_t currOffset = *dataOffset; + size_t lastOffset = dataSize - patternSize; + + const uint8_t *d = (const uint8_t *) data; + const uint8_t *ptn = (const uint8_t *) pattern; + const uint8_t *ptnMask = (const uint8_t *) patternMask; + + if (patternMask == nullptr) { + while (currOffset <= lastOffset) { + size_t i; + for (i = 0; i < patternSize; i++) { + if (d[currOffset + i] != ptn[i]) + break; + } + + if (i == patternSize) { + *dataOffset = currOffset; + return true; + } + + currOffset++; + } + } else { + while (currOffset <= lastOffset) { + size_t i; + for (i = 0; i < patternSize; i++) { + if ((d[currOffset + i] & ptnMask[i]) != ptn[i]) + break; + } + + if (i == patternSize) { + *dataOffset = currOffset; + return true; + } + + currOffset++; + } + } + + return false; +} + +bool KernelPatcher::findAndReplaceWithMask(void *data, size_t dataSize, const void *find, size_t findSize, const void *findMask, size_t findMaskSize, const void *replace, size_t replaceSize, const void *replaceMask, size_t replaceMaskSize, size_t count, size_t skip) { + if (dataSize < findSize) return false; + + uint8_t *d = (uint8_t *) data; + const uint8_t *repl = (const uint8_t *) replace; + const uint8_t *replMsk = (const uint8_t *) replaceMask; + + size_t replCount = 0; + size_t dataOffset = 0; + + while (true) { + bool found = findPattern(find, findMask, findSize, data, dataSize, &dataOffset); + if (!found) break; + + // dataOffset + findSize - 1 is guaranteed to be a valid offset here. As + // dataSize can at most be SIZE_T_MAX, the maximum valid offset is + // SIZE_T_MAX - 1. In consequence, dataOffset + findSize cannot wrap around. + + // skip this finding if requested + if (skip > 0) { + skip--; + dataOffset += findSize; + continue; + } + + if (UNLIKELY(MachInfo::setKernelWriting(true, KernelPatcher::kernelWriteLock) != KERN_SUCCESS)) { + SYSLOG("patcher", "failed to obtain write permissions for f/r"); + return false; + } + + // perform replacement + if (replaceMask == nullptr) { + lilu_os_memcpy(&d[dataOffset], replace, replaceSize); + } else { + for (size_t i = 0; i < findSize; i++) + d[dataOffset + i] = (d[dataOffset + i] & ~replMsk[i]) | (repl[i] & replMsk[i]); + } + + if (UNLIKELY(MachInfo::setKernelWriting(false, KernelPatcher::kernelWriteLock) != KERN_SUCCESS)) { + SYSLOG("patcher", "failed to restore write permissions for f/r"); + } + + replCount++; + dataOffset += replaceSize; + + // check replace count if requested + if (count > 0) { + count--; + if (count == 0) + break; + } + } + + return replCount > 0; +} bool KernelPatcher::routeMultipleInternal(size_t id, RouteRequest *requests, size_t num, mach_vm_address_t start, size_t size, bool kernelRoute, bool force, JumpType jump) { bool errorsFound = false;