DPAPI机制解析

admin 2023年12月1日12:58:20评论116 views字数 10167阅读33分53秒阅读模式



DPAPI机制解析


DPAPI机制解析


DPAPI机制解析

DPAPI概述

DPAPI机制解析

从Windows 2000开始,Microsoft随操作系统一起提供了一种特殊的数据保护接口,称为Data Protection Application Programming Interface(DPAPI)。DPAPI是一个简单的密码学应用程序接口,并且被广泛运用在很多Windows应用程序中,例如,Windows凭据管理器、浏览器和Outlook邮箱等。DPAPI分别提供了加密函数CryptProtectData 与解密函数 CryptUnprotectData 以用作数据的加密解密。还为了防止其他人查看进程中的敏感信息,分别提供了加密函数CryptProtectMemory与解密函数 CryptUnprotectMemory来对内存进行加密与解密。

理论上,数据保护API可以实现任何类型的数据对称加密;在实践中,其在Windows操作系统中的主要用途是使用对称加密算法对其他非对称加密算法的密钥进行加密。

对于几乎所有密码系统来说,最困难的挑战之一是“密钥管理”——其中部分挑战就是如何安全地存储解密密钥。如果密钥以纯文本存储,则可以访问密钥的任何用户都可以访问加密的数据。如果密钥被加密,则又需要另一个密钥,周而复始。因此DPAPI允许开发者使用从用户的登录私钥导出的对称密钥来加密密钥,或者在系统加密的情况下使用系统的域验证私钥来加密密钥。


DPAPI机制解析



DPAPI机制解析

DPAPI中的密码

DPAPI机制解析

我们在上文中所说,DPAPI需要用户的登录密码来提供保护,但是实际上,DPAPI使用的是用户的登录凭据,在用户使用密码登录的操作系统中,登录凭据只是用户密码的哈希值。为了简单起见,以下将会使用“密码”、“用户密码”或“登录密码”来代指此凭据。

使用登录密码的一个小缺点是,在同一用户下运行的所有应用程序都可以访问它们知道的任何受保护数据。当然,由于应用程序必须存储自己的受保护数据,因此其他应用程序访问这些数据可能会有些困难,但并非不可能。为了解决这个问题,DPAPI 允许应用程序在保护数据时使用额外的secret,然后在取消对于该数据保护时同样也需要这个额外的secret才能取消对该数据的保护。


DPAPI机制解析


DPAPI机制解析

Master Key文件

DPAPI机制解析


