Password Algorithms: Internet Explorer 10 (Windows Vault)

Introduction

Microsoft added a new feature to Windows 7 called ‘Vault’ which you can access through the Credential Manager in control panel or vaultcmd from command line. It works very similar to Gnome Key Ring on Linux or the Apple Keychain on Mac OS.

In versions 7, 8 and 9 of Internet Explorer, passwords were protected using DPAPI and the login URL as entropy before being saved in the registry. The new algorithm in IE10 continues to use DPAPI but the encryption of credentials is now handled by the Vault Service.

Vault System Service

Like most Windows Encryption, the protection of Vault data occurs within a LocalSystem service. vaultsvc.dll contains the service code and is loaded by the Local Security Account Subsystem (lsass.exe) at boot time.

Between 18-24 functions (depending on OS) are exposed to clients over a local RPC end point. On Windows 7 is an additional KeyRing Credential UI application (VaultSysUI.exe) launched by the service if it requires information from the owner of a vault.

For example, you have the ability to lock a vault with a password.
windows_7_lock
You can also configure a vault to require permission from the user when an application attempts to access the password element.
windows_7_prompt
In both situations, VaultSysUI will display a window to the user and then write the response back to heap memory which Vault Service can access. :-)

Although both these features are useful and add further protection to a user’s credentials, they were removed in Windows 8 along with other functionality.

Vault Client Library Access

From the user session, RPC calls are made through API exported by vaultcli.dll
Explorer.exe loads Credui.dll and Vault.dll when accessing the Credential Manager through the Control Panel.
credman
You can also use vaultcmd.exe to add/remove credentials but it doesn’t display passwords on either 7 or 8.
vaultcmd7
On Windows 8 . . .
vaultcmd8
For whatever reasons, there was a pretty significant reduction in Vault features between Windows 7 and 8. Below is a list of what was removed.

  • Creation / Deletion of vaults.
  • Loading / Unloading external vault files.
  • Locking / Unlocking vaults with additional password protection.

Protection Methods

Windows 7 has 2 methods available but Windows 8 only has 1.
DPAPI (Data Protection API) is used by default but on Windows 7, you can also use a password.

The algorithm used to protect passwords is RSA PBKDF2.

Recovery of Web Credentials

As said, there were some changes to Vault service between Windows 7 and 8.
VaultGetItem requires an additional parameter on Windows 8 and the VAULT_ITEM structure has an extra property. Here’s the structure for Windows 7

typedef struct _VAULT_ITEM_W7 {
  GUID SchemaId;
  LPCWSTR pszCredentialFriendlyName;
  PVAULT_ITEM_ELEMENT pResourceElement;
  PVAULT_ITEM_ELEMENT pIdentityElement;
  PVAULT_ITEM_ELEMENT pAuthenticatorElement;
  FILETIME LastModified;
  DWORD dwFlags;
  DWORD dwPropertiesCount;
  PVAULT_ITEM_ELEMENT pPropertyElements;
} VAULT_ITEM_W7, *PVAULT_ITEM_W7;

And for Windows 8 . . .

typedef struct _VAULT_ITEM_W8 {
  GUID SchemaId;
  LPCWSTR pszCredentialFriendlyName;
  PVAULT_ITEM_ELEMENT pResourceElement;
  PVAULT_ITEM_ELEMENT pIdentityElement;
  PVAULT_ITEM_ELEMENT pAuthenticatorElement;
  PVAULT_ITEM_ELEMENT pPackageSid;
  FILETIME LastModified;
  DWORD dwFlags;
  DWORD dwPropertiesCount;
  PVAULT_ITEM_ELEMENT pPropertyElements;
} VAULT_ITEM_W8, *PVAULT_ITEM_W8;

I’ve written a tool to recover IE10 passwords using the Vault API, here’s example of output on Windows 7 machine.
ie10_decode
For those of you that want to know more about recovery process, you can grab source and binary here.
Because the Windows Vault Service remains undocumented, I can’t guarantee the accuracy of information provided. The latest protection of Web Credentials for Internet Explorer is indeed weaker than previous algorithm for 7, 8 and 9 but the upside is that with the Vault you can reliably backup/restore your passwords when needed.

