Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
launchd-portrep — CVE-2018-4280: Mach port replacement vulnerability in launchd on macOS 10.13.5 leading to local privilege escalation and SIP bypass. | Kitploit
Tools/GitHubGitHub/bazad/launchd-portrep
Privilege EscalationExploit FrameworksExploitationPenetration TestingRed TeamingBinary Exploitation
GitHubbazad/launchd-portrep

launchd-portrep

CVE-2018-4280: Mach port replacement vulnerability in launchd on macOS 10.13.5 leading to local privilege escalation and SIP bypass.

View Repository
596157 years agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

launchd-portrep

launchd-portrep is an exploit for a port replacement vulnerability in launchd, the initial userspace process and service management daemon on macOS. By sending a crafted Mach message to the bootstrap port, launchd can be coerced into deallocating its send right for any Mach port to which the attacker also has a send right. This allows the attacker to impersonate any launchd service it can look up to the rest of the system.

The vulnerability

Launchd multiplexes multiple different Mach message handlers over its main port, including a MIG handler for exception messages. If a process sends a mach_exception_raise or mach_exception_raise_state_identity message to its own bootstrap port, launchd will receive and process that message as a host-level exception.

Unfortunately, launchd's handling of these messages is buggy. If the exception type is EXC_CRASH, then launchd will deallocate the thread and task ports sent in the message and then return KERN_FAILURE from the service routine, causing the MIG system to deallocate the thread and task ports again. (The assumption is that if a service routine returns success, then it has taken ownership of all resources in the Mach message, while if the service routine returns an error, then it has taken ownership of none of the resources.)

Here is the code from launchd's service routine for mach_exception_raise messages, decompiled using IDA/Hex-Rays and lightly edited for readability:

kern_return_t __fastcall
catch_mach_exception_raise(                             // (a) The service routine is
        mach_port_t           exception_port,           //     called with values directly
        mach_port_t           thread,                   //     from the Mach message
        mach_port_t           task,                     //     sent by the client. The
        unsigned int          exception,                //     thread and task ports could
        mach_exception_data_t code,                     //     be arbitrary send rights.
        unsigned int          codeCnt)
{
    kern_return_t kr;      // eax@1 MAPDST
    kern_return_t result;  // eax@10
    int pid;               // [rsp+14h] [rbp-43Ch]@1
    char codes_str[1024];  // [rsp+20h] [rbp-430h]@5
    __int64 __stack_guard; // [rsp+420h] [rbp-30h]@1

    __stack_guard = *__stack_chk_guard_ptr;
    pid = -1;
    kr = pid_for_task(task, &pid);
    if ( kr )
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    if ( codeCnt )
    {
        do
        {
            __snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", *code);
            ++code;
            --codeCnt;
        }
        while ( codeCnt );
    }
    launchd_log_2(
        0LL,
        3LL,
        "Host-level exception raised: pid = %d, thread = 0x%x, "
            "exception type = 0x%x, codes = { %s }",
        pid,
        thread,
        exception,
        codes_str);
    kr = deallocate_mach_port(thread);                  // (b) The "thread" port sent in
    if ( kr )                                           //     the message is deallocated.
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    kr = deallocate_mach_port(task);                    // (c) The "task" port sent in the
    if ( kr )                                           //     message is deallocated.
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    result = 0;
    if ( *__stack_chk_guard_ptr == __stack_guard )
    {
        LOBYTE(result) = exception == 10;               // (d) If the exception type is 10
        result *= 5;                                    //     (EXC_CRASH), then an error
    }                                                   //     KERN_FAILURE is returned.
    return result;                                      //     MIG will deallocate the
}                                                       //     ports again.

This double-deallocate of the port names is problematic because a process can set any ports it wants as the task and thread ports in the exception message. Launchd performs no checks that the received send rights actually correspond to a thread and a task; the ports could, for example, be send rights to ports already in launchd's IPC space. Then the double-deallocate would actually cause launchd to drop a user reference on one of its own ports.

This bug can be exploited to free launchd's send right to any Mach port to which the attacking process also has a send right. In particular, if the attacking process can look up a system service using launchd, then it can free launchd's send right to that service and then impersonate the service to the rest of the system. After that there are many different routes to gain system privileges.

Exploit strategy to get task_for_pid-allow

This bug is a less general version of CVE-2016-7637, a Mach port user reference handling issue in XNU discovered by Ian Beer that allowed processes to free Mach ports in other processes. Ian Beer exploited that vulnerability on macOS by replacing launchd's send right to the com.apple.CoreServices.coreservicesd endpoint and impersonating coreservicesd to the rest of the system. Coreservicesd is an attractive target because it is one of a few services to which clients will send their task port in a Mach message. By replacing launchd's send right to coreservicesd with his own port and then triggering privileged clients to look up and communicate with coreservicesd, he was able to obtain the task port for a privileged process and then execute code within that process.

Since the behavior on macOS hasn't changed, I basically copied Ian Beer's exploit strategy for this vulnerability. We send exception messages to launchd containing coreservicesd's service port until we free launchd's send right to that port. We can detect when we've freed the right by calling bootstrap_look_up() again on the service: if launchd returns an invalid port name, then we've successfully freed launchd's send right to the port. Then, we repeatedly register and unregister a large number of services with launchd until one of the services we register is assigned the same Mach port name in launchd's IPC space as the original coreservicesd port. At this point, any process that looks up com.apple.CoreServices.coreservicesd in launchd will receive a send right to our fake service rather than the real coreservicesd. We then run a MITM server on the fake service port, inspecting all Mach ports in the messages received from clients before sending them along to the real coreservicesd. At this point we send a message to sysdiagnose causing it to run a tailspin, which causes sysdiagnose to connect to our fake coreservicesd port and send us its task port. Since sysdiagnose has the task_for_pid-allow entitlement, we can now get the task port for any process.

In order to (mostly) restore proper functioning of the system, we use sysdiagnose to obtain launchd's task port, and then use launchd's task port to replace launchd's send right to our fake service port back with a send right to the real coreservicesd. That way future clients can actually reach coreservicesd.

One problem I've noticed with this approach is that the system seems to hang on shutdown for a short while. I'm assuming that this is because tampering with launchd's ports messes up some of launchd's accounting or port notifications. I haven't investigated this issue further, but restarting coreservicesd using launchctl seems to fix it:

Download Tool