├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── lib └── iokernelrw.h ├── misc └── Info.plist └── src ├── IOKernelRW.cpp ├── IOKernelRW.h ├── IOKernelRWUserClient.cpp ├── IOKernelRWUserClient.h └── kmod.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /IOKernelRW.kext 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Siguza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := IOKernelRW 2 | SRC := src 3 | 4 | # Don't use ?= with $(shell ...) 5 | ifndef CXX_FLAGS 6 | CXX_FLAGS := --std=gnu++17 -Wall -O3 -nostdinc -nostdlib -mkernel -DKERNEL -isystem $(shell xcrun --show-sdk-path)/System/Library/Frameworks/Kernel.framework/Headers -Wl,-kext -lcc_kext $(CXXFLAGS) 7 | endif 8 | 9 | .PHONY: all install clean 10 | 11 | all: $(TARGET).kext/Contents/_CodeSignature/CodeResources 12 | 13 | $(TARGET).kext/Contents/MacOS/$(TARGET): $(SRC)/*.cpp $(SRC)/*.h | $(TARGET).kext/Contents/MacOS 14 | $(CXX) -arch arm64e -arch x86_64 -o $@ $(SRC)/*.cpp $(CXX_FLAGS) 15 | 16 | $(TARGET).kext/Contents/Info.plist: misc/Info.plist | $(TARGET).kext/Contents 17 | cp -f $^ $@ 18 | 19 | $(TARGET).kext/Contents/_CodeSignature/CodeResources: $(TARGET).kext/Contents/MacOS/$(TARGET) $(TARGET).kext/Contents/Info.plist 20 | codesign -s - -f $(TARGET).kext 21 | 22 | $(TARGET).kext/Contents $(TARGET).kext/Contents/MacOS: 23 | mkdir -p $@ 24 | 25 | install: all 26 | sudo cp -R $(TARGET).kext /Library/Extensions/ 27 | sudo mkdir -p /usr/local/include/ 28 | sudo cp lib/iokernelrw.h /usr/local/include/ 29 | 30 | clean: 31 | rm -rf $(TARGET).kext 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IOKernelRW 2 | 3 | Custom kext for kernel r/w on macOS. 4 | 5 | If you don't know what that means, **stay away!** Dark things sleep in the abyss. 6 | 7 | Needless to say, this will lower the security of your operating system to zero. That's the whole point. And with any luck it will also lower its stability to zero. 8 | 9 | ### Building 10 | 11 | ``` 12 | make 13 | ``` 14 | 15 | ### Installation 16 | 17 | 1. Boot into recoveryOS and run: 18 | 19 | ``` 20 | bputil -k 21 | csrutil disable 22 | ``` 23 | 2. Reboot back into macOS and run (from within this folder): 24 | 25 | ``` 26 | make install 27 | ``` 28 | 3. Follow the OS prompts to approve the kext and reboot. 29 | 30 | If you ever want to **undo** the above, you can just run `bputil -f` from within macOS (while connected to the internet) and reboot (unless you had previously lowered the security of your installation, in which case I trust you know how to adjust it back to how it was before). 31 | 32 | ### Usage 33 | 34 | This creates an `IOKernelRW` service node in the IORegistry, on which an `IOKernelRWUserClient` can be opened by processes with the `com.apple.security.siguza.kernel-rw` entitlement. 35 | 36 | The userclient exports a few external methods, all of which only take scalar inputs and provide no output (other than the return value). The following external methods exist: 37 | 38 | | ID | Name | Arg 0 | Arg 1 | Arg 2 | Arg 3 | 39 | | :--: | :---------- | :------------------- | :------------------------ | :----- | :-------------------- | 40 | | 0 | `readVirt` | source address (kVA) | destination address (uVA) | length | _N/A_ | 41 | | 1 | `writeVirt` | source address (uVA) | destination address (kVA) | length | _N/A_ | 42 | | 2 | `readPhys` | source address (PA) | destination address (uVA) | length | alignment1 | 43 | | 3 | `writePhys` | source address (uVA) | destination address (PA) | length | alignment1 | 44 | 45 | 1 Values of 4 or 8 force all accesses to physical memory to be uncached and use the specified granularity as word size in bytes. Physical address and length must be a multiple of the chosen alignment. A value of 0 maps the physical range as cacheable instead, and puts no constraint on alignment or access granularity. 46 | 47 | A header library with convenience wrappers exists in [`lib/iokernelrw.h`](https://github.com/Siguza/IOKernelRW/blob/master/lib/iokernelrw.h). 48 | 49 | ### Future Plans 50 | 51 | The goal of this project is to provide capabilities that fall somewhere between what a kernel LPE would provide you and whatever primitives are useful for research and poking at hardware, with some degree of minimalism. 52 | 53 | Things I thought about implementing: 54 | 55 | - **Mapping memory into userspace** 56 | Would remove the overhead of going through MIG and mapping/unmapping memory for each access, but is probably less powerful than direct accesses from kernel mode due to PPL constraints on what can be mapped into userspace? Would also require keeping track of `IOMemoryDescriptor` objects attached to the userclient, and that would require state management, reference counting, locking etc. 57 | - **Kernel function calling** 58 | Reasonable request. Might implement this if I ever feel like fiddling with PAC. 59 | - **PAC signature forging** 60 | Should be constructable with a kernel calling primitive. 61 | - **Kernel base leak** 62 | Should be constructable via phys read from something like CTRR lockdown registers or IORVBAR. 63 | 64 | ### License 65 | 66 | [MIT](https://github.com/Siguza/IOKernelRW/blob/master/LICENSE). 67 | -------------------------------------------------------------------------------- /lib/iokernelrw.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBIOKERNELRW_H 2 | #define LIBIOKERNELRW_H 3 | 4 | #include 5 | #include 6 | 7 | static inline io_connect_t iokernelrw_open(void) 8 | { 9 | io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOKernelRW")); 10 | if(!MACH_PORT_VALID(service)) 11 | { 12 | return MACH_PORT_NULL; 13 | } 14 | 15 | io_connect_t client = MACH_PORT_NULL; 16 | kern_return_t ret = IOServiceOpen(service, mach_task_self(), 0, &client); 17 | IOObjectRelease(service); 18 | if(ret != KERN_SUCCESS) 19 | { 20 | return MACH_PORT_NULL; 21 | } 22 | return client; 23 | } 24 | 25 | static inline kern_return_t iokernelrw_read(io_connect_t client, uint64_t from, void *to, uint64_t len) 26 | { 27 | uint64_t in[] = { from, (uint64_t)to, len }; 28 | return IOConnectCallScalarMethod(client, 0, in, 3, NULL, NULL); 29 | } 30 | 31 | static inline kern_return_t iokernelrw_write(io_connect_t client, void *from, uint64_t to, uint64_t len) 32 | { 33 | uint64_t in[] = { (uint64_t)from, to, len }; 34 | return IOConnectCallScalarMethod(client, 1, in, 3, NULL, NULL); 35 | } 36 | 37 | static inline kern_return_t iokernelrw_read_phys(io_connect_t client, uint64_t from, void *to, uint64_t len, uint8_t align) 38 | { 39 | uint64_t in[] = { from, (uint64_t)to, len, align }; 40 | return IOConnectCallScalarMethod(client, 2, in, 4, NULL, NULL); 41 | } 42 | 43 | static inline kern_return_t iokernelrw_write_phys(io_connect_t client, void *from, uint64_t to, uint64_t len, uint8_t align) 44 | { 45 | uint64_t in[] = { (uint64_t)from, to, len, align }; 46 | return IOConnectCallScalarMethod(client, 3, in, 4, NULL, NULL); 47 | } 48 | 49 | #endif /* LIBIOKERNELRW_H */ 50 | -------------------------------------------------------------------------------- /misc/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | IOKernelRW 7 | CFBundleIdentifier 8 | net.siguza.iokernelrw 9 | CFBundleName 10 | IOKernelRW 11 | CFBundlePackageType 12 | KEXT 13 | CFBundleVersion 14 | 1 15 | IOKitPersonalities 16 | 17 | IOKernelRW 18 | 19 | CFBundleIdentifier 20 | net.siguza.iokernelrw 21 | IOClass 22 | IOKernelRW 23 | IOMatchCategory 24 | IOKernelRW 25 | IOProviderClass 26 | IOResources 27 | IOResourceMatch 28 | IOBSD 29 | IOUserClientClass 30 | IOKernelRWUserClient 31 | 32 | 33 | OSBundleLibraries 34 | 35 | com.apple.kpi.bsd 36 | 11.0 37 | com.apple.kpi.dsep 38 | 11.0 39 | com.apple.kpi.iokit 40 | 11.0 41 | com.apple.kpi.libkern 42 | 11.0 43 | com.apple.kpi.mach 44 | 11.0 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/IOKernelRW.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "IOKernelRW.h" 3 | 4 | #define super IOService 5 | OSDefineMetaClassAndFinalStructors(IOKernelRW, IOService) 6 | 7 | bool IOKernelRW::start(IOService *provider) 8 | { 9 | if(!super::start(provider)) 10 | { 11 | return false; 12 | } 13 | 14 | registerService(); 15 | 16 | return true; 17 | } 18 | -------------------------------------------------------------------------------- /src/IOKernelRW.h: -------------------------------------------------------------------------------- 1 | #ifndef IOKERNELRW_H 2 | #define IOKERNELRW_H 3 | 4 | #include 5 | 6 | class IOKernelRW : public IOService 7 | { 8 | OSDeclareFinalStructors(IOKernelRW); 9 | public: 10 | virtual bool start(IOService *provider) override; 11 | }; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/IOKernelRWUserClient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "IOKernelRWUserClient.h" 8 | 9 | #define super IOUserClient 10 | OSDefineMetaClassAndFinalStructors(IOKernelRWUserClient, IOUserClient) 11 | 12 | bool IOKernelRWUserClient::initWithTask(task_t owningTask, void *securityID, uint32_t type) 13 | { 14 | if(!super::initWithTask(owningTask, securityID, type)) 15 | { 16 | return false; 17 | } 18 | 19 | bool allow = false; 20 | OSObject *entitlement = copyClientEntitlement(owningTask, "com.apple.security.siguza.kernel-rw"); 21 | if(entitlement) 22 | { 23 | allow = (entitlement == kOSBooleanTrue); 24 | entitlement->release(); 25 | } 26 | return allow; 27 | } 28 | 29 | IOReturn IOKernelRWUserClient::externalMethod(uint32_t selector, IOExternalMethodArguments *args, IOExternalMethodDispatch *dispatch, OSObject *target, void *reference) 30 | { 31 | static const IOExternalMethodDispatch methods[] = 32 | { 33 | /* 0 */ { (IOExternalMethodAction)&IOKernelRWUserClient::readVirt, 3, 0, 0, 0 }, 34 | /* 1 */ { (IOExternalMethodAction)&IOKernelRWUserClient::writeVirt, 3, 0, 0, 0 }, 35 | /* 2 */ { (IOExternalMethodAction)&IOKernelRWUserClient::readPhys, 4, 0, 0, 0 }, 36 | /* 3 */ { (IOExternalMethodAction)&IOKernelRWUserClient::writePhys, 4, 0, 0, 0 }, 37 | }; 38 | 39 | if(selector < sizeof(methods)/sizeof(methods[0])) 40 | { 41 | dispatch = const_cast(&methods[selector]); 42 | target = this; 43 | } 44 | 45 | return super::externalMethod(selector, args, dispatch, target, reference); 46 | } 47 | 48 | IOReturn IOKernelRWUserClient::readVirt(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args) 49 | { 50 | int r = copyout((const void*)args->scalarInput[0], (user_addr_t)args->scalarInput[1], args->scalarInput[2]); 51 | return r == 0 ? kIOReturnSuccess : kIOReturnVMError; 52 | } 53 | 54 | IOReturn IOKernelRWUserClient::writeVirt(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args) 55 | { 56 | int r = copyin((user_addr_t)args->scalarInput[0], (void*)args->scalarInput[1], args->scalarInput[2]); 57 | return r == 0 ? kIOReturnSuccess : kIOReturnVMError; 58 | } 59 | 60 | IOReturn IOKernelRWUserClient::physcopy(uint64_t src, uint64_t dst, uint64_t len, uint64_t alignment, IODirection direction) 61 | { 62 | IOReturn retval = kIOReturnError; 63 | IOReturn ret; 64 | IOOptionBits mapOptions = 0; 65 | 66 | uint64_t va = direction == kIODirectionIn ? dst : src; 67 | uint64_t pa = direction == kIODirectionIn ? src : dst; 68 | 69 | switch(alignment) 70 | { 71 | case 0: 72 | break; 73 | 74 | case 4: 75 | case 8: 76 | if((pa % alignment) != 0 || (len % alignment) != 0) 77 | { 78 | return kIOReturnNotAligned; 79 | } 80 | mapOptions |= kIOMapInhibitCache; 81 | break; 82 | 83 | default: 84 | return kIOReturnBadArgument; 85 | } 86 | 87 | uint64_t voff = va & PAGE_MASK; 88 | uint64_t poff = pa & PAGE_MASK; 89 | va &= ~(uint64_t)PAGE_MASK; 90 | pa &= ~(uint64_t)PAGE_MASK; 91 | 92 | IOMemoryDescriptor *vDesc = IOMemoryDescriptor::withAddressRange((mach_vm_address_t)va, (len + voff + PAGE_MASK) & ~(uint64_t)PAGE_MASK, direction == kIODirectionIn ? kIODirectionOut : kIODirectionIn, current_task()); 93 | if(!vDesc) 94 | { 95 | retval = iokit_vendor_specific_err(1); 96 | } 97 | else 98 | { 99 | ret = vDesc->prepare(); 100 | if(ret != kIOReturnSuccess) 101 | { 102 | retval = ret; 103 | } 104 | else 105 | { 106 | IOMemoryMap *vMap = vDesc->map(); 107 | if(!vMap) 108 | { 109 | retval = iokit_vendor_specific_err(2); 110 | } 111 | else 112 | { 113 | IOMemoryDescriptor *pDesc = IOMemoryDescriptor::withPhysicalAddress((IOPhysicalAddress)pa, (len + poff + PAGE_MASK) & ~(uint64_t)PAGE_MASK, direction); 114 | if(!pDesc) 115 | { 116 | retval = iokit_vendor_specific_err(3); 117 | } 118 | else 119 | { 120 | IOMemoryMap *pMap = pDesc->map(mapOptions); 121 | if(!pMap) 122 | { 123 | retval = iokit_vendor_specific_err(4); 124 | } 125 | else 126 | { 127 | IOVirtualAddress v = vMap->getVirtualAddress(); 128 | IOVirtualAddress p = pMap->getVirtualAddress(); 129 | const void *from = (const void*)(direction == kIODirectionIn ? p + poff : v + voff); 130 | void *to = ( void*)(direction == kIODirectionIn ? v + voff : p + poff); 131 | 132 | switch(alignment) 133 | { 134 | case 0: 135 | bcopy(from, to, len); 136 | break; 137 | 138 | case 4: 139 | for(size_t i = 0; i < len/4; ++i) 140 | { 141 | ((volatile uint32_t*)to)[i] = ((const volatile uint32_t*)from)[i]; 142 | } 143 | break; 144 | 145 | case 8: 146 | for(size_t i = 0; i < len/8; ++i) 147 | { 148 | ((volatile uint64_t*)to)[i] = ((const volatile uint64_t*)from)[i]; 149 | } 150 | break; 151 | } 152 | 153 | retval = kIOReturnSuccess; 154 | 155 | pMap->release(); 156 | } 157 | pDesc->release(); 158 | } 159 | vMap->release(); 160 | } 161 | vDesc->complete(); 162 | } 163 | vDesc->release(); 164 | } 165 | 166 | return retval; 167 | } 168 | 169 | IOReturn IOKernelRWUserClient::readPhys(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args) 170 | { 171 | return physcopy(args->scalarInput[0], args->scalarInput[1], args->scalarInput[2], args->scalarInput[3], kIODirectionIn); 172 | } 173 | 174 | IOReturn IOKernelRWUserClient::writePhys(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args) 175 | { 176 | return physcopy(args->scalarInput[0], args->scalarInput[1], args->scalarInput[2], args->scalarInput[3], kIODirectionOut); 177 | } 178 | -------------------------------------------------------------------------------- /src/IOKernelRWUserClient.h: -------------------------------------------------------------------------------- 1 | #ifndef IOKERNELRWUSERCLIENT_H 2 | #define IOKERNELRWUSERCLIENT_H 3 | 4 | #include 5 | 6 | class IOKernelRWUserClient : public IOUserClient 7 | { 8 | OSDeclareFinalStructors(IOKernelRWUserClient); 9 | public: 10 | virtual bool initWithTask(task_t owningTask, void *securityID, uint32_t type) override; 11 | virtual IOReturn externalMethod(uint32_t selector, IOExternalMethodArguments *args, IOExternalMethodDispatch *dispatch, OSObject *target, void *reference) override; 12 | 13 | private: 14 | static IOReturn readVirt(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args); 15 | static IOReturn writeVirt(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args); 16 | static IOReturn readPhys(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args); 17 | static IOReturn writePhys(IOKernelRWUserClient *client, void *reference, IOExternalMethodArguments *args); 18 | 19 | static IOReturn physcopy(uint64_t src, uint64_t dst, uint64_t len, uint64_t alignment, IODirection direction); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/kmod.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | 4 | #include 5 | 6 | static kern_return_t start(kmod_info_t *ki, void *data) 7 | { 8 | return KERN_SUCCESS; 9 | } 10 | 11 | static kern_return_t stop(kmod_info_t *ki, void *data) 12 | { 13 | return KERN_SUCCESS; 14 | } 15 | 16 | __attribute__((visibility("default"))) KMOD_EXPLICIT_DECL(net.siguza.iokernelrw, "1.0.0", start, stop); 17 | 18 | } 19 | --------------------------------------------------------------------------------