DPAPI 最初生成一个称为 MasterKey (主密钥)的强密钥,它受用户密码的保护,通常为64字节的随机数据。DPAPI 使用称为“基于密码的密钥派生”的标准加密过程(PKCS#5)从密码生成password-derived key(密码派生密钥)。然后,将此password-derived key与3DES一起使用来加密MasterKey,最后将其存储在用户的配置文件目录中。

然而,MasterKey 并未明确用于保护数据。DPAPI使用的对称会话密钥是由Master Key、16字节的随机数据以及额外的secret(如果应用程序选择提供)生成的,这个会话密钥被用来加密数据。为了安全性考虑,该会话密钥不被保存,加密后直接清除,只保存派生会话密钥时使用的 16字节的随机数据。当需要解密数据时,从加密数据块中提取该随机数据并以同样的方式再次派生出会话密钥对数据进行解密。


· Master Key加密

Master Key 曾经使用的第一种实现方式是使用用户密码的NTLM Hash来解密。这种方式由于其存在极大的安全风险已经不再使用。由于NTLM Hash是存储在SAM文件中,只要攻击者获取到Hash就可以用来生成Master Key来解密数据了。它在技术上允许潜在的犯罪分子通过直接从 SAM 文件获取 NTLM 哈希来解密Master Key,从而解密任何 DPAPI blob,甚至不需要用户的密码。

为了防止Master Key被篡改,它现在使用HMAC(Hash-based Message Authentication Code)进行加密。

1.首先DPAPI使用SHA1作为HMAC和用户的登录密码来派生HMAC密钥。

2.然后,将上文中所述的password-derived key与3DES一起使用来加密 MasterKey 和 MasterKey 的 HMAC。

3.盐和迭代计数都是非秘密值,因此与加密的MasterKey一起存储,但未加密。这允许DPAPI在给定用户密码的情况下轻松解密MasterKey。


·Master Key文件结构

Master Key文件结构如下图:

DPAPI机制解析

如上图所示,一个Master Key文件由5个部分组成:

1. 标头和系统信息

2. 用户的Master Key

3. 本地备份加密密钥

4. 唯一的 CREDHIST 文件标识符

5. 域的Master Key备份

在Windows 2000中,系统可以存储本地Master Key备份,而不是CREDHIST标识符。这就是 DPAPI 系统极易受到攻击的原因。

以下是有关5个单元更多的细节:

1. 标头属性

a. dwVersion - 用于兼容性检查的Master Key文件的版本。

b. szGuid - 具有唯一Master Key标识符的字符串值,通常与文件名匹配。

c. dwPolicy - 具有各种标志的字段。例如,如果设置了该字段的位 3,则将使用 SHA1 算法创建Master Key的解密密钥。

2. 用户Master Key属性

a. dwUserKeySize - 当前槽长度。

b. dwVersion - 数据结构版本。

c. pSalt - 盐,即 16 个随机字节的数据,参与Master Key的解密并防止使用彩虹表的数据攻击。

d. dwPBKDF2IterationCount - PBKDF2 加密密钥生成函数中的迭代次数。

e. HMACAlgId - 哈希算法标识符。

f. CryptAlgId - 使用的加密算法。

g. pKey - 用户的加密Master Key。

3. 本地备份密钥属性 (Windows 2000)

a. dwLocalKeySize - 本地备份密钥的大小

b. dwVersion - 数据结构版本。

c. pSalt - 盐,即 16 个随机字节的数据,参与Master Key的解密并防止使用彩虹表的数据攻击。

d. pKey - 加密的本地备份密钥。

4. CREDHIST 文件的 GUID 属性(Windows XP 及更高版本)

a. dwLocalKeySize - 本地备份密钥的大小

b. dwVersion - 数据结构版本。

c. guidCredHist - CREDHIST 文件二进制标识符。

5. 域的Master Key备份属性

a. dwDomainKeySize - 域的Master Key备份的大小。

b. dwVersion - 数据结构版本。

c. pSalt - 盐,即 16 个随机字节的数据,参与Master Key的解密并防止使用彩虹表的数据攻击。

d. dwPBKDF2IterationCount - PBKDF2 加密密钥生成函数中的迭代次数。

e. HMACAlgId - 哈希算法标识符。

f. CryptAlgId - 使用的加密算法。

g. pKey - 加密的域备份密钥。其解密需要存储在 Active Directory 数据库中的域控制器 RSA 私钥。


·新Master Key的创建

1. 首先,调用API函数RtlGenRandom,它会返回一个伪随机生成的64位字节数的数据。

2. 使用用户的登录密码、安全标识符(SID)和额外的16位字节数的随机数据(所谓的“盐”)来加密步骤1中获得的64字节的随机数据。

3. Master Key获得一个唯一的GUID。每一个DPAPI blob都储存该唯一标识符。

4. 使用用户密码创建和加密的Master Key与其他系统数据一起存储在Master Key存储文件夹(Master Key File)中的单独文件中。

a. 用户的Master Key存储在%APPDATA%/Microsoft/Protect/%SID% 中。其中{SID}为该用户的安全标识符。

b. 系统的Master Key存储在 %WINDIR%/System32/Microsoft/Protect 中,用于解密 DPAPI blob,受本地系统帐户的保护。


·用户密码更新后Master Key的变化

修改用户密码

Windows 安全策略假定定期更改用户密码,并且 DPAPI 必须为用户提供与密码更改之前相同级别的个人加密数据访问权限。这是通过所有用户的Master Key的三步同步来实现的:

1. 所有Master Key使用旧用户密码进行解密

2. 使用新密码对所有密钥进行加密并保存到磁盘

3. 如果选择了相应的选项,则会备份它们

所有Master Key的重新加密可能需要很长时间,因此,如上文所述,这些操作是在 LSA 内的单独线程中执行的。


重置用户密码

当管理员手动重置用户密码,或者使用密码重置程序之一删除用户密码时,就会出现这种情况。

尽管此时用户仍然可以登录系统,但此时所有使用 DPAPI 加密的数据都不再可用,因为用户的旧密码对于此时的系统来说是未知的,所以 DPAPI 无法解密Master Key。如果在强制密码重置后再次设置用户旧密码,DPAPI 将返回其原始状态,并且所有 DPAPI blob 将再次可用。发生这种情况是因为即便DPAPI无法解密Master Key,但DPAPI仍然保留了Master Key的所有副本。


DPAPI机制解析


DPAPI机制解析

DPAPI加解密过程

DPAPI机制解析

简单来说,DPAPI中的数据加密解密分为四个阶段,如下图:

DPAPI机制解析

1. 首先,应用程序调用 DPAPI 函数之一,指定要加密或解密的数据以及附加secret参数(如有)。

2. 这些函数属于CryptAPI结构,位于Crypt32.dll中。进一步处理的请求会从Crypt32.dll向LSA(Local Security Authority)发出本地RPC(Remote Process Call)调用。LSA 是一个系统进程,在启动时启动并运行直到计算机关闭。本地RPC调用无需遍历网络,因此所有数据都会保留在本地计算机上。然后,这些 RPC 调用的端点调用 DPAPI 私有函数来保护或取消保护数据。

3. 然后,DPAPI 私有函数使用 Crypt32.dll 回调 CryptoAPI,以在 LSA 的安全环境中实际加密或解密数据。

4. 最后在LSA中,加密或解密过后的数据会通过一个安全的RPC通道回到Crypt.dll中,再从Crypt.dll中传回原本的应用程序。


·加密算法

不同操作系统中DPAPI默认使用的加密算法不同,具体如下表:

操作系统

加密算法

哈希算法

PBKDF2 中的迭代次数

Windows2000

RC4

SHA1

1

WindowsXP

3DES

SHA1

4000

WindowsVista

3DES

SHA1

24000

Windows7

AES256

SHA512

5600

Windows10-11

AES256

SHA512

8000


DPAPI机制解析


DPAPI机制解析

DPAPI接口调用

DPAPI机制解析

·Data加密与解密demo

使用以下C++代码示例可以达成DPAPI数据加解密的接口调用(CryptProtectData 与CryptUnprotectData),可更改其中参数来达成自己所需的加密效果:

1.#include "stdafx.h"  2.#include <stdio.h>  3.#include <windows.h>  4.#include <Wincrypt.h>  5.#include <dpapi.h>  6.#pragma comment(lib, "Crypt32.lib")  7.  8.void main()  9.{  10.    DATA_BLOB DataIn;  11.    DATA_BLOB DataOut;  12.    DATA_BLOB DataVerify;  13.    BYTE *pbDataInput = (BYTE *)"Hello world of data protection.";  14.    DWORD cbDataInput = strlen((char *)pbDataInput) + 1;  15.    DataIn.pbData = pbDataInput;  16.    DataIn.cbData = cbDataInput;  17.    CRYPTPROTECT_PROMPTSTRUCT PromptStruct;  18.    LPWSTR pDescrOut = NULL;  19.    //-------------------------------------------------------------------  20.    //  Begin processing.  21.  22.    printf("The data to be encrypted is: %sn", pbDataInput);  23.  24.    //-------------------------------------------------------------------  25.    //  Initialize PromptStruct.  26.  27.    ZeroMemory(&PromptStruct, sizeof(PromptStruct));  28.    PromptStruct.cbSize = sizeof(PromptStruct);  29.    PromptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;  30.    PromptStruct.szPrompt = L"This is a user prompt.";  31.  32.    //-------------------------------------------------------------------  33.    //  Begin protect phase.  34.  35.    if (CryptProtectData(  36.        &DataIn,          // 指向需要加密的数据   37.        L"This is the description string.", // 对于加密数据的描述38.        NULL,            // 指向用于加密数据的密码或其他附加熵(如有)   39.        NULL,            // 该参数并未使用,必须设置为NULL40.        &PromptStruct,        // 指向PromptStruct的指针,提供有关在何处和何时显示提示(可以为NULL)41.        0,            //dwFlags,具体作用可查询官方文档42.        &DataOut))          //指向接收加密数据的地方43.    {  44.        printf("The encryption phase worked. n");  45.    }  46.    else  47.    {  48.        printf("Encryption error!");  49.    }  50.    //-------------------------------------------------------------------  51.    //   Begin unprotect phase.  52.  53.    if (CryptUnprotectData(  54.        &DataOut,      // 指向保存的加密数据 55.        &pDescrOut,      // 指向保存的加密数据的描述56.        NULL,                // 指向用于加密数据的密码或其他附加熵(如有)   57.        NULL,                // 该参数并未使用,必须设置为NULL58.        &PromptStruct,       // 指向PromptStruct的指针,提供有关在何处和何时显示提示(可以为NULL)59.        0,  //dwFlags,具体作用可查询官方文档60.        &DataVerify))    //指向接收解密数据的地方61.    {  62.        printf("The decrypted data is: %sn", DataVerify.pbData);  63.        printf("The description of the data was: %Sn", pDescrOut);  64.        system("pause");  65.    }  66.    else  67.    {  68.        printf("Decryption error!");  69.    }  70.}  


注:使用C++和C#加密数字以及英文的结果是相同的,但是加密中文的结果会不一样,原因是编码不同。


·Data demo效果演示

下图为上述示例代码执行效果:

DPAPI机制解析

点击设置安全级别后→选择【高】→点击下一页,则可以设置用于加密数据的密码,如下图:

DPAPI机制解析
DPAPI机制解析

若选择设置密码后,在解密时会要求输入密码,若不选择设置密码,则可以直接解密,如下图:

DPAPI机制解析

密码输入错误也无法进行解密,如下图:

DPAPI机制解析

输入正确密码后,解密成功:

DPAPI机制解析


·Memory加密与解密demo

使用以下C++代码示例可以达成DPAPI内存加解密的接口调用(CryptProtectMemory与CryptUnprotectMemory),可更改其中参数来达成自己所需的加密效果:

1.#include "stdafx.h"  2.#include <windows.h>  3.#include <stdio.h>  4.#include <Wincrypt.h>  5.#include <dpapi.h>  6.#pragma comment(lib, "Crypt32.lib")  7.  8.#define SSN_STR_LEN 12  // includes null  9.  10.void main()  11.{  12.    HRESULT hr = S_OK;  13.    LPWSTR pSensitiveText = NULL;  14.    DWORD cbSensitiveText = 0;  15.    DWORD cbPlainText = SSN_STR_LEN * sizeof(WCHAR);  16.    DWORD dwMod = 0;  17.  18.    //  Memory to encrypt must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE.  19.    if (dwMod = cbPlainText % CRYPTPROTECTMEMORY_BLOCK_SIZE)  20.        cbSensitiveText = cbPlainText +(CRYPTPROTECTMEMORY_BLOCK_SIZE - dwMod);  21.    else  22.        cbSensitiveText = cbPlainText;  23.  24.    pSensitiveText = (LPWSTR)LocalAlloc(LPTR, cbSensitiveText);  25.    if (NULL == pSensitiveText)  26.    {  27.        wprintf(L"Memory allocation failed.n");  28.    //  return E_OUTOFMEMORY;  29.    }  30.    wcscpy_s(pSensitiveText, 5,L"1234");  31.    //  Place sensitive string to encrypt in pSensitiveText.  32.  33.    if (!CryptProtectMemory(pSensitiveText, cbSensitiveText,  34.        CRYPTPROTECTMEMORY_SAME_PROCESS))  35.    {  36.        wprintf(L"CryptProtectMemory failed: %dn", GetLastError());  37.        SecureZeroMemory(pSensitiveText, cbSensitiveText);  38.        LocalFree(pSensitiveText);  39.        pSensitiveText = NULL;  40.    //  return E_FAIL;  41.    }  42.  43.    if (CryptUnprotectMemory(pSensitiveText, cbSensitiveText,  44.        CRYPTPROTECTMEMORY_SAME_PROCESS))  45.    {  46.        wprintf(L"CryptUnprotectMemory successed!n");  47.        // Use the decrypted string.  48.    }  49.    else  50.    {  51.        wprintf(L"CryptUnprotectMemory failed: %dn",  52.            GetLastError());  53.    }  54.    //  Call CryptUnprotectMemory to decrypt and use the memory.  55.  56.    SecureZeroMemory(pSensitiveText, cbSensitiveText);  57.    LocalFree(pSensitiveText);  58.    pSensitiveText = NULL;  59.}  



·Memory demo效果演示

使用VS对上述示例代码进行调试,我们在

DPAPI机制解析

该代码处打上断点,在执行完这句代码后局部变量如下图:

DPAPI机制解析

可以看到在内存地址0x00E1C6A0处储存了“1234”的值,那么我们查看一下内存,如下图:

DPAPI机制解析

可以看到0x00E1C6A0储存的值为,31,32,33,34,分别对应1234的ASCII码。

我们继续执行代码,发现在执行完

DPAPI机制解析

这句代码后,0x00E1C6A0储存的值变为如下图所示:

DPAPI机制解析

可以看到此时0x00E1C6A0储存的值已经变为了随机的数据,完成了加密。

我们继续执行代码,发现在执行完

DPAPI机制解析

这句代码后,0x00E1C6A0储存的值又变回了31,32,33,34。证明解密成功。

DPAPI机制解析


DPAPI机制解析


DPAPI机制解析

参考文献

DPAPI机制解析

1. DPAPI Secrets. Security Analysis and Data Recovery in DPAPI. www.passcape.com/index.php?section=docsys&cmd=details&id=28.

2. Windows系统存储区DPAPI的解密技术研究 - 百度文库. wenku.baidu.com/view/388bea4b56270722192e453610661ed9ad5155bb.html?_wkts_=1700210324209.

3. DPAPI Master Key Analysis. www.passcape.com/windows_password_recovery_dpapi_master_key.

4. Wikipedia contributors. “Data Protection API.” Wikipedia, 27 Mar. 2023, en.wikipedia.org/wiki/Data_Protection_API.

5. Windows信息搜集篇(五)之DPAPI 数据加密与Dns域传送漏洞利用 - 404p3rs0n - 博客园. www.cnblogs.com/404p3rs0n/p/15656584.html.






往期精彩合集



移动APP隐私安全,做到这些就够了

PE文件格式

车联网安全思考

安全实验室受邀参加看雪安全开发者峰会分享安全研究成果

Captcha丧钟已响

如何设计科技感海报

奇技妙用之Digispark在人脸模糊测试中的应用

智能网联汽车安全攻击面介绍

格式化字符串漏洞探究

简述生成式人工智能面临的法律风险


联想GIC全球安全实验室(中国)

[email protected]


DPAPI机制解析





原文始发于微信公众号(联想全球安全实验室):DPAPI机制解析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月1日12:58:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DPAPI机制解析http://cn-sec.com/archives/2259071.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息