PasswordAlgorithms: Internet Explorer 7, 8, 9
IE7、8、9中的密码算法
原文链接:Password Algorithms: Internet Explorer 7, 8, 9
一,介绍
在Windows8系统下,IE10 采用了不同于之前的加密算法和存放方案,所以我可能会在以后单独分析它。现在我将分析Windows7下9.0.9版本的IE
这里的一切都与传统的IE7,8完全一致。
考虑到客户可能会避免将系统升级到Windows8,所以我认为这种保护措施还是值得细细讨论。
二,存储
用户所有的自动完成表单都存放在NTUSER.DAT文件中,并且这个文件是由SHA-1散列和DPAPI的大二进制对象(BLOB)组成。
下面是我系统的一些hash散列
C:\>regquery "HKCU\Software\Microsoft\InternetExplorer\IntelliForms\Storage2"
HKEY_CURRENT_USER\Software\Microsoft\InternetExplorer\IntelliForms\Storage2
6FBD22A243E7F5A0D660199683F52543E80CEB99EC REG_BINARY 01000000D08C9DDF0115D1118. . .
DF11F9BE8F0049A2FBFF29C6D49FE77383C2A6783A REG_BINARY 01000000D08C9DDF0115D1118. . .
E4CE6B2B79515319A7360D97E3B217F2FC843CC019 REG_BINARY 01000000D08C9DDF0115D1118. . .
为了防止离线破解,这些BLOB已经被截断。当IE连接到一个网站时,它需要凭据(指账户)来自动登录,它将
1, 导出URL的SHA-1的校验和
2, 在自动完成表单中搜索这个校验和
3, 如果发现校验和,就使用URL解密DPAPI保护的BLOB,并自动填充登录字段。
三,生成
先拿到第二个哈希值(上面展示的三个哈希值中的第二个,老外应该是要对那个hash散列开始分析)
DF11F9BE8F0049A2FBFF29C6D49FE77383C2A678 3A
这个是采用Unicode编码的字符串“https://accounts.google.com/servicelogin” 使用SHA-1产生的校验和
最后一个字节0x3A 是每个字节采用SHA-1散列后的校验和
以下函数使用Windowscrypto API演示了这个过程。
bool GetUrlHash(std::wstring url, std::wstring &result) {
HCRYPTPROV hProv;
HCRYPTHASH hHash;
bool bResult = false;
std::transform(url.begin(), url.end(), url.begin(), ::tolower);
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) {
if (CryptHashData(hHash, (PBYTE)url.c_str(),
url.length() * sizeof(wchar_t) + 2, 0)) {
BYTE bHash[20];
DWORD dwHashLen = sizeof(bHash);
if ((bResult = CryptGetHashParam(hHash, HP_HASHVAL, bHash,
&dwHashLen, 0))) {
BYTE chksum = 0;
wchar_t ch[4];
for (size_t i = 0;i < 20 + 1;i++) {
BYTE x;
if (i < 20) {
x = bHash[i];
chksum += x;
} else {
x = chksum;
}
wsprintf(ch, L"%02X", x);
result.push_back(ch[0]);
result.push_back(ch[1]);
}
}
}
CryptDestroyHash(hHash);
}
CryptReleaseContext(hProv, 0);
}
return bResult;
}
每一个用户名和密码都采用Unicode格式存储
如果存在相同的URL的表单凭据,新的账户信息会被添加在现有的表单凭据中
问题在于,并没有正式的文档准确的描述了凭据表单的结构,幸而,网上有一个文档描叙了它的旧版结构,这对我帮助很大。
enum { MAX_STRINGS = 200 };
enum { INDEX_SIGNATURE=0x4B434957 };
enum { INIT_BUF_SIZE=1024 };
enum { LIST_DATA_PASSWORD = 1 };
struct StringIndex {
DWORD dwSignature;
DWORD cbStringSize; // up to not including first StringEntry
DWORD dwNumStrings; // Num of StringEntry present
INT64 iData; // Extra data for string list user
struct tagStringEntry {
union
{
DWORD_PTR dwStringPtr; // When written to store
LPWSTR pwszString; // When loaded in memory
};
FILETIME ftLastSubmitted;
DWORD dwStringLen; // Length of this string
}StringEntry[];
};
使用这个结构来解析一个已经解密的BLOB以供参考引起了一些麻烦,并且需要一些小的改动。在IEFrame.dll中,CryptProtectData()使用URL 作为凭据(密钥)来加密StringIndex和凭据表单。
接下来的问题是发现被用做密钥的原始的URL,这也是为什么IE密码算法相当不错的原因。
四,获取登录网址
有许多方法可以获取URL用于解析IE7-IE9密码。缓冲中通常有一个访问网站的列表,这个列表可以列举出我们想要的URL。
下面是使用COM的老获取URL的一种方式
void EnumCache1() {
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr)) {
IUrlHistoryStg2 *pHistory = NULL;
hr = CoCreateInstance(CLSID_CUrlHistory, NULL,
CLSCTX_INPROC_SERVER,
IID_IUrlHistoryStg2,(void**)(&pHistory));
if (SUCCEEDED(hr)) {
IEnumSTATURL *pUrls = NULL;
hr = pHistory->EnumUrls(&pUrls);
if (SUCCEEDED(hr)) {
while (TRUE) {
STATURL st;
ULONG result;
hr = pUrls->Next(1, &st, &result);
if (SUCCEEDED(hr) && result == 1) {
AddUrl(st.pwcsUrl);
} else {
break;
}
}
pUrls->Release();
}
pHistory->Release();
}
CoUninitialize();
}
}
另一种方法是使用WININET API
void EnumCache2()
{
HANDLE hEntry;
DWORD dwSize;
BYTE buffer[8192];
LPINTERNET_CACHE_ENTRY_INFO info = (LPINTERNET_CACHE_ENTRY_INFO) buffer;
dwSize = 8192;
hEntry = FindFirstUrlCacheEntry(NULL, info, &dwSize);
if (hEntry != NULL) {
do {
if (info->CacheEntryType != COOKIE_CACHE_ENTRY) {
AddUrl(info->lpszSourceUrlName);
}
dwSize = 8192;
} while (FindNextUrlCacheEntry(hEntry, info, &dwSize));
FindCloseUrlCache(hEntry);
}
}
从更高的角度来看,你也可以解析index.dat文件,但我这里并不打算那么做,因为它是在取证的高度。
一个更好的方法可能是阅读从互联网上收集的URL列表。
五,恢复账户
恢复账户,即IE7-IE9的解密表单过程,它迫使我们使用URL表来解密,以下过程可以收集到自动完成表单的列表
#define MAX_URL_HASH 255
#define MAX_URL_DATA 8192
typedef struct _IE_STORAGE_ENTRY {
std::wstring UrlHash;
DWORD cbData;
BYTE pbData[MAX_URL_DATA];
} IE_STORAGE_ENTRY, *PIE_STORAGE_ENTRY;
DWORD GetAutocompleteEntries() {
HKEY hKey;
DWORD dwResult;
dwResult = RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2",
0, KEY_QUERY_VALUE, &hKey);
if (dwResult == ERROR_SUCCESS) {
DWORD dwIndex = 0;
while (TRUE) {
IE_STORAGE_ENTRY entry;
DWORD cbUrl = MAX_URL_HASH;
wchar_t UrlHash[MAX_URL_HASH];
entry.cbData = MAX_URL_DATA;
dwResult = RegEnumValue(hKey, dwIndex, UrlHash, &cbUrl,
NULL, 0, entry.pbData, &entry.cbData);
if (dwResult == ERROR_SUCCESS) {
entry.UrlHash = UrlHash;
ac_entries.push_back(entry);
} else if (dwResult == ERROR_NO_MORE_ITEMS) {
break;
}
dwIndex++;
}
RegCloseKey(hKey);
}
return ac_entries.size();
}
现在,有了URL字符串和自动完成表单,我们可以尝试使用CryptUnprotectData()去解密那些数据。然后使用StringIndex结构解析解密后的数据。
void ParseBlob(PBYTE pInfo, const wchar_t *url) {
StringIndex* pBlob = (StringIndex*)pInfo;
// get offset of data
PBYTE pse = (PBYTE)&pInfo[pBlob->cbHdrSize + pBlob->cbStringSize1] ;
// process 2 entries for each login
for (DWORD i = 0;i < pBlob->dwNumStrings;i += 2) {
// get username and password
wchar_t *username = (wchar_t*)&pse[pBlob->StringEntry[i + 0].dwStringPtr];
wchar_t *password = (wchar_t*)&pse[pBlob->StringEntry[i + 1].dwStringPtr];
bool bTime;
wchar_t last_logon[MAX_PATH];
if (lstrlen(password) > 0) {
// get last time this was used
FILETIME ft;
SYSTEMTIME st;
FileTimeToLocalFileTime(&pBlob->StringEntry[i].ftLastSubmitted, &ft);
FileTimeToSystemTime(&ft, &st);
bTime = (GetDateFormatW(LOCALE_SYSTEM_DEFAULT, 0, &st, L"MM/dd/yyyy", last_logon, MAX_PATH) > 0);
} else {
bTime = false;
}
wprintf(L"\n%-30s %-20s %-15s %s", username, password, bTime ? last_logon : L"NEVER", url);
}
}
六,总结
由于URL作为entropy(熵)来使用,因为可以恢复自动完成表单的所有条目。
这仅仅可以恢复当前流行的网页的凭据表单,诸如Facebook, Gmail, Instagram, Hotmail.
但少为人知的将是问题,除非网址被存储被高速缓存中。