// // kern_patcher.hpp // Lilu // // Copyright © 2016-2017 vit9696. All rights reserved. // #ifndef kern_patcher_hpp #define kern_patcher_hpp #include #include #include #include #include #include namespace Patch { union All; void deleter(All * NONNULL); } #ifdef LILU_KEXTPATCH_SUPPORT union OSKextLoadedKextSummaryHeaderAny; #endif /* LILU_KEXTPATCH_SUPPORT */ class KernelPatcher { public: /** * Errors set by functions */ enum class Error { NoError, NoKinfoFound, NoSymbolFound, KernInitFailure, KernRunningInitFailure, KextListeningFailure, DisasmFailure, MemoryIssue, MemoryProtection, PointerRange, AlreadyDone, LockError, Unsupported, InvalidSymbolFound }; /** * Get last error * * @return error code */ EXPORT Error getError(); /** * Reset all the previous errors */ EXPORT void clearError(); /** * Initialise KernelPatcher, prepare for modifications */ void init(); /** * Deinitialise KernelPatcher, must be called regardless of the init error */ void deinit(); /** * Kernel write lock used for performing kernel & kext writes to disable cpu preemption * See MachInfo::setKernelWriting */ EXPORT static IOSimpleLock *kernelWriteLock; /** * Kext information */ struct KextInfo; #ifdef LILU_KEXTPATCH_SUPPORT struct KextInfo { static constexpr size_t Unloaded {0}; enum SysFlags : uint64_t { Loaded, // invoke for kext if it is already loaded Reloadable, // allow the kext to unload and get patched again Disabled, // do not load this kext (formerly achieved pathNum = 0, this no longer works) FSOnly, // do not use prelinkedkernel (kextcache) as a symbol source FSFallback, // perform fs fallback if kextcache failed Reserved, SysFlagNum, }; static constexpr uint64_t UserFlagNum {sizeof(uint64_t)-SysFlagNum}; static_assert(UserFlagNum > 0, "There should be at least one user flag"); const char *id {nullptr}; const char **paths {nullptr}; size_t pathNum {0}; bool sys[SysFlagNum] {}; bool user[UserFlagNum] {}; size_t loadIndex {Unloaded}; // Updated after loading /** * Disable this info from being used * May be called from onPatcherLoad callbacks to disable certain kexts */ void switchOff() { sys[KernelPatcher::KextInfo::Disabled] = true; } }; static_assert(sizeof(KextInfo) == 4 * sizeof(size_t) + sizeof(uint64_t), "KextInfo is no longer ABI compatible"); #endif /* LILU_KEXTPATCH_SUPPORT */ /** * Loads and stores kinfo information locally * * @param id kernel item identifier * @param paths item filesystem path array * @param num number of path entries * @param isKernel kinfo is kernel info * @param fsonly avoid using prelinkedkernel for kexts * @param fsfallback fallback to reading from filesystem if prelink failed * * @return loaded kinfo id */ EXPORT size_t loadKinfo(const char *id, const char * const paths[], size_t num=1, bool isKernel=false, bool fsonly=false, bool fsfallback=false); #ifdef LILU_KEXTPATCH_SUPPORT /** * Loads and stores kinfo information locally * * @param info kext to load, updated on success * * @return loaded kinfo id */ EXPORT size_t loadKinfo(KextInfo *info); #endif /* LILU_KEXTPATCH_SUPPORT */ /** * Kernel kinfo id */ static constexpr size_t KernelID {0}; /** * Update running information * * @param id loaded kinfo id * @param slide loaded slide * @param size loaded memory size * @param force force recalculatiob * * @return new size */ EXPORT size_t updateRunningInfo(size_t id, mach_vm_address_t slide=0, size_t size=0, bool force=false); /** * Any kernel */ static constexpr uint32_t KernelAny {0}; /** * Check kernel compatibility * * @param min minimal requested version or KernelAny * @param max maximum supported version or KernelAny * * @return true on success */ EXPORT static bool compatibleKernel(uint32_t min, uint32_t max); /** * Erase coverage instruction prefix (like inc qword ptr[]), that causes function routing to fail * * @param addr address to valid instruction code * @param count amount of instructions to inspect */ EXPORT void eraseCoverageInstPrefix(mach_vm_address_t addr, size_t count=5); /** * Erase coverage instruction prefix (like inc qword ptr[]), that causes function routing to fail * * @param addr address to valid instruction code * @param count amount of instructions to inspect * @param limit amount of bytes to inspect */ EXPORT void eraseCoverageInstPrefix(mach_vm_address_t addr, size_t count, off_t limit); /** * Solve a kinfo symbol * * @param id loaded kinfo id * @param symbol symbol to solve * * @return running symbol address or 0 */ EXPORT mach_vm_address_t solveSymbol(size_t id, const char *symbol); /** * Solve a kinfo symbol in range with designated type * * @param id loaded kinfo id * @param symbol symbol to solve * @param start start address range * @param size address range size * @param crash kernel panic on invalid non-zero address * * @return running symbol address or 0 casted to type T (mach_vm_address_t) */ template inline T solveSymbol(size_t id, const char *symbol, mach_vm_address_t start, size_t size, bool crash=false) { auto addr = solveSymbol(id, symbol); if (addr) { if (addr >= start && addr < start + size) return (T)addr; code = Error::InvalidSymbolFound; SYSTRACE("patcher", "address " PRIKADDR " is out of range " PRIKADDR " with size %lX", CASTKADDR(addr), CASTKADDR(start), size); PANIC_COND(crash, "patcher", "address " PRIKADDR " is out of range " PRIKADDR " with size %lX", CASTKADDR(addr), CASTKADDR(start), size); } return (T)nullptr; } /** * Solve request to resolve multiple symbols in one shot and simplify error handling * * @seealso solveMultiple(). */ struct SolveRequest { /** * The symbol to solve */ const char *symbol {nullptr}; /** * The symbol address on success, otherwise NULL. */ mach_vm_address_t *address {nullptr}; /** * Construct a solve request conveniently */ template SolveRequest(const char *s, T &addr) : symbol(s), address(reinterpret_cast(&addr)) { } }; /** * Solve multiple functions with basic error handling * * @param id loaded kinfo id * @param requests an array of requests to solve * @param num requests array size * @param start start address range * @param size address range size * @param crash kernel panic on invalid non-zero address * @param force continue on first error * * @return false if at least one symbol cannot be solved. */ inline bool solveMultiple(size_t id, SolveRequest *requests, size_t num, mach_vm_address_t start, size_t size, bool crash=false, bool force=false) { for (size_t index = 0; index < num; index++) { auto result = solveSymbol(id, requests[index].symbol, start, size, crash); if (result) { *requests[index].address = result; } else { clearError(); if (!force) return false; } } return true; } /** * Solve multiple functions with basic error handling * * @param id loaded kinfo id * @param requests an array of requests to solve * @param start start address range * @param size address range size * @param crash kernel panic on invalid non-zero address * @param force continue on first error * * @return false if at least one symbol cannot be solved. */ template inline bool solveMultiple(size_t id, SolveRequest (&requests)[N], mach_vm_address_t start, size_t size, bool crash=false, bool force=false) { return solveMultiple(id, requests, N, start, size, crash, force); } /** * Hook kext loading and unloading to access kexts at early stage */ EXPORT void setupKextListening(); /** * Free file buffer resources and effectively make prelinked kext loading impossible */ void freeFileBufferResources(); /** * Activates monitoring functions if necessary */ void activate(); /** * Load handling structure */ class KextHandler { using t_handler = void (*)(KextHandler *); KextHandler(const char * const i, size_t idx, t_handler h, bool l, bool r) : id(i), index(idx), handler(h), loaded(l), reloadable(r) {} public: static KextHandler *create(const char * const i, size_t idx, t_handler h, bool l=false, bool r=false) { return new KextHandler(i, idx, h, l, r); } static void deleter(KextHandler *i NONNULL) { delete i; } void *self {nullptr}; const char * const id {nullptr}; size_t index {0}; mach_vm_address_t address {0}; size_t size {0}; t_handler handler {nullptr}; bool loaded {false}; bool reloadable {false}; }; #ifdef LILU_KEXTPATCH_SUPPORT /** * Enqueue handler processing at kext loading * * @param handler handler to process */ EXPORT void waitOnKext(KextHandler *handler); /** * Update kext handler features * * @param info loaded kext info with features */ void updateKextHandlerFeatures(KextInfo *info); /** * Arbitrary kext find/replace patch */ struct LookupPatch { KextInfo *kext; const uint8_t *find; const uint8_t *replace; size_t size; size_t count; }; /** * Apply a find/replace patch * * @param patch patch to apply */ EXPORT void applyLookupPatch(const LookupPatch *patch); /** * Apply a find/replace patch with additional constraints * * @param patch patch to apply * @param startingAddress start with this address (or kext/kernel lowest address) * @param maxSize maximum size to lookup (or kext/kernel max size) */ EXPORT void applyLookupPatch(const LookupPatch *patch, uint8_t *startingAddress, size_t maxSize); #endif /* LILU_KEXTPATCH_SUPPORT */ /** * Route function to function * * @param from function to route * @param to routed function * @param buildWrapper create entrance wrapper * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param revertible patches could be reverted * * @return wrapper pointer or 0 on success */ EXPORT mach_vm_address_t routeFunction(mach_vm_address_t from, mach_vm_address_t to, bool buildWrapper=false, bool kernelRoute=true, bool revertible=true) DEPRECATE("Use routeMultiple where possible"); /** * Route function to function with long jump * * @param from function to route * @param to routed function * @param buildWrapper create entrance wrapper * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param revertible patches could be reverted * * @return wrapper pointer or 0 on success */ EXPORT mach_vm_address_t routeFunctionLong(mach_vm_address_t from, mach_vm_address_t to, bool buildWrapper=false, bool kernelRoute=true, bool revertible=true) DEPRECATE("Use routeMultiple where possible"); /** * Route function to function with short jump * * @param from function to route * @param to routed function * @param buildWrapper create entrance wrapper * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param revertible patches could be reverted * * @return wrapper pointer or 0 on success */ EXPORT mach_vm_address_t routeFunctionShort(mach_vm_address_t from, mach_vm_address_t to, bool buildWrapper=false, bool kernelRoute=true, bool revertible=true) DEPRECATE("Use routeMultiple where possible"); /** * Route block at assembly level * * @param from address to route * @param opcodes opcodes to insert * @param opnum number of opcodes * @param buildWrapper create entrance wrapper * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * * @return wrapper pointer or 0 on success */ EXPORT mach_vm_address_t routeBlock(mach_vm_address_t from, const uint8_t *opcodes, size_t opnum, bool buildWrapper=false, bool kernelRoute=true); /** * Route virtual function to function * * @param obj OSObject-compatible instance * @param off function offset in a virtual table (arch-neutral, i.e. divided by sizeof(uintptr_t) * @param func function to replace with * @param orgFunc pointer to store the original function * * @return true on success */ template static inline bool routeVirtual(void *obj, size_t off, T func, T *orgFunc=nullptr) { // First OSObject (and similar) field is its virtual table. auto vt = obj ? reinterpret_cast(obj)[0] : nullptr; if (vt) { // Do not try to replace twice! if (vt[off] == func) return false; if (orgFunc) *orgFunc = vt[off]; vt[off] = func; return true; } return false; } /** * Route request to simplify casting and error handling * See routeMultiple. * * symbol symbol to lookup * from solved symbol (assigned by routeMultiple) * to destination address * org trampoline storage to the original symbol */ struct RouteRequest { const char *symbol {nullptr}; mach_vm_address_t from {0}; const mach_vm_address_t to {0}; mach_vm_address_t *org {nullptr}; /** * Construct RouteRequest for wrapping a function * @param s symbol to lookup * @param t destination address * @param o trampoline storage to the original symbol */ template RouteRequest(const char *s, T t, mach_vm_address_t &o) : symbol(s), to(reinterpret_cast(t)), org(&o) { } /** * Construct RouteRequest for wrapping a function * @param s symbol to lookup * @param t destination address * @param o trampoline storage to the original symbol */ template RouteRequest(const char *s, T t, O &o) : RouteRequest(s, t, reinterpret_cast(o)) { } /** * Construct RouteRequest for routing a function * @param s symbol to lookup * @param t destination address */ template RouteRequest(const char *s, T t) : symbol(s), to(reinterpret_cast(t)) { } }; /** * Simple route multiple functions with basic error handling * * @param id kernel item identifier * @param requests an array of requests to replace * @param num requests array size * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * * @return false if it at least one error happened */ EXPORT bool routeMultiple(size_t id, RouteRequest *requests, size_t num, mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false); /** * Simple route multiple functions with basic error handling with long routes * * @param id kernel item identifier * @param requests an array of requests to replace * @param num requests array size * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * * @return false if it at least one error happened */ EXPORT bool routeMultipleLong(size_t id, RouteRequest *requests, size_t num, mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false); /** * Simple route multiple functions with basic error handling with short routes * * @param id kernel item identifier * @param requests an array of requests to replace * @param num requests array size * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * * @return false if it at least one error happened */ EXPORT bool routeMultipleShort(size_t id, RouteRequest *requests, size_t num, mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false); /** * Simple route multiple functions with basic error handling * * @param id kernel item identifier * @param requests an array of requests to replace * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * * @return false if it at least one error happened */ template inline bool routeMultiple(size_t id, RouteRequest (&requests)[N], mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false) { return routeMultiple(id, requests, N, start, size, kernelRoute, force); } /** * Simple route multiple functions with basic error handling with long routes * * @param id kernel item identifier * @param requests an array of requests to replace * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * * @return false if it at least one error happened */ template inline bool routeMultipleLong(size_t id, RouteRequest (&requests)[N], mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false) { return routeMultipleLong(id, requests, N, start, size, kernelRoute, force); } /** * Simple route multiple functions with basic error handling with long routes * * @param id kernel item identifier * @param requests an array of requests to replace * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * * @return false if it at least one error happened */ template inline bool routeMultipleShort(size_t id, RouteRequest (&requests)[N], mach_vm_address_t start=0, size_t size=0, bool kernelRoute=true, bool force=false) { return routeMultipleShort(id, requests, N, start, size, kernelRoute, force); } /** * 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) { 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; } 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; } return false; } /** * Simple find and replace in kernel memory but require both `find` and `replace` buffers to have the same length */ template static inline bool findAndReplace(void *data, size_t dataSize, const uint8_t (&find)[N], const uint8_t (&replace)[N]) { return findAndReplace(data, dataSize, find, N, replace, N); } private: /** * Jump type for routing */ enum class JumpType { Auto, Long, Short, Medium }; /** * The minimal reasonable memory requirement */ static constexpr size_t TempExecutableMemorySize {4096}; /** * As of 10.12 we seem to be not allowed to call vm_ functions from several places including onKextSummariesUpdated. */ static uint8_t tempExecutableMemory[TempExecutableMemorySize]; /** * Offset to tempExecutableMemory that is safe to use */ size_t tempExecutableMemoryOff {0}; /** * Patcher status */ _Atomic(bool) activated = false; /** * Read previous jump destination from function * * @param from formerly routed function * @param jumpType previous jump type * * @return wrapper pointer on success or 0 */ mach_vm_address_t readChain(mach_vm_address_t from, JumpType &jumpType); /** * Created routed trampoline page * * @param func original area * @param min minimal amount of bytes that will be overwritten * @param opcodes opcodes to insert before function * @param opnum number of opcodes * * @return trampoline pointer or 0 */ mach_vm_address_t createTrampoline(mach_vm_address_t func, size_t min, const uint8_t *opcodes=nullptr, size_t opnum=0); /** * Route function to function * * @param from function to route * @param to routed function * @param buildWrapper create entrance wrapper * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param revertible patches could be reverted * @param jumpType jump type to use, relative short or absolute long * @param info info to access address slots to use for shorter routing * @param org write pointer to this variable * * @return wrapper pointer or 0 on success */ mach_vm_address_t routeFunctionInternal(mach_vm_address_t from, mach_vm_address_t to, bool buildWrapper=false, bool kernelRoute=true, bool revertible=true, JumpType jumpType=JumpType::Auto, MachInfo *info=nullptr, mach_vm_address_t *org=nullptr); /** * Simple route multiple functions with basic error handling with long routes * * @param id kernel item identifier * @param requests an array of requests to replace * @param num requests array size * @param start start address range * @param size address range size * @param kernelRoute kernel change requiring memory protection changes and patch reverting at unload * @param force continue on first error * @param jumpType jump type to use, relative short or absolute long * * @return false if it at least one error happened */ 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); #ifdef LILU_KEXTPATCH_SUPPORT /** * Process loaded kext */ void processKext(kmod_info_t *kmod, bool loaded); /** * Process already loaded kexts once at the start * */ void processAlreadyLoadedKexts(); /** * Pointer to loaded kmods for kexts */ kmod_info_t **kextKmods {nullptr}; /** * Called at kext unloading if kext listening is enabled on macOS 10.6 and newer */ static OSReturn onOSKextUnload(void *thisKext); /** * A pointer to OSKext::unload() */ mach_vm_address_t orgOSKextUnload {}; /** * Called at kext loading and unloading if kext listening is enabled on macOS 10.6 and newer */ static void onOSKextSaveLoadedKextPanicList(); /** * A pointer to OSKext::saveLoadedKextPanicList() */ mach_vm_address_t orgOSKextSaveLoadedKextPanicList {}; #if defined(__i386__) /** * Called at kext loading if kext listening is enabled on macOS 10.4 and 10.5 */ static kern_return_t onKmodCreateInternal(kmod_info_t *kmod, kmod_t *id); /** * A pointer to kmod_create_internal() */ mach_vm_address_t orgKmodCreateInternal {}; #endif #endif /* LILU_KEXTPATCH_SUPPORT */ /** * Kernel prelink image in case prelink is used */ MachInfo *prelinkInfo {nullptr}; /** * Loaded kernel items */ evector kinfos; /** * Applied patches */ evector kpatches; #ifdef LILU_KEXTPATCH_SUPPORT /** * Awaiting kext notificators */ evector khandlers; /** * Awaiting already loaded kext list */ bool waitingForAlreadyLoadedKexts {false}; /** * Flag to prevent kext processing during an unload */ bool isKextUnloading {false}; #endif /* LILU_KEXTPATCH_SUPPORT */ /** * Current error code */ Error code {Error::NoError}; static constexpr size_t INVALID {0}; /** * Jump instruction sizes */ static constexpr size_t SmallJump {1 + sizeof(int32_t)}; static constexpr size_t LongJump {6 + sizeof(uintptr_t)}; static constexpr size_t MediumJump {6}; static constexpr uint8_t SmallJumpPrefix {0xE9}; static constexpr uint16_t LongJumpPrefix {0x25FF}; /** * Atomic trampoline generator, wraps jumper into 64-bit or 128-bit storage */ union FunctionPatch { struct PACKED LongPatch { uint16_t opcode; uint32_t argument; uintptr_t disp; uint8_t org[sizeof(uint64_t) - sizeof(uintptr_t) + sizeof(uint16_t)]; } l; static_assert(sizeof(l) == (sizeof(uint64_t) * 2), "Invalid long patch rounding"); struct PACKED MediumPatch { uint16_t opcode; uint32_t argument; uint8_t org[2]; } m; static_assert(sizeof(m) == sizeof(uint64_t), "Invalid medium patch rounding"); struct PACKED SmallPatch { uint8_t opcode; uint32_t argument; uint8_t org[3]; } s; static_assert(sizeof(s) == sizeof(uint64_t), "Invalid small patch rounding"); template inline void sourceIt(mach_vm_address_t source) { // Note, this one violates strict aliasing, but we play with the memory anyway. for (size_t i = 0; i < sizeof(T::org); ++i) reinterpret_cast(this)->org[i] = *reinterpret_cast(source + offsetof(T, org) + i); } uint64_t value64; #if defined(__x86_64__) unsigned __int128 value128; #endif } patch; /** * Possible kernel paths */ #ifdef LILU_COMPRESSION_SUPPORT const char *prelinkKernelPaths[7] { // This is the usual kernel cache place, which often the best thing to use "/System/Library/Caches/com.apple.kext.caches/Startup/kernelcache", // Otherwise fallback to one of the prelinked kernels // Since we always verify the LC_UUID value, trying the kernels could be done in any order. "/System/Library/PrelinkedKernels/prelinkedkernel", // normal "/macOS Install Data/Locked Files/Boot Files/prelinkedkernel", // 10.13 installer "/com.apple.boot.R/prelinkedkernel", // 10.12+ fusion drive installer "/com.apple.boot.S/System/Library/PrelinkedKernels/prelinkedkernel", // 10.11 fusion drive installer "/com.apple.recovery.boot/prelinkedkernel", // recovery "/kernelcache" // 10.7 installer }; #endif const char *kernelPaths[2] { "/System/Library/Kernels/kernel", //since 10.10 "/mach_kernel" }; }; #endif /* kern_patcher_hpp */