Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
blanket — CVE-2018-4280: iOS 11.2.6의 launchd에서 발견된 Mach port 교체 취약점으로 샌드박스 탈출, 권한 상승, 코드 서명 우회가 가능합니다. | Kitploit
도구/GitHubGitHub/bazad/blanket
Privilege EscalationiOS SecurityVulnerability AnalysisExploitationPost-ExploitationMobile SecurityBinary Exploitation
GitHubbazad/blanket

blanket

CVE-2018-4280: iOS 11.2.6의 launchd에서 발견된 Mach port 교체 취약점으로 샌드박스 탈출, 권한 상승, 코드 서명 우회가 가능합니다.

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

blanket

Blanket은 iOS 11.2.6을 대상으로 하는 샌드박스 탈출 도구이며, 주요 취약점은 iOS 11.4.1에서만 패치되었습니다. 이 도구는 launchd의 Mach 포트 교체 취약점(CVE-2018-4280)과 다른 서비스들의 여러 가지 사소한 취약점을 악용하여, 샌드박스가 적용되지 않고 root로 실행되며 task_for_pid-allow 자격을 가진 ReportCrash 프로세스 내에서 코드를 실행합니다. 이를 통해 amfid와 같은 보안에 중요한 프로세스를 포함하여 폰에서 실행 중인 모든 프로세스에 대한 전면적인 제어가 가능해집니다.

이 익스플로잇은 여러 단계로 구성됩니다. 이 README에서는 주요 취약점과 샌드박스 탈출의 각 단계를 단계별로 설명합니다.

시스템 서비스 가장

iOS의 크래시 리포팅을 조사하던 중, 저는 launchd에서 Mach 포트 교체 취약점을 발견했습니다. 특정한 방식으로 크래시가 발생하면, 프로세스는 커널이 launchd에 Mach 메시지를 보내도록 만들 수 있으며, 이로 인해 launchd는 자신의 IPC 네임스페이스에 있는 Mach 포트에 대한 send right를 과도하게 해제(over-deallocate)하게 됩니다. 이를 통해 공격자는 시스템의 나머지 부분에 대해 조회할 수 있는 모든 launchd 서비스를 가장할 수 있으며, 권한 상승으로 이어질 수 있는 다양한 경로가 열립니다.

이 취약점은 macOS에도 존재하지만, iOS에서 이 취약점을 트리거하는 것은 더 어렵습니다. launchd에는 Mach 예외 메시지가 커널로부터 온 것인지 확인하는 검사가 있기 때문입니다.

CVE-2018-4280: EXC_CRASH 예외 메시지 처리 중 launchd Mach 포트 과잉 해제

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

불행히도 launchd의 이러한 메시지 처리는 버그가 있습니다. 예외 유형이 EXC_CRASH이면 launchd는 메시지에 포함된 스레드 및 태스크 포트를 해제한 다음 서비스 루틴에서 KERN_FAILURE를 반환하므로, MIG 시스템이 스레드와 태스크 포트를 다시 해제하게 됩니다. (여기서 가정하는 것은 서비스 루틴이 성공을 반환하면 Mach 메시지의 모든 리소스에 대한 소유권을 가져간 것이고, 오류를 반환하면 어떤 리소스도 소유한 것이 없다는 점입니다.)

