The certification password of Internet Explorer 7 and operation of auto complete
----------------------------------------------------------------------
The certification password of Internet Explorer 7 and operation of
auto complete
Author:SapporoWorks(http://homepage2.nifty.com/spw/)
at
ScanNetSecurity(https://www.netsecurity.ne.jp/)
----------------------------------------------------------------------
 When we set a certification password as "Save," or auto complete is
validated in Internet Explorer (IE), the data are stored in the
computer. The operation method for these data has not been formally
thrown open to the public. However, we can obtain the data with
WnetEnumCachedPasswords*1 in IE 4.01 and earlier versions, and with
Protected Storage service, which use the IPStore interface*2 in IE
4.01 and later versions.
About the certification password of Internet Explorer and operation
of auto complete, refer to http://codezine.jp/a/article.aspx?aid=147.
In IE 7, this saving format was completely changed and the data
cannot be read using the existing method. In this paper, we are going
to describe working with these data in IE7. The readers of this paper
are expected to have some knowledge of C++, Microsoft Platform SDK,
and MSDN library.
Sample cords, executables, and how to implement the program are shown
on the support pages at http://www.sapporoworks.ne.jp/ie7_pass/.
Sample cords are created with Microsoft Visual Studio 2005 C++ and
tested on WindowsXP(SP2).
HideSeek Ver2.1.0 and prior, Supported in IE7
http://homepage2.nifty.com/spw/software/hideseek/
*1 WnetEnumCachedPasswords
WnetEnumCachedPasswords is closed API and can be used by acquiring
the address with GetProcAddress from mpr.dll.
*2 IPStore interface
http://msdn.microsoft.com/library/en-us/devnotes/winprog/ipstore.asp
Three saving formats
In IE7, the following three saving formats are changed:
(1) Web certification password
In IE7, a dialogue asking for input is shown when we access a Web
server which needs certification (basic certification). "user name"
and "password" should be input here. If "store a password" is
validated, these data are automatically input the next time the
server is accessed.
(2) Password by auto complete
This is the method for saving the password that goes in the password
field. On the screen, it is masked like ●●●●. If the
former
input remains, the password is automatically input when the input in
the text field is completed.
--------------------------------------------------------------------
<form name="test-form">
<br>name:<input name="user" type="text">
<br>password:<input name="pass" type="password">
<br><input name="btn" type=submit value="Sign in">
</form>
--------------------------------------------------------------------
(3) Automatic complete character strings
By remembering the strings that are input in the following text
field, the character strings are complemented when you input data in
the field. The previous sentence is not clear. This is not the
password information, but it is highly possible that unexpected
personal information is stored there.
--------------------------------------------------------------------
<form name="test-form">
<br>search string:<input name="q" type="text">
<br><input name="btn" type=submit value="Google Search">
</form>
--------------------------------------------------------------------
We shall describe each operation method next.
>> Web certification password
The data of the Web certification password can be recounted in
CredEnumerate, which is a low level API function for certificate
management.
【 About Authentication Functions - Credentials Management
Functions】
http://msdn.microsoft.com/library/en-us/secauthn/security/authenticati
on_functions.asp
【CREDENTIALStructure Excerpt from wincred.h】
--------------------------------------
typedef struct _CREDENTIALA {
DWORD Flags;
DWORD Type;
LPSTR TargetName;
LPSTR Comment;
FILETIME LastWritten;
DWORD CredentialBlobSize;
LPBYTE CredentialBlob;
DWORD Persist;
DWORD AttributeCount;
PCREDENTIAL_ATTRIBUTEA Attributes;
LPSTR TargetAlias;
LPSTR UserName;
} CREDENTIALA, *PCREDENTIALA;
--------------------------------------
When recounted in CredEnumerate, it can be acquired as certificate
data that is described with credential structure. However, the data
of the Web certification password are a server name and a title
character string in which Type being a member of this structure is
"1" and whose TargetName begins with "Microsoft_WinInet_." Since the
binary data are ciphered in CryptProtectData, which is a cryptography
function, their contents cannot be learned. By decoding with
CryptUnprotectData, we can finally acquire the username and password.
The password when decoding with CryptUnprotectData is the data that
is 4 times the string "abe2869f-9b47-4cd9-a358-c22904dba7f7."
A sample cord is as follows:
--------------------------------------------------------------------
// wincred.h is included in Platform SDK.
// To use CryptUnprotectData, it is necessary to enlink Crypt32.lib.
#include <windows.h>
#include <wincrypt.h>
#include <wincred.h>
void main(int argc,char *argv[])
{
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DATA_BLOB OptionalEntropy;
short tmp[37];
char *password={"abe2869f-9b47-4cd9-a358-c22904dba7f7"};
for(int i=0; i< 37; i++)
tmp[i] = (short int)(password[i] * 4);
OptionalEntropy.pbData = (BYTE *)&tmp;
OptionalEntropy.cbData = 74;
DWORD Count;
PCREDENTIAL *Credential;
if(CredEnumerate(NULL,0,&Count,&Credential)){
for(int i=0;i<Count;i++){
DataIn.pbData = (BYTE *)Credential[i] -> CredentialBlob;
DataIn.cbData = Credential[i] -> CredentialBlobSize;
if(CryptUnprotectData(&DataIn,NULL,&OptionalEntropy,NULL,NULL,0,&DataO
ut)){
printf("Type : %d\n",Credential[i] ->Type);
printf("TargetName : %s\n",Credential[i] ->TargetName);
printf("DataOut.pbData : %ls\n",DataOut.pbData);
}
}
CredFree(Credential);
}
}
--------------------------------------------------------------------
The result to execute the above-mentioned program is as follows:
The first data shows that the user ID and the password in https://
enter.nifty.com/ are " user ID: user-id, password:password."
--------------------------------------------------------------------
C:\>sample_1.exe
Type : 1
TargetName : Microsoft_WinInet_enter.nifty.com:443/Service
DataOut.pbData : user-id:password
Type : 1
TargetName : Microsoft_WinInet_192.168.0.1:80/test-server
DataOut.pbData : test:123456
--------------------------------------------------------------------
>> password by auto complete
The password by using auto complete is sorted in the following
registry:
"HKEY_CURRENT_USER/Software/Microsoft/Internet
Explorer/IntelliForms/Storage2"
In this registry, there are values whose name is a string of 42 bytes
in hexadecimal notation. Each is saved password data by auto
complete of each URL.
A string in hexadecimal notation is a hash value of the URL in which
the concerned password is input. The value is ciphered in
CryptProtectData using the URL as a password, and stored in
REG_BINARY.
We cannot guess the original strings from the hash value, and cannot
read the contents unless we know the URL. In this respect, the
security level has been improved from that in IE6.
In the sample of this paper, the URLs are acquired from the URL
history and deciphered in a round robin method. If the data is the
same as the registry name when calculating hash values of all URLs
visited in the past, it is the password of the concerned data.
(* We cannot decipher a password of a URL whose history is deleted.)
A cord is checked by looking through the history first.
--------------------------------------------------------------------
// retrieve the history of URL
int GetUrlHistory(wchar_t *UrlHistory[URL_HISTORY_MAX])
{
int max = 0;
CoInitialize(NULL);// COM Initialization
IUrlHistoryStg2* pUrlHistoryStg2=NULL;
HRESULT hr = CoCreateInstance(CLSID_CUrlHistory, NULL,
CLSCTX_INPROC_SERVER,IID_IUrlHistoryStg2,(void**)(&pUrlHistoryStg2));
if(SUCCEEDED(hr)){
IEnumSTATURL* pEnumUrls;
hr = pUrlHistoryStg2->EnumUrls(&pEnumUrls);
if (SUCCEEDED(hr)){
STATURL StatUrl[1];
ULONG ulFetched;
while (max<URL_HISTORY_MAX && (hr = pEnumUrls->Next(1,
StatUrl, &ulFetched)) == S_OK){
if (StatUrl->pwcsUrl != NULL) {
// If there is a parameter,delete it.
wchar_t *p;
if(NULL!=(p = wcschr(StatUrl->pwcsUrl,'?')))
*p='\0';
UrlHistory[max] = new
wchar_t[wcslen(StatUrl->pwcsUrl)+1];
wcscpy(UrlHistory[max],StatUrl->pwcsUrl);
max++;
}
}
pEnumUrls->Release();
}
pUrlHistoryStg2->Release();
}
CoUninitialize();
return max;
}
--------------------------------------------------------------------
Microsoft documents say that the history of IE can be read with the
EnumUrls method of the IUrlHistoryStg interface.
【 IUrlHistoryStg Interface 】
http://msdn.microsoft.com/workshop/networking/urlhist/iurlhistorystg/i
urlhistory.asp
【 IUrlHistoryStg::EnumUrls Method 】
http://msdn.microsoft.com/workshop/networking/urlhist/iurlhistorystg/e
numurls.asp
In the sample above, the parameter after ? is deleted from enumerated
strings and stored.
We are going to describe the method to acquire the hash string, which
is a value of the registry, from the URL that will be a password.
The hash value calculates the value by CryptHashData, while the
relevant URL is used as a password, and makes 20 bytes from the
beginning of the strings. This poses a problem as 2 bytes fewer than
the 42 bytes of the registry name make the 20 bytes of a string into
40 bytes. The value of the first 1 byte is acquired by adding each
byte from the beginning to the 20th byte. In the sample, we
calculate the last 1 byte in an unsigned char tail. Please use this
as a reference.
--------------------------------------------------------------------
// Calculate the hash value from Password, and retrieve it as a
character string "Hashstr."
void GetHashStr(wchar_t *Password,char *HashStr)
{
HashStr[0]='\0';
HCRYPTPROV hProv = NULL;
HCRYPTHASH hHash = NULL;
CryptAcquireContext(&hProv, 0,0,PROV_RSA_FULL,0);
// instance of hash calculation
if(CryptCreateHash(hProv,CALG_SHA1, 0, 0,&hHash)){
//calculation of hash value
if(CryptHashData(hHash,(unsigned char
*)Password,(wcslen(Password)+1)*2,0)){
// retrieve 20 bytes of hash value
DWORD dwHashLen=20;
BYTE Buffer[20];
if(CryptGetHashParam(hHash,HP_HASHVAL,Buffer,&dwHashLen,0)){
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
// creation of character string based on hash
char TmpBuf[128];
unsigned char tail=0;// variable to calculate value
for the last 2 bytes
// convert to a character string in hexadecimal
notation
for(int i=0;i<20;i++){
unsigned char c = Buffer[i];
tail+=c;
wsprintf(TmpBuf,"%s%2.2X",HashStr,c);
strcpy(HashStr,TmpBuf);
}
// add the last 2 bytes
wsprintf(TmpBuf,"%s%2.2X",HashStr,tail);
strcpy(HashStr,TmpBuf);
}
}
}
}
--------------------------------------------------------------------
>> password by auto complete No.2
Enumerate all the values included in the target registry key
[Storage2], and compare it with the hash string derived from the URL
history. If the URL is the same, decode the registry value using the
URL as a password. The decoding should be done in
CryptUnprotectData. The procedure is the same as that of the Web
certification password.
The operation is implemented in the following cord.
--------------------------------------------------------------------
void main(int argc,char* argv[])
{
// retrieve URL from the history
wchar_t *UrlHistory[URL_HISTORY_MAX];
int UrlListoryMax = GetUrlHistory(UrlHistory);
char *KeyStr = {"Software\\Microsoft\\Internet
Explorer\\IntelliForms\\Storage2"};
HKEY hKey;
// enumerate values of the target registry
if(ERROR_SUCCESS==RegOpenKeyEx(HKEY_CURRENT_USER,KeyStr,0,KEY_QUERY_VA
LUE,&hKey)){
for(int i=0;;i++){
char Val[1024];
DWORD Size = 1024;
if(ERROR_NO_MORE_ITEMS==RegEnumValue(hKey,i,Val, &Size,
NULL,NULL, NULL, NULL))
break;
// compare the value of the retrieved registry with the
hash value of the history URL
for(int n=0;n<UrlListoryMax;n++){
char HashStr[1024];
// calculate hash using URL as Password
GetHashStr(UrlHistory[n],HashStr);
if(strcmp(Val,HashStr)==0){// find password(URL)
printf("ur : %ls\n",UrlHistory[n]);
printf("hash : %s\n",HashStr);
// retrieve data from the taget registry
DWORD BufferLen;
DWORD dwType;
RegQueryValueEx(hKey,Val,0,&dwType,NULL,&BufferLen);
BYTE *Buffer = new BYTE[BufferLen];
if(RegQueryValueEx(hKey,Val,0,&dwType,Buffer,&BufferLen)==ERROR_SUCCES
S){
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DATA_BLOB OptionalEntropy;
DataIn.pbData = Buffer;
DataIn.cbData = BufferLen;
OptionalEntropy.pbData = (unsigned char
*)UrlHistory[n];
OptionalEntropy.cbData =
(wcslen(UrlHistory[n])+1)*2;
//release protection
if(CryptUnprotectData(&DataIn,0,&OptionalEntropy,NULL,NULL,1,&DataOut))
{
PrintData((char *)DataOut.pbData);//
display the decoded data
LocalFree(DataOut.pbData);
}
delete [] Buffer;
}
break;
}
}
}
RegCloseKey(hKey);
}
}
--------------------------------------------------------------------
The internal structure of the decoded data is as follows.
The 4th byte and 8th byte are the header and size of the data. The
number of the included data is in the 20th byte from the beginning.
After that, information of one data, which consists of 16 bytes
appearing in series, and then the data themselves come. In the
information of 16 bytes, the date the data is saved, the offset,
where the data themselves are saved, and its size are stored.
The following code shows the data decoded, based on the data
structure.
--------------------------------------------------------------------
void PrintData(char *Data)
{
nsigned int HeaderSize;
unsigned int DataSize;
unsigned int DataMax;
memcpy(&HeaderSize,&Data[4],4); //the 4th byte from the beginning
is Header size
memcpy(&DataSize,&Data[8],4); //the 8th byte from the beginning
is Data size
memcpy(&DataMax,&Data[20],4); //the 20th byte from the
beginning is Data number
printf("HeaderSize=%d DataSize=%d
DataMax=%d\n",HeaderSize,DataSize,DataMax);
char *pInfo = &Data[36];
char *pData = &Data[HeaderSize];
// afterwards, the same number of information data (16 bytes) as
the data number comes
for(int n=0;n<DataMax;n++){
FILETIME ft,ftLocal;
SYSTEMTIME st;
unsigned int offset;
memcpy(&offset,pInfo,4); // the null byte from the beginning
of information data is the offset of the data
memcpy(&ft,pInfo+4,8); // the 4th byte from the beginning
of information data is the date
// the 12th byte from the beginning of information data is
the data length
FileTimeToLocalFileTime(&ft,&ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
char TmpBuf[1024];
int l = ::WideCharToMultiByte(CP_THREAD_ACP, 0,(wchar_t*)
&Data[HeaderSize+12+offset], -1, NULL, 0, NULL, NULL );
if(-1!=l){
::WideCharToMultiByte(CP_THREAD_ACP, 0,
(wchar_t*)&Data[HeaderSize+12+offset],
wcslen((wchar_t*)&Data[HeaderSize+12+offset])+1, TmpBuf, l, NULL,
NULL );
printf("[%d][%4.4d/%2.2d/%2.2d %2.2d:%2.2d]%s\n"
,n,st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,TmpBuf);
}
pInfo+=16;
}
}
--------------------------------------------------------------------
The result to execute the sample program is as follows.
--------------------------------------------------------------------
C:\>sample_2.exe
url :
http://www.amazon.co.jp/gp/flex/sign-out.html/ref=pd_irl_gw_r/XXX-XXXX
XXX-XXXXXXX
hash : 9EF333A1BDEDAA158D829497873EC11436ACDA9019
HeaderSize=56 DataSize=72 DataMax=2
[0][2006/11/23 11:24]user@xxxxxxxxxxxx
[1][2006/11/23 11:24]123456
--------------------------------------------------------------------
>> auto complete character string
Auto complete character strings are stored in nearly the same form as
the auto complete password. There are only two differences: the
first is that the registry in which the data is saved is as follows,
and the second is that the tag name of a form is used as a password
for encrypting data, instead of the URL.
"HKEY_CURRENT_USER/Software/Microsoft/Internet
Explorer/IntelliForms/Storage1"
The tag name of the form used for a password is "q" in the form like
this:
--------------------------------------------------------------------
<form name="test-form">
<br>search string:<input name="q" type="text">
<br><input name="btn" type=submit value="Google Search">
</form>
--------------------------------------------------------------------
As the tag name is not left in the computer while the history of URLs
are, we cannot check using a round robin method. Therefore, we
cannot decode unless we can guess the name.
We are going to illustrate the tag name used in the input form of
major search pages and its hash strings.
[Google]
q C6FB044EC2BD401521D6B1082276415638196D8004
[Yahoo JAPAN]
p E1D111AE435EE00BF07DF91CE5AF8FE83F7E3370EA
[MSN Japan]
q C6FB044EC2BD401521D6B1082276415638196D8004
[goo]
mt 8A40878496B3A02B8277C2AD25255C111A5A02D755
[infoseek]
qt 90D5C215D3DA44C6D0D6B7E9FD3CA053A5EFBEF1A8
If the hash strings above are in the registry "Storage1", they are
the search strings that were used in the relevant search page.
Further, the tag name should be lower case when used as a password.
For example, "Q" should be "q", and "Name" should be "name".
Then, we show the code that reads the auto complete data when the tag
name is "q" as follows:
GetHashStr(), which creates hash strings, and ProntData(), which
indicates retrieved data, are the same as used in the code of
"password by auto complete".
--------------------------------------------------------------------
void main(int argc,char* argv[])
{
wchar_t TagStr[128];
wcscpy(TagStr,L"q");
char HashStr[128];
GetHashStr(TagStr,HashStr);
char *KeyStr = {"Software\\Microsoft\\Internet
Explorer\\IntelliForms\\Storage1"};
HKEY hKey;
// enumerate values of the target registry
if(ERROR_SUCCESS==RegOpenKeyEx(HKEY_CURRENT_USER,KeyStr,0,KEY_QUERY_VA
LUE,&hKey)){
for(int i=0;;i++){
char Val[1024];
DWORD Size = 1024;
if(ERROR_NO_MORE_ITEMS==RegEnumValue(hKey,i,Val, &Size,
NULL,NULL, NULL, NULL))
break;
// compare the value of the retrieved registry with the
hash value of string 'q"
if(strcmp(Val,HashStr)==0){// find password(URL)
printf("tag : %ls\n",TagStr);
printf("hash : %s\n",HashStr);
// retrieve data from the taget registry
DWORD BufferLen;
DWORD dwType;
RegQueryValueEx(hKey,Val,0,&dwType,NULL,&BufferLen);
BYTE *Buffer = new BYTE[BufferLen];
if(RegQueryValueEx(hKey,Val,0,&dwType,Buffer,&BufferLen)==ERROR_SUCCES
S){
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DATA_BLOB OptionalEntropy;
DataIn.pbData = Buffer;
DataIn.cbData = BufferLen;
OptionalEntropy.pbData = (unsigned char *)TagStr;
OptionalEntropy.cbData = (wcslen(TagStr)+1)*2;
//release protection
if(CryptUnprotectData(&DataIn,0,&OptionalEntropy,NULL,NULL,1,&DataOut))
{
PrintData((char *)DataOut.pbData);
LocalFree(DataOut.pbData);
}
delete [] Buffer;
}
break;
}
}
RegCloseKey(hKey);
}
}
--------------------------------------------------------------------
The results from executing the sample program are as follows:
--------------------------------------------------------------------
C:\>sample_3.exe
tag : q
hash : C6FB044EC2BD401521D6B1082276415638196D8004
HeaderSize=104 DataSize=68 DataMax=5
[0][2006/11/25 06:35]test
[1][2006/11/25 06:35]test2
[2][2006/11/25 07:42]travel Hokkaido
[3][2006/11/25 07:49]sightseeing
[4][2006/11/25 07:50]hotel
--------------------------------------------------------------------
>> Summary
We have described the closed data saved by IE7. This is the method
for handling the data that should be concealed in a normal
situation. Therefore, if you use this technique, for example to
create applications, pay attention to security issues.