├── .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 |
--------------------------------------------------------------------------------