// // kern_user.hpp // Lilu // // Copyright © 2016-2017 vit9696. All rights reserved. // #ifndef kern_user_hpp #define kern_user_hpp #include #include #include #include class UserPatcher { public: /** * Initialise UserPatcher, prepare for modifications * * @param patcher kernel patcher instance * @param preferSlowMode policy boot type * * @return true on success */ bool init(KernelPatcher &patcher, bool preferSlowMode); /** * Deinitialise UserPatcher, must be called regardless of the init error */ void deinit(); /** * Obtain page protection * * @param map vm map * @param addr map offset * * @return protection */ EXPORT vm_prot_t getPageProtection(vm_map_t map, vm_map_address_t addr); /** * Mach segment/section references for patch locations */ enum FileSegment : uint32_t { SegmentsTextStart, SegmentTextText = SegmentsTextStart, SegmentTextStubs, SegmentTextConst, SegmentTextCstring, SegmentTextUstring, SegmentsTextEnd = SegmentTextUstring, SegmentsDataStart, SegmentDataConst = SegmentsDataStart, SegmentDataCfstring, SegmentDataCommon, SegmentsDataEnd = SegmentDataCommon, SegmentTotal }; /** * Mach segment names kept in sync with FileSegment */ const char *fileSegments[SegmentTotal] { "__TEXT", "__TEXT", "__TEXT", "__TEXT", "__TEXT", "__DATA", "__DATA", "__DATA" }; /** * Mach section names kept in sync with FileSegment */ const char *fileSections[SegmentTotal] { "__text", "__stubs", "__const", "__cstring", "__ustring", "__const", "__cfstring", "__common" }; /** * Binary modification patches flags */ enum BinaryModPatchFlags { /* * Only applies to one process, not globally. */ LocalOnly = 1 }; /** * Structure holding lookup-style binary patches */ struct BinaryModPatch { cpu_type_t cpu; uint32_t flags; const uint8_t *find; const uint8_t *replace; size_t size; size_t skip; size_t count; FileSegment segment; uint32_t section; }; #if defined(__i386__) static_assert(sizeof(BinaryModPatch) == 36, "BinaryModPatch 32-bit ABI compatibility failure"); #elif defined(__x86_64__) static_assert(sizeof(BinaryModPatch) == 56, "BinaryModPatch 64-bit ABI compatibility failure"); #else #error Unsupported arch. #endif /** * Structure describing the modifications for the binary */ struct BinaryModInfo { const char *path; BinaryModPatch *patches; size_t count; vm_address_t startTEXT; vm_address_t endTEXT; vm_address_t startDATA; vm_address_t endDATA; }; /** * Structure describing relevant processes run */ struct ProcInfo { /** * Process matching flags */ enum ProcFlags { MatchExact = 0, MatchAny = 1, MatchPrefix = 2, MatchSuffix = 4, MatchMask = MatchExact | MatchAny | MatchPrefix | MatchSuffix }; /** * Unused (aka disabled) proc info section */ static constexpr uint32_t SectionDisabled {0}; const char *path {nullptr}; uint32_t len {0}; uint32_t section {SectionDisabled}; uint32_t flags {MatchExact}; }; /** * External callback type for on process invocation * * @param user user provided pointer at registering * @param patcher user patcher instance * @param map process image vm_map * @param path path to the binary absolute or relative * @param len path length excluding null terminator */ using t_BinaryLoaded = void (*)(void *user, UserPatcher &patcher, vm_map_t map, const char *path, size_t len); /** * Instructs user patcher to do further actions * * @param procs process list * @param procNum process list size * @param mods modification list * @param modNum modification list size * @param callback callback function * @param user pointer that will be passed to the callback function */ bool registerPatches(ProcInfo **procs, size_t procNum, BinaryModInfo **mods, size_t modNum, t_BinaryLoaded callback, void *user); /** * Reads current process header * * @param map vm map * @param header Mach-O header * * @return false on failure */ EXPORT bool getTaskHeader(vm_map_t map, mach_header_64 &header); /** * Disables dyld_shared_cache for the current process * * @param map vm map * * @return false on mach image failure */ EXPORT bool injectRestrict(vm_map_t map); /** * Injects payload into the process right after the header with EP replacement. * * @param map vm map * @param payload code * @param size code size (up to PAGE_SIZE) * @param ep original entrypoint (may be written to code before copying) * * @return false on mach image failure */ EXPORT bool injectPayload(vm_map_t map, uint8_t *payload, size_t size, void *ep=nullptr); /** * Allocates a new segment in the process. * * @param map vm map * @param addr allocation address (e.g. a little below SHARED_REGION_BASE_X86_64) * @param payload code * @param size code size (must be PAGE_SIZE-aligned) * @param prot segment protection * * @return allocated address or 0 on failure */ EXPORT vm_address_t injectSegment(vm_map_t taskPort, vm_address_t addr, uint8_t *payload, size_t size, vm_prot_t prot); /** * Activates monitoring functions if necessary */ void activate(); /** * Get active dyld shared cache path. * * @return shared cache path constant */ EXPORT static const char *getSharedCachePath() DEPRECATE("Use matchSharedCachePath, macOS 12 has multiple caches"); /** * Check if the supplied path matches dyld shared cache path. * * @param path image path * * @return shared cache path constant */ EXPORT static bool matchSharedCachePath(const char *path); private: /** * Kernel function prototypes */ using vm_shared_region_t = void *; using shared_file_mapping_np = void *; using t_currentMap = vm_map_t (*)(void); using t_getTaskMap = vm_map_t (*)(task_t); using t_getMapMin = vm_map_offset_t (*)(vm_map_t); using t_vmMapSwitchProtect = void (*)(vm_map_t, boolean_t); using t_vmMapCheckProtection = boolean_t (*)(vm_map_t, vm_map_offset_t, vm_map_offset_t, vm_prot_t); using t_vmMapReadUser = kern_return_t (*)(vm_map_t, vm_map_address_t, const void *, vm_size_t); using t_vmMapWriteUser = kern_return_t (*)(vm_map_t, const void *, vm_map_address_t, vm_size_t); /** * Original kernel function trampolines */ mach_vm_address_t orgCodeSignValidatePageWrapper {}; mach_vm_address_t orgCodeSignValidateRangeWrapper {}; mach_vm_address_t orgVmSharedRegionMapFile {}; mach_vm_address_t orgVmSharedRegionSlide {}; mach_vm_address_t orgVmSharedRegionSlideMojave {}; t_currentMap orgCurrentMap {nullptr}; t_getMapMin orgGetMapMin {nullptr}; t_getTaskMap orgGetTaskMap {nullptr}; t_vmMapSwitchProtect orgVmMapSwitchProtect {nullptr}; t_vmMapCheckProtection orgVmMapCheckProtection {nullptr}; t_vmMapReadUser orgVmMapReadUser {nullptr}; t_vmMapWriteUser orgVmMapWriteUser {nullptr}; mach_vm_address_t orgTaskSetMainThreadQos {}; /** * Kernel function wrappers */ static boolean_t codeSignValidatePageWrapper(void *blobs, memory_object_t pager, memory_object_offset_t page_offset, const void *data, unsigned *tainted); static boolean_t codeSignValidateRangeWrapper(void *blobs, memory_object_t pager, memory_object_offset_t range_offset, const void *data, memory_object_size_t data_size, unsigned *tainted); static vm_map_t swapTaskMap(task_t task, thread_t thread, vm_map_t map, boolean_t doswitch); static vm_map_t vmMapSwitch(vm_map_t map); static kern_return_t vmSharedRegionMapFile(vm_shared_region_t shared_region, unsigned int mappings_count, shared_file_mapping_np *mappings, memory_object_control_t file_control, memory_object_size_t file_size, void *root_dir, uint32_t slide, user_addr_t slide_start, user_addr_t slide_size); static void execsigs(proc_t p, thread_t thread); static int vmSharedRegionSlide(uint32_t slide, mach_vm_offset_t entry_start_address, mach_vm_size_t entry_size, mach_vm_offset_t slide_start, mach_vm_size_t slide_size, memory_object_control_t sr_file_control); static int vmSharedRegionSlideMojave(uint32_t slide, mach_vm_offset_t entry_start_address, mach_vm_size_t entry_size, mach_vm_offset_t slide_start, mach_vm_size_t slide_size, mach_vm_offset_t slid_mapping, memory_object_control_t sr_file_control); static void taskSetMainThreadQos(task_t task, thread_t main_thread); /** * Applies page patches to the memory range * * @param data_ptr pages in kernel memory * @param data_size data size divisible by PAGE_SIZE */ void performPagePatch(const void *data_ptr, size_t data_size); /** * dyld shared cache map entry structure */ struct MapEntry { const char *filename; size_t length; vm_address_t startTEXT; vm_address_t endTEXT; vm_address_t startDATA; vm_address_t endDATA; }; /** * Obtains __TEXT addresses from .map files * * @param mapBuf read .map file * @param mapSz .map file size * @param mapEntries entries to look for * @param nentries number of entries * * @return number of entries found */ size_t mapAddresses(const char *mapBuf, MapEntry *mapEntries, size_t nentries); /** * Stored ASLR slide of dyld shared cache */ uint32_t storedSharedCacheSlide {0}; /** * Set once shared cache slide is defined */ bool sharedCacheSlideStored {false}; /** * Set on init to decide on whether to use __RESTRICT or patch dyld shared cache */ bool patchDyldSharedCache {false}; /** * Kernel patcher instance */ KernelPatcher *patcher {nullptr}; /** * Pending callback entry */ struct PendingUser { /** * Patch requested for path */ char path[MAXPATHLEN] {}; /** * Patch requested for path */ uint32_t pathLen {0}; }; /** * Stored pending callback */ ThreadLocal pending; /** * Current minimal proc name length */ uint32_t currentMinProcLength {0}; /** * Provided binary modification list */ BinaryModInfo **binaryMod {nullptr}; /** * Amount of provided binary modifications */ size_t binaryModSize {0}; /** * Provided process list */ ProcInfo **procInfo {nullptr}; /** * Amount of provided processes */ size_t procInfoSize {0}; /** * Provided global callback for on proc invocation */ ppair userCallback {}; /** * Applies dyld shared cache patches * * @param map current process map * @param slide ASLR offset * @param cpu cache cpu type * @param restore true to rollback the changes */ void patchSharedCache(vm_map_t map, uint32_t slide, cpu_type_t cpu, bool applyChanges=true); /** * Structure holding userspace lookup patches */ struct LookupStorage { struct PatchRef { size_t i {0}; evector pageOffs; evector segOffs; static PatchRef *create() { return new PatchRef; } static void deleter(PatchRef *r NONNULL) { r->pageOffs.deinit(); r->segOffs.deinit(); delete r; } }; const BinaryModInfo *mod {nullptr}; evector refs; Page *page {nullptr}; vm_address_t pageOff {0}; static LookupStorage *create() { auto p = new LookupStorage; if (p) { p->page = Page::create(); if (!p->page) { deleter(p); p = nullptr; } } return p; } static void deleter(LookupStorage *p NONNULL) { if (p->page) { Page::deleter(p->page); p->page = nullptr; } p->refs.deinit(); delete p; } }; struct Lookup { uint32_t offs[4] {}; static constexpr size_t matchNum {4}; evector c[matchNum]; }; evector lookupStorage; Lookup lookup; /** * Restrict 64-bit entry overlapping DYLD_SHARED_CACHE to enforce manual library loading */ segment_command_64 restrictSegment64 { LC_SEGMENT_64, sizeof(segment_command_64), "__RESTRICT", SHARED_REGION_BASE_X86_64, 1, 0, 0, 0, 0, 0, 0 }; /** * Restrict 32-bit entry overlapping DYLD_SHARED_CACHE to enforce manual library loading */ segment_command restrictSegment32 { LC_SEGMENT, sizeof(segment_command), "__RESTRICT", SHARED_REGION_BASE_I386, 1, 0, 0, 0, 0, 0, 0 }; /** * Temporary buffer for reading image data */ uint8_t tmpBufferData[PAGE_SIZE*3] {}; /** * Kernel auth listener handle */ kauth_listener_t listener {nullptr}; /** * Patcher status */ _Atomic(bool) activated = false; /** * Validation cookie */ void *cookie {nullptr}; /** * Flags for codesign (PL) offset in struct proc. (uint32_t p_csflags) */ size_t csFlagsOffset {0}; /** * Exec callback * * @param credential kauth credential * @param idata cookie * @param action passed action, we only need KAUTH_FILEOP_EXEC * @param arg0 pointer to vnode (vnode *) for executable * @param arg1 pointer to path (char *) to executable * * @return 0 to allow further execution */ static int execListener(kauth_cred_t /* credential */, void *idata, kauth_action_t action, uintptr_t /* arg0 */, uintptr_t arg1, uintptr_t, uintptr_t); /** * Unrestricted vm_protect, that takes care of Mojave codesign limitations for everyone's good. * See vm_protect description. */ kern_return_t vmProtect(vm_map_t map, vm_offset_t start, vm_size_t size, boolean_t set_maximum, vm_prot_t new_protection); /** * Callback invoked at process loading * * @param path binary path * @param len path length */ void onPath(const char *path, uint32_t len); /** * Reads files from BinaryModInfos and prepares lookupStorage * * @return true on success */ bool loadFilesForPatching(); /** * Reads dyld shared cache and obtains segment offsets * * @return true on success */ bool loadDyldSharedCacheMapping(); /** * Prepares quick page lookup based on lookupStorage values * * @return true on success */ bool loadLookups(); /** * Hooks memory access to get ready for patching * * @return true on success */ bool hookMemoryAccess(); /** * Peforms the actual binary patching * * @param map vm map * @param path binary path * @param len path length */ void patchBinary(vm_map_t map, const char *path, uint32_t len); /** * DYLD shared cache map path for 10.10+ on Haswell */ static constexpr const char *SharedCacheMapHaswell {"/private/var/db/dyld/dyld_shared_cache_x86_64h.map"}; /** * DYLD shared cache map path for all other systems and older CPUs */ static constexpr const char *SharedCacheMapLegacy {"/private/var/db/dyld/dyld_shared_cache_x86_64.map"}; /** * DYLD shared cache path on Haswell+ before Big Sur */ static constexpr const char *sharedCacheHaswell {"/private/var/db/dyld/dyld_shared_cache_x86_64h"}; /** * DYLD shared cache path on older systems before Big Sur */ static constexpr const char *sharedCacheLegacy {"/private/var/db/dyld/dyld_shared_cache_x86_64"}; /** * DYLD shared cache map path on Haswell+ on Big Sur */ static constexpr const char *bigSurSharedCacheMapHaswell {"/System/Library/dyld/dyld_shared_cache_x86_64h.map"}; /** * DYLD shared cache map path on older systems on Big Sur */ static constexpr const char *bigSurSharedCacheMapLegacy {"/System/Library/dyld/dyld_shared_cache_x86_64.map"}; /** * DYLD shared cache path on Haswell+ on Big Sur */ static constexpr const char *bigSurSharedCacheHaswell {"/System/Library/dyld/dyld_shared_cache_x86_64h"}; /** * DYLD shared cache path on older systems on Big Sur */ static constexpr const char *bigSurSharedCacheLegacy {"/System/Library/dyld/dyld_shared_cache_x86_64"}; }; #endif /* kern_user_hpp */