다음은 mach_exception_raise 메시지에 대한 launchd의 서비스 루틴 코드로, IDA/Hex-Rays를 사용해 디컴파일하고 가독성을 위해 약간 편집한 것입니다.```C 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 exception_type_t exception, // thread and task ports could mach_exception_data_t code, // be arbitrary send rights. mach_msg_type_number_t codeCnt) { __int64 __stack_guard; // ST28_8@1 kern_return_t kr; // w0@1 MAPDST kern_return_t result; // w0@4 __int64 codes_left; // x25@6 mach_exception_data_type_t code_value; // t1@7 int pid; // [xsp+34h] [xbp-44Ch]@1 char codes_str[1024]; // [xsp+38h] [xbp-448h]@7

__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 ( current_audit_token.val[5] )                   // (b) If the message was sent by
{                                                   //     a process with a nonzero PID
    result = KERN_FAILURE;                          //     (any non-kernel process),
}                                                   //     the message is rejected.
else
{
    if ( codeCnt )
    {
        codes_left = codeCnt;
        do
        {
            code_value = *code;
            ++code;
            __snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", code_value);
            --codes_left;
        }
        while ( codes_left );
    }
    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_port(thread);                   // (c) The "thread" port sent in
    if ( kr )                                       //     the message is deallocated.
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    kr = deallocate_port(task);                     // (d) The "task" port sent in the
    if ( kr )                                       //     message is deallocated.
    {
        _os_assumes_log(kr);
        _os_avoid_tail_call();
    }
    if ( exception == EXC_CRASH )                   // (e) If the exception type is
        result = KERN_FAILURE;                      //     EXC_CRASH, then KERN_FAILURE
    else                                            //     is returned. MIG will
        result = 0;                                 //     deallocate the ports again.
}
*__stack_chk_guard_ptr;
return result;

}

이 코드가 수행하는 작업은 다음과 같습니다:

1. 이 함수는 `mach_exception_raise` 예외 메시지를 위한 Mach 서비스 루틴입니다. launchd가 `mach_exception_raise` Mach 예외 메시지를 처리할 때 Mach 시스템에 의해 직접 호출됩니다. 서비스 루틴에 전달되는 인자는 Mach 메시지에서 파싱되므로, 메시지 전송자가 제어할 수 있습니다.
2. (b)에서 launchd는 Mach 예외 메시지가 커널에 의해 전송되었는지 확인합니다. 전송자의 audit 토큰은 필드 5에 전송 프로세스의 PID를 포함하며, 커널의 경우에만 0이 됩니다. 메시지가 커널에 의해 전송된 것이 아니면 거부됩니다.
3. 메시지의 thread 및 task 포트는 (c)와 (d)에서 명시적으로 할당 해제됩니다.
4. (e)에서 launchd는 예외 유형이 `EXC_CRASH`인지 확인하고, 그렇다면 `KERN_FAILURE`를 반환합니다. 의도는 `EXC_CRASH` 메시지를 처리하지 않도록 하여, ReportCrash가 corpse 핸들러로 호출되게 하기 위한 것으로 보입니다. 그러나 이 시점에서 `KERN_FAILURE`를 반환하면 나중에 예외 메시지가 정리될 때 task 및 thread 포트가 다시 할당 해제됩니다. 즉, 이 두 포트가 이중으로 할당 해제됩니다.

이 취약점을 실제로 활용하려면 launchd가 제공하는 Mach 서비스에 대한 launchd의 send 권리를 해제하여, 해당 서비스를 시스템의 나머지 부분에 사칭할 수 있어야 합니다. 즉, 예외 메시지의 task 및 thread 포트가 launchd에서 해제하려는 Mach 서비스 포트에 대한 실제 send 권리여야 합니다. 그런 다음 악성 예외 메시지를 launchd에 보내 서비스 포트를 해제한 후, 동일한 포트 이름이 재사용되도록 시도하되, 이번에는 우리가 receive 권리를 보유한 Mach 포트에 할당되도록 해야 합니다. 그렇게 하면 클라이언트가 launchd에 서비스의 Mach 포트에 대한 send 권리를 요청할 때, launchd는 대신 우리 포트에 대한 send 권리를 넘겨주어 우리가 클라이언트에게 해당 서비스를 사칭할 수 있게 됩니다. 이후 시스템 권한을 획득하는 다양한 경로가 존재합니다.


### 취약점 트리거

취약점을 실제로 트리거하려면 메시지가 커널에 의해 전송되었는지 확인하는 검사를 우회해야 합니다. 예외 메시지를 launchd에 직접 보내면 그냥 폐기되기 때문입니다. 어떻게든 커널이 실제 thread 및 task 포트 대신 시스템 서비스에 대한 Mach send 권리를 포함한 "악성" 예외 메시지를 보내도록 해야 합니다.

알고 보니 Mach 트랩인 `task_set_special_port`를 사용하면 특정 상황에서 실제 task 포트 대신 사용될 사용자 지정 send 권리를 설정할 수 있습니다. 그러한 상황 중 하나는 커널이 task를 대신하여 예외 메시지를 생성할 때입니다. 커널은 예외 메시지에 실제 task send 권리를 넣는 대신 `task_set_special_port`가 제공한 send 권리를 사용합니다. 더 구체적으로, task가 `task_set_special_port`를 호출하여 `TASK_KERNEL_PORT` 특수 포트에 사용자 지정 값을 설정한 다음 task가 크래시하면, 커널이 생성한 예외 메시지의 "task" 필드에는 실제 task 포트가 아닌 사용자 지정 포트에 대한 send 권리가 포함됩니다. 동등한 API인 `thread_set_special_port`를 사용하면 생성된 예외 메시지의 "thread" 필드에 사용자 지정 포트를 설정할 수 있습니다.

이러한 동작 덕분에 커널이 task 및 thread 포트 대신 Mach 서비스 포트를 포함하는 "악성" 예외 메시지를 생성하도록 만드는 것은 전혀 어렵지 않습니다. 그러나 생성된 예외 메시지가 launchd에 전달되도록 해야 합니다.

다시 말하지만, 올바른 API를 알고 있다면 커널이 "악성" 예외 메시지를 launchd에 전달하도록 만드는 것도 어렵지 않습니다. `thread_set_exception_ports` 함수는 이 스레드의 예외 메시지가 전달될 포트로 임의의 Mach send 권리를 설정합니다. 따라서 부트스트랩 포트로 `thread_set_exception_ports`를 호출하기만 하면, 이후 발생하는 모든 예외로 인해 커널이 launchd에 예외 메시지를 보내게 됩니다.

퍼즐의 마지막 조각은 올바른 예외 유형을 얻는 것입니다. 이 취약점은 `EXC_CRASH` 예외에 대해서만 트리거됩니다. 약간의 시행착오를 통해 표준 `abort` 함수를 호출하면 `EXC_CRASH` 예외를 쉽게 생성할 수 있음을 알 수 있습니다.

요약하면, 기존에 잘 문서화된 API를 사용하여 커널이 우리를 대신해 악성 `EXC_CRASH` 예외 메시지를 생성하고 launchd에 전달하도록 만들어 취약점을 트리거하고 Mach 서비스 포트를 해제할 수 있습니다:

1. `thread_set_exception_ports`를 사용하여 이 스레드의 예외 핸들러로 launchd를 설정합니다.
2. `bootstrap_look_up`을 호출하여 launchd에서 사칭하려는 서비스의 서비스 포트를 가져옵니다.
3. `task_set_special_port`/`thread_set_special_port`를 호출하여 예외 메시지에서 실제 task 및 thread 포트 대신 해당 서비스 포트를 사용하도록 설정합니다.
4. `abort`를 호출합니다. 커널이 `EXC_CRASH` 예외 메시지를 launchd에 보내지만, 메시지의 task 및 thread 포트는 대상 서비스 포트가 됩니다.
5. launchd가 예외 메시지를 처리하고 서비스 포트를 해제합니다.


### 크래시 이후 코드 실행

위 전략에는 문제가 있습니다. `abort`를 호출하면 우리 프로세스가 종료됩니다. 취약점을 트리거한 후 코드를 실행하려면 다른 프로세스에서 크래시를 수행할 방법이 필요합니다.
도구 다운로드