<<< Date Index >>>     <<< Thread Index >>>

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&#65288;http://homepage2.nifty.com/spw/&#65289;
                  at 
ScanNetSecurity&#65288;https://www.netsecurity.ne.jp/&#65289;
----------------------------------------------------------------------
&#12288;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 &#9679;&#9679;&#9679;&#9679;.  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&#65306;<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.

&#12304; About Authentication Functions&#12288;- Credentials Management 
Functions&#12305;
http://msdn.microsoft.com/library/en-us/secauthn/security/authenticati
on_functions.asp

&#12304;CREDENTIALStructure Excerpt from&#12288;wincred.h&#12305;
--------------------------------------
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.

&#12304; IUrlHistoryStg Interface &#12305;
http://msdn.microsoft.com/workshop/networking/urlhist/iurlhistorystg/i
urlhistory.asp

&#12304; IUrlHistoryStg::EnumUrls Method &#12305;
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&#12288;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&#65288;URL&#65289;
                    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&#65288;URL&#65289;
                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.