Below is just a list of API available/removed between Windows 7 and 8.

Credential Vault Client Library Function Windows 7 Windows 8
VaultCreateItemType Yes Yes
VaultDeleteItemType Yes Yes
VaultEnumerateItemTypes Yes Yes
VaultAddItem Yes Yes
VaultFindItems Yes Yes
VaultEnumerateItems Yes Yes
VaultGetItem Yes Yes
VaultRemoveItem Yes Yes
VaultGetItemType Yes Yes
VaultOpenVault Yes Yes
VaultCloseVault Yes Yes
VaultGetInformation Yes Yes
VaultEnumerateVaults Yes Yes
VaultSetInformation Yes No
VaultCreateVault Yes No
VaultCopyVault Yes No
VaultDeleteVault Yes No
VaultLoadVaults Yes No
VaultUnloadVaults Yes No
VaultCopyItem Yes No
VaultMoveItem Yes No
VaultLockVault Yes No
VaultUnlockVault Yes No
VaultConfirmVaultAccess Yes No
VaultEnumerateSettingUnits No Yes
VaultGetSettingUnit No Yes
VaultApplySettingUnit No Yes
VaultRemoveSettingUnit No Yes
VaultTriggerSync No Yes

Password Algorithms: Cisco Unified Personal Communicator


This application took some time to acquire online because Cisco prevents you downloading unless you are:

  • Direct Customer
  • Partner-Reseller
  • Service Contract Owners
  • CCIE Professional
  • PICA Customer

I’ve installed on my Windows 7 workstation so results may differ.
There’s a checkbox beneath the username and password fields labelled Automatically Sign In which is disabled by default.

If you check this box, a DAT file will be created in your profile under the following path:

C:\Users\dietrich\AppData\Local\Cisco\Unified Communications\Client Services Framework\Config\Profile

The name of the file is generated by UuidCreate()
The contents are encoded with Base64 and once decoded into binary appears to be DPAPI blob.

  00000000 01 00 00 00 d0 8c 9d df 01 15 d1 11 8c 7a 00 c0      ....ð..¯..Ð..z.+
  00000010 4f c2 97 eb 01 00 00 00 57 4a ac d0 b9 66 a5 4b      O-.Ù....WJ¼ð¦fÑK
  00000020 b3 47 a3 ea 37 85 af 43 00 00 00 00 32 00 00 00      ¦GúÛ7.»C....2...
  00000030 61 00 75 00 74 00 68 00 5f 00 73 00 76 00 6e 00      a.u.t.h._.s.v.n.
  00000040 2e 00 73 00 69 00 6d 00 70 00 6c 00 65 00 2e 00      ..s.i.m.p.l.e...
  00000050 77 00 69 00 6e 00 63 00 72 00 79 00 70 00 74 00      w.i.n.c.r.y.p.t.

What’s interesting about this blob in particular is the description string which is: “auth_svn.simple.wincrypt”

I initially thought this may be part of some library and sure enough it was just that! :) LibSVN to be exact.

Here’s a snippet of the code in win32_crypto.c which uses the same description to encrypt data.

/*-----------------------------------------------------------------------*/
/* Windows simple provider, encrypts the password on Win2k and later.    */
/*-----------------------------------------------------------------------*/

/* The description string that's combined with unencrypted data by the
   Windows CryptoAPI. Used during decryption to verify that the
   encrypted data were valid. */
   
static const WCHAR description[] = L"auth_svn.simple.wincrypt";

/* Implementation of svn_auth__password_set_t that encrypts
   the incoming password using the Windows CryptoAPI. */
static svn_boolean_t
windows_password_encrypter(apr_hash_t *creds,
                           const char *realmstring,
                           const char *username,
                           const char *in,
                           apr_hash_t *parameters,
                           svn_boolean_t non_interactive,
                           apr_pool_t *pool)
{
  DATA_BLOB blobin;
  DATA_BLOB blobout;
  svn_boolean_t crypted;

  blobin.cbData = strlen(in);
  blobin.pbData = (BYTE*) in;
  crypted = CryptProtectData(&blobin, description, NULL, NULL, NULL,
                             CRYPTPROTECT_UI_FORBIDDEN, &blobout);
  if (crypted)
    {
      char *coded = apr_palloc(pool, apr_base64_encode_len(blobout.cbData));
      apr_base64_encode(coded, (const char*)blobout.pbData, blobout.cbData);
      crypted = svn_auth__simple_password_set(creds, realmstring, username,
                                              coded, parameters,
                                              non_interactive, pool);
      LocalFree(blobout.pbData);
    }

  return crypted;
}

