现在的位置: 首页 > 综合 > 正文

通过 DPAPI 获取当前帐号保存的 MSN Messenger 密码

2012年08月21日 ⁄ 综合 ⁄ 共 3863字 ⁄ 字号 评论关闭
原文:http://www.blogcn.com/User8/flier_lu/index.html?id=3300158

tomekeeper昨天在水木上贴了一个通过 DPAPI 获取保存的 MSN 密码的代码。其核心思想是从 MSN 加密保存在注册表中的键里,把加密后字符串抠出来,然后使用 DPAPI 的函数 CryptUnprotectData 解密之。关键代码如下:

//
ret = RegOpenKeyEx
(
HKEY_CURRENT_USER,
"Software\Microsoft\MSNMessenger",
0,
KEY_READ,
&hKey
);
//
ret = RegQueryValueEx
(
hKey,
"Password.NET Messenger Service",
//
);

DataIn.pbData 
= Data + 2//口令密文从第二位开始
DataIn.cbData = dwSize-2;

CryptUnprotectData
(
&DataIn,
NULL,
NULL,
NULL,
NULL,
1,
&DataOut
);

base64_decode (DataOut.pbData, Data, strlen(DataOut.pbData));
printf ( 
"MSN PassWord: %s ", Data);

不过这种方法针对 XP/2003 环境下的新版本 MSN 没有效果,因为现在已经不再直接保存在注册表键里。于是我安装类似思路,使用 Windbg 和 IDA Pro 大概分析了一下新版本的保存密码方法。得出结论是 MSN 使用了 XP/2003 新增的对当前用户凭据集 (credential set) 管理的函数,将加密后密码放到这个里面统一管理。
可以使用新增的 CredReadDomainCredentials 函数,给定读取目标来获得加密凭据,如:

typedef struct _CREDENTIAL_TARGET_INFORMATIONW {
LPWSTR TargetName;
LPWSTR NetbiosServerName;
LPWSTR DnsServerName;
LPWSTR NetbiosDomainName;
LPWSTR DnsDomainName;
LPWSTR DnsTreeName;
LPWSTR PackageName;
ULONG Flags;
DWORD CredTypeCount;
LPDWORD CredTypes;
}
 CREDENTIAL_TARGET_INFORMATIONW, *PCREDENTIAL_TARGET_INFORMATIONW;

DWORD CredTypes 
= CRED_TYPE_DOMAIN_VISIBLE_PASSWORD;

CREDENTIAL_TARGET_INFORMATIONW target 
=
{ L"messenger.hotmail.com", NULL, NULL, NULL,
L
"Passport.Net", NULL, L"Passport1.4"01&CredTypes }
;

DWORD dwCount 
= 0;
PCREDENTIALW
*creds;

Win32Check(CredReadDomainCredentialsW(
&target, CRED_CACHE_TARGET_INFORMATION, &dwCount, &creds),
"Cannot read user's domain credential, maybe you are not save your password");

这里的 CREDENTIAL_TARGET_INFORMATIONW 结构指定要获取凭据的来源和包名称;CredReadDomainCredentialsW 则根据此信息,通过 NdrClientCall2 函数发送 RPC 调用,完成实际功能。
读取出来的凭据包括此凭据的用户名 (UserName) 和凭据内容 (CredentialBlob):

typedef struct _CREDENTIALW {
DWORD Flags;
DWORD Type;
LPWSTR TargetName;
LPWSTR Comment;
FILETIME LastWritten;
DWORD CredentialBlobSize;
LPBYTE CredentialBlob;
DWORD Persist;
DWORD AttributeCount;
PCREDENTIAL_ATTRIBUTEW Attributes;
LPWSTR TargetAlias;
LPWSTR UserName;
}
 CREDENTIALW, *PCREDENTIALW;

PCREDENTIALW cred 
= creds[0];

对 MSN Messenger 来说,CREDENTIALW::UserName 中就是登陆帐号了。MSN Messenger 每次显示文件菜单时,都会从凭据集中获取当前帐号名称用于显示。而每次登陆时,则进一步将凭据集的内容使用 CryptUnprotectData 函数进行解密,代码如下:

static unsigned char entropyData[] = {
0xe00x000xc80x000x080x010x100x01,
0xc00x000x140x010xd80x000xdc0x00,
0xb40x000xe40x000x180x010x140x01,
0x040x010xb40x000xd00x000xdc0x00,
0xd00x000xe00x000xb40x000xe00x00,
0xd80x000xdc0x000xc80x000xb40x00,
0x100x010xd40x000x140x010x180x01,
0x140x010xd40x000x080x010xdc0x00,
0xdc0x000xe40x000x080x010xc00x000x000x00 }
;

PCREDENTIALW cred 
= creds[0];
DATA_BLOB data 
= { cred->CredentialBlobSize, cred->CredentialBlob },
entropy 
= sizeof(entropyData), entropyData }, pass = 0, NULL };

BOOL ret 
= CryptUnprotectData(&data, NULL, &entropy, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &pass);

这里 entropyData 保存着通过 WinDbg 直接从 MSN Messenger 代码中抠取出来的内部使用固定加密密钥,以后有可能根据版本不同发生变化。因为新版 MSN Messenger 不在直接加密密码,而是使用此密钥加密,因此与 tomekeeper 那种处理稍有不同。解密后的 pass中保存的就是明文的当前 MSN Messenger 帐号的密码了,呵呵。

static const std::string toString(LPCWSTR lpStr, DWORD cbStr)
{
std::
string str;

str.resize(WideCharToMultiByte(CP_ACP, 
0, lpStr, cbStr, NULL, 0, NULL, NULL));
WideCharToMultiByte(CP_ACP, 
0, lpStr, cbStr, (char *)str.c_str(), str.size(), NULL, NULL);

return str;
}


static const std::string toString(LPCWSTR lpStr)
{
return toString(lpStr, wcslen(lpStr));
}


m_username 
= toString(cred->UserName);

m_fCredFree(creds);

Win32Check(ret, 
"Cannot decrypt credential"[img]/images/wink.gif[/img];

m_password 
= toString((LPCWSTR)pass.pbData, pass.cbData / sizeof(wchar_t));

::LocalFree(pass.pbData);

看起来好像很不安全,其实也还好了,呵呵。因为 CredReadDomainCredentials 和 CryptUnprotectData 等系列函数,缺省都是使用的当前帐号的安全上下文进行加密解密,哪怕换了台机器这些加密后的数据都不再有意义。因此除非你机器被人搞到当前帐号或者 Administrator 帐号权限,否则密码都还是安全的;反过来说,如果这两个帐号被别人搞到,随便装个键盘记录程序也能够达到类似的效果。因此总体上来说还是比较安全的,这个代码也只能作为忘记自动记录的 MSN 密码时的恢复工具 :P 其实这个才是我们俩研究此问题的主要目的,呵呵

Just for fun! :P

抱歉!评论已关闭.