Rather than write a decryption tool, I just dumped the contents of output returned by CryptUnprotectData() in debugger.

It’s an XML file which contains plaintext credentials and this is how Personal Communicator Automatically signs in. :)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <UserCredentialDetails><profileName>Profile1</profileName>
    <credentials><username>userid</username>
      <password>password</password>
      <credentialsType>PRESENCE_SERVICE</credentialsType>
      <rememberMe>true</rememberMe>
    </credentials>
  </UserCredentialDetails>

It may be possible to have multiple profiles but I didn’t look into it.

Password Algorithms: Internet Explorer 7, 8, 9

Introduction

IE10 on Windows 8 uses a different algorithm for encryption and storage so I might follow up with separate entry later. For now I’m analysing version 9.0.9 on Windows 7.
Everything here should work fine with legacy IE 7 and 8.

Considering customers may avoid migrating to Windows 8, I thought this protection was still worth covering in detail.

Storage

All autocomplete entries for a user are stored in NTUSER.DAT and they consist of a SHA-1 hash and DPAPI blob. Here’s a dump of some hashes from my own system..

C:\>reg query "HKCU\Software\Microsoft\Internet Explorer\IntelliForms\Storage2"

HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IntelliForms\Storage2
    6FBD22A243E7F5A0D660199683F52543E80CEB99EC    REG_BINARY    01000000D08C9DDF0115D1118. . .
    DF11F9BE8F0049A2FBFF29C6D49FE77383C2A6783A    REG_BINARY    01000000D08C9DDF0115D1118. . .
    E4CE6B2B79515319A7360D97E3B217F2FC843CC019    REG_BINARY    01000000D08C9DDF0115D1118. . .

The blobs have been truncated to avoid potential offline decryption.
Whenever IE connects to a site which requires login credentials, it will:

  1. Derive SHA-1 checksum of lowercase(URL).
  2. Search for the checksum in autocomplete entries.
  3. If checksum is found, decrypt DPAPI blob using URL and autofill the login fields.

Generation

Take the second hash..

DF11F9BE8F0049A2FBFF29C6D49FE77383C2A678 3A

This is a SHA-1 checksum of the unicode string “https://accounts.google.com/servicelogin”
The last byte 0x3A is a checksum based on addition of each byte in SHA-1 result.
The following function demonstrates this with Windows crypto 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;
}

Each username and password is stored in unicode format.
If there’s more than 1 set of credentials for the same URL, these will be added to the existing data.

The problem is that the actual structure for an entry is officially undocumented.
Fortunately, there’s an older revision of the structure online which helps a lot! :)

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[];
};

Parsing a decrypted blob using this structure for reference caused a few headaches and required minor changes. In IEFrame.dll, CryptProtectData() is used with URL as entropy to encrypt StringIndex + credentials.

The next problem is discovering the original URL used as entropy and this is what makes IE password algorithm quite good..

Obtaining URLs

There are a number of ways to harvest URLs for the purpose of recovering IE7-IE9 passwords. The Cache normally has a list of websites visited which can be enumerated.
Here’s one such way using COM

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();
  }  
}

And another using 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);
  }
}

To take things a bit further, you could also parse index.dat files but I won’t go into that here since it’s in the realm of forensics.
A better approach is probably reading a list of URLs harvested from the internet.

Recovery

Recovery is close to how IE7-IE9 process decrypts entries except we’re forcing the decryption process using a list of URL.
The following collects a list of auto complete entries


#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();
}

Now with list of URL strings and autocomplete entries, we can attempt to decrypt using CryptUnprotectData() The decrypted data is then parsed based on the StringIndex structure.

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);
  }
}

Conclusion

Because the URL is used as entropy, that can be problemtatic recovering all autocomplete entries.
It would be simple to recover credentials of popular services like Facebook, Gmail, Instagram, Hotmail..etc but the less well known services would be problem unless URL was stored in cache.

Password Algorithms: Skype (Windows)

Introduction

There’s a fantastic article by Fabrice Desclaux and Kostya Kortchinsky which describes the encryption and structures used but doesn’t provide any code.

The article indicates it isn’t possible to decrypt the password which probably explains the lack of code by other people.

Taken from the article…

If told to, Skype will save in the config.xml file

  • The login MD5 hash (username\nskyper\password)
  • The generated RSA private key
  • The Skype encrypted corresponding RSA public key

Everything is heavily encrypted, but in a symmetric way :)
The following algorithms are used

  • CryptProtectData(), CryptUnprotectData()
  • SHA-1
  • AES-256
  • “FastTrack Cipher”
  • 1024+ bit RSA

Only an MD5 hash of password is stored in the user’s profile and it’s encrypted with AES-256
Once hash is decrypted, the only recovery methods available are dictionary attack or variation of brute force.

The information here applies to version 5.10.0.116 but should also work without hitch on some older versions (4.2 was also tested)

Storage

There are 2 things required in order to dump the MD5 hash.

  1. DPAPI blob :
    HKEY_CURRENT_USER\Software\Skype\ProtectedStorage

  2. Credentials ciphertext :
    %APPDATA%\Skype\<login id>\config.xml

The DPAPI blob is just stored as binary and can be passed straight to CryptUnprotectData()

C:\>reg query HKCU\Software\Skype\ProtectedStorage

HKEY_CURRENT_USER\Software\Skype\ProtectedStorage
    0    REG_BINARY    01000000D08C9DDF0115D1118C7A00C04FC297EB01000

The Credentials are hexadecimal string stored in XML file

<?xml version="1.0"?>
<config version="1.0" serial="66" timestamp="1344481520.27">
  <Lib>
    <Account>
      <Credentials3>322EBDF6D922E91F7EB68

As a result of the XML file I ended up using the following libraries from here:

  • libxml2-2.7.8.win32.zip
  • iconv-1.9.2.win32.zip
  • openssl-0.9.8a.win32.zip
  • zlib-1.2.5.win32.zip

Generation

The following demonstrates creation of the MD5 hash using OpenSSL

void GenHash(const char *id, const char *pwd) {
    MD5_CTX ctx;
    const char *skype = "\nskyper\n";
    u_int8_t dgst[32];
    
    MD5_Init(&ctx);
    MD5_Update(&ctx, id, strlen(id));
    MD5_Update(&ctx, skype, strlen(skype));
    MD5_Update(&ctx, pwd, strlen(pwd));
    MD5_Final(dgst, &ctx);

    printf("\n  Login ID = %s"
           "\n  Password = %s"
           "\n  MD5 hash = ", id, pwd);
    
    for (int i = 0;i < 16;i++) {
      printf("%02x", dgst[i]);
    }
    printf("\n");
}
.....
C:\>skype_dump username password
  ...
  Login ID = username
  Password = password
  MD5 hash = 27f6a9d892475e6ce0391de8d2d893f7

Recovery

To extract the Credentials ciphertext, you could read the contents of config.xml and scan for <Credentials3> and </Credentials3>
Here, I’m using LibXML :P

bool GetCredentials(BYTE ciphertext[], std::string config_xml) {    
    bool bFound = false;
    
    // try open config.xml
    xmlTextReaderPtr reader;
    reader = xmlReaderForFile(config_xml.c_str(), NULL, 0);
    
    // tested with Credentials2 or Credentials3
    const xmlChar *credentials; 
    credentials = (const xmlChar*)"Credentials";

    if (reader != NULL) {
    
      // while nodes are available
      while (xmlTextReaderRead(reader) == 1) {
        // get name
        const xmlChar *name;
        name = xmlTextReaderConstName(reader);
        if (name == NULL) continue;

        // equal to credentials we're searching for?
        if (xmlStrncmp(credentials, name, xmlStrlen(credentials)) == 0) {

          // read the next value
          if (xmlTextReaderRead(reader) == 1) {
            const xmlChar *value;
            value = xmlTextReaderConstValue(reader);
            
            for (int i = 0;i < 16;i++) {
              sscanf((const char*)&value[i * 2], "%02x", &ciphertext[i]);
            }
            bFound = true;
            break;
          }
        }
      }
      xmlFreeTextReader(reader);
    }
    xmlCleanupParser();
    return bFound;
}

Obtain the salt which is passed to SHA-1 before being used to create AES key.

PBYTE GetSalt(DWORD &cbSalt) {
    BYTE aBlob[2048];
    DWORD cbSize = sizeof(aBlob);
    const char skype_path[] = "Software\\Skype\\ProtectedStorage";
    
    LSTATUS lStatus = SHGetValue(HKEY_CURRENT_USER, skype_path, 
        "0", 0, aBlob, &cbSize);
      
    if (lStatus != ERROR_SUCCESS) {
      printf("  Unable to open skype key : %08x", lStatus);
      return NULL;
    }

    DATA_BLOB in, out;
    
    in.pbData = aBlob;
    in.cbData = cbSize;
    
    if (CryptUnprotectData(&in, NULL, NULL, NULL, 
        NULL, 0, &out)) {
      cbSalt = out.cbData;
      return out.pbData;
    } else {
      printf("  Unable to decrypt skype entry.");
    }
    return NULL;
}

Then with both the ciphertext and salt, we can decrypt MD5 hash…

void DecryptHash(PBYTE pbCipherText, PBYTE pbSalt, DWORD cbSalt) {
    
    SHA_CTX ctx;
    AES_KEY key;
    
    u_int8_t dgst[40], buffer[AES_BLOCK_SIZE];
    
    memset(&buffer, 0, sizeof(buffer));
    
    // use counter mode + SHA-1 to generate key
    for (ULONG i = 0;i < 2;i++) {
      ULONG ulIndex = _byteswap_ulong(i);
        
      SHA1_Init(&ctx);
      SHA1_Update(&ctx, &ulIndex, sizeof(ulIndex));
      SHA1_Update(&ctx, pbSalt, cbSalt);
      SHA1_Final(&dgst[i*20], &ctx);
    }
    
    AES_set_encrypt_key(dgst, 256, &key);
    AES_encrypt(buffer, buffer, &key);
    
    printf("\n  MD5 hash = ");
    
    // decrypt MD5 hash with XOR
    for (int i = 0;i < 16;i++) {
      printf("%02x", pbCipherText[i] ^ buffer[i]);
    }
    printf("\n");
}

Conclusion

If you want to know more about the internals of Skype, I’d strongly recommend the “Vanilla Skype” papers 1 and 2

It’s safe to say MD5 isn’t a good choice of algorithms for protecting passwords.
Maybe as more recovery tools become available, Microsoft will revise the code to use something stronger.
source code

Password Algorithms: Yahoo Messenger

Introduction

OPSWAT listed Yahoo Messenger as the 3rd most popular IM application in June 2011 with 15.10% of market share.

I read Slicks in-depth analysis already and other claims of recovery being “impossible” so I was interested to see if any progress could be made upon Slicks earlier work.

As Slick indicates in his article, it would appear the only login information stored on the user’s computer is a token, therefore you can certainly transfer the token to another machine but you can’t recover the plaintext password.

Storage

Based on version 11.5.0 which is the latest release, if the user checks “Remember My ID and password”, the application will store some session information in the user’s personal registry file: NTUSER.DAT

C:>reg query "HKCU\Software\Yahoo\pager" /v ETS

HKEY_CURRENT_USER\Software\Yahoo\pager
    ETS REG_SZ  eJxjZGBguNAz9z6j6EXBniqGA/6Hpr9mBIq90W3gn8fk6T . . .

The above value is truncated but from analysing the binaries, it’s definitely a Base64 string.

Algorithm

If you pass the ETS string to OpenSSL for decoding, it reveals a ZLib blob which you can tell from the first couple of bytes. 0x78 0x9c

# hexdump ets.bin
00000000: 78 9c 63 64 60 60 b8 d0 - 33 f7 3e a3 e8 45 c1 9e   x.cd.... 3....E..
00000010: 2a 86 03 fe 87 a6 bf 66 - 04 8a bd d1 6d e0 9f c7   .......f ....m...
00000020: e4 e9 3a 6d 8a d0 c7 43 - ba 8d 6b 80 42 0c 4c 20   ...m...C ..k.B.L.
00000030: 82 81 39 8d 81 61 05 90 - 16 00 e2 9a da fe 63 a5   ..9..a.. ......

We can decompress this stream using Zpipe and dump it once again to see the results.

# zpipe -d < ets.bin > unknown.bin
# hexdump unknown.bin
00000000: 01 00 00 00 d0 8c 9d df - 01 15 d1 11 8c 7a 00 c0   ........ .....z..
00000010: 4f c2 97 eb 01 00 00 00 - ec 2d 80 0f 9e 02 49 45   O....... ......IE
00000020: 96 94 12 f1 c2 2d 81 ac - 00 00 00 00 02 00 00 00   ........ ........
00000030: 00 00 03 66 00 00 a8 00 - 00 00 10 00 00 00 7c 7d   ...f.... ........

The above binary data is a DPAPI blob and this is passed to CryptUnprotectData using some entropy.

The entropy is the string “MBCS sucks” with the Yahoo id appended, for example.

MBCS suckssomeyahooid@domain.com

The result of the decrypted DPAPI blob is a Yahoo 64 encoded string which Slick discusses in his article.
My own cookie/token looked something like the following:

ALRX7U8E1cicRDzhXXX5vlue_Mg.9BmHv25VeQRdTriqUbw6MccGEg--

The Yahoo 64 algorithm uses same alphabet as standard Base64 except the last 2 bytes are different and padding uses “-” instead of “=”

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"

Conclusion

We’ve seen how it’s possible to dump the token from the registry which can at least in theory be used to login from another system.

Perhaps in future entry the token generation can be revealed :)

To be continued . . .

Password Algorithms: Google Chrome

Introduction

Google Chrome is very popular right now for web browsing.

As of June 2012, it accounts for 27% of market share according to Wikipedia statistics. which is the largest ahead of Internet Explorer (24%) and Firefox. (19%)

It’s main user base appears to be in Russia, India and South American nations.

I’m analysing the browsers password algorithm on Windows so it could differ on other platforms.

Storage

Based on the current version, 20.0.1132.47, Chrome asks me to “Save password” for gmail login.

After confirming to save, a file called Login Data appears in my windows profile.

C:\Documents and Settings<user id>\Local Settings\Application Data\Google\Chrome\User Data\Default

If you’re on Vista or later, you would find it created in

C:\Users\<user id>\AppData\Local\Google\Chrome\User Data\Default

A hex dump of this file tells us it’s an SQLITE3 database.

00000000: 53 51 4c 69 74 65 20 66 - 6f 72 6d 61 74 20 33 00   SQLite.f ormat.3.
00000010: 08 00 01 01 00 40 20 20 - 00 00 00 04 00 00 00 06   ........ ........
00000020: 00 00 00 00 00 00 00 00 - 00 00 00 03 00 00 00 01   ........ ........
00000030: 00 00 00 00 00 00 00 00 - 00 00 00 01 00 00 00 00   ........ ........

Using the sqlite3 shell, let’s look inside.

# sqlite3 "Login Data"
SQLite version 3.7.11 2012-03-20 11:35:50
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
logins  meta
sqlite> .mode column
sqlite> .width 2 20
sqlite> pragma table_info(logins);
0   origin_url            VARCHAR     1                       0
1   action_url            VARCHAR     0                       0
2   username_element      VARCHAR     0                       0
3   username_value        VARCHAR     0                       0
4   password_element      VARCHAR     0                       0
5   password_value        BLOB        0                       0
6   submit_element        VARCHAR     0                       0
7   signon_realm          VARCHAR     1                       0
8   ssl_valid             INTEGER     1                       0
9   preferred             INTEGER     1                       0
10  date_created          INTEGER     1                       0
11  blacklisted_by_user   INTEGER     1                       0
12  scheme                INTEGER     1                       0

Obviously you can use GUI for this too but I’m fan of the command line generally as it can be quicker once you master it.

I’ve dumped the entries from the database to a text file for a better look

echo .dump | sqlite3 "Login Data" >logins.txt

The password_value has been truncated here to fit on the page and also hide ciphertext which could be decrypted offline but looks something like.

X'01000000D08C9DDF0115D1118C7A00C04FC297EB01000 . . .

Initially, this blob appears to be a DPAPI blob and on closer inspection of binaries, I confirm CryptUnprotectData() is used without any entropy values.

Generation

For Windows operating systems the password_value is essentially a DPAPI blob derived from CryptProtectData() and inserted into the “Login Data” database.

On my own Linux system, Chrome uses the Gnome-Keyring and how it works is outside the scope of a short blog entry :)

The Login Data file does appear in

/home/dietrich/.config/google-chrome/Default

However, it doesn’t contain any entries and I haven’t tried to disable the keyring service to observe what effect that has on Chrome and it’s functionaliy.

Recovery

Because DPAPI encrypts data based on either the DPAPI_SYSTEM values stored in LSA secrets or the user’s password, you can’t copy the database file to another machine and decrypt without using special tools.

In order for the following code to work, it must be executed under the profile of user that saved the passwords.

I had to compile SQLITE3 library for Visual Studio which was very straight forward.

Download the SQLITE3 sources, compile and create library.

cl /O1 /Os /Oy /GS- sqlite3.c /c
lib sqlite3.obj /out:sqlite3.lib

First part of program obtains the path of database.

    string login_db;
    
    // if user doesn't provide filename, app will use local profile
    if (argc > 1) {
      login_db = argv[1];
    } else {
      CHAR lpszPath[MAX_PATH];
      
      if (!SHGetSpecialFolderPath(NULL, lpszPath, 
            CSIDL_LOCAL_APPDATA, FALSE)) {
        
        printf("\nUnable to determine \"Local Settings\" folder");
        return 0;
      }
      login_db = lpszPath;
      login_db += "\\Google\\Chrome\\User Data\\Default\\Login Data";
    }
    
    // ensure file exists
    if (GetFileAttributes(login_db.c_str()) == INVALID_FILE_ATTRIBUTES) {
      printf("\n\"%s\" does not exist\n", login_db.c_str());
      return 0;
    }

Load the database into memory using SQLITE3 library functions and query the password_value before passing to CryptUnprotectData()
We’ll need the username_value and signon_realm too.

sqlite3 *db;

// open database
if (sqlite3_open(login_db.c_str(), &db) == SQLITE_OK) {
  sqlite3_stmt *stmt;
  string query = "SELECT username_value, password_value, signon_realm FROM logins";

  // execute SQL statement
  if (sqlite3_prepare_v2(db, query.c_str(), -1, stmt, 0) == SQLITE_OK) {

    while (sqlite3_step(stmt) == SQLITE_ROW) {
      DATA_BLOB in, out;
      string realm, username, password;

      username = (char*)sqlite3_column_text(stmt, 0);
      realm = (char*)sqlite3_column_text(stmt, 2);

      in.pbData = (LPBYTE) sqlite3_column_blob(stmt, 1);
      in.cbData = sqlite3_column_bytes(stmt, 1);

      // decrypt using DPAPI
      if (CryptUnprotectData(&in, NULL, NULL, NULL, NULL, 1, &out)) {
        password = (char*)out.pbData;
        password[out.cbData] = 0;

        LocalFree(out.pbData);
      } else {
        password = "<decryption failed>";
      }
      printf("\n%s | %s | %s", username.c_str(), password.c_str(), realm.c_str());
    }

    sqlite3_finalize(stmt);
  } else {
    printf("\n[-] sqlite3_prepare_v2("%s") : %s\n",
        login_db.c_str(), sqlite3_errmsg(db));
  }
  sqlite3_close(db);
} else {
  printf("\n[-] sqlite3_open("%s") : %s\n",
    login_db.c_str(), sqlite3_errmsg(db));
}

The code here is just to demonstrate reading each entry from database and passing to windows for decryption.

Conclusion

The strength of DPAPI depends on how strong the user’s password is or if backup keys are available

There are solutions that perform recovery without the user’s password but we’ll look at this later. :)