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

Active Directory Password Hash Extraction

Just added a tool for offline Active Directory password hash extraction.
It has very basic functionality right now but much more is planned.

Command line application that runs on Windows only at the moment.

  ntds_decode -s <FILE> -d <FILE> -m -i

    -s <FILE> : SYSTEM registry hive
    -d <FILE> : Active Directory database
    -m        : Machines (omitted by default)
    -i        : Inactive, Locked or Disabled accounts (omitted by default)

The SYSTEM registry hive and Active Directory database are from a domain controller.
These files are obviously locked so you need to backup using the Volume Shadow Copy Service.

The output format is similar to pwdump and only runs on Windows at the moment.
LM and NTLM hashes are extracted from active user accounts only.

ntds_decode mounts the SYSTEM file so Administrator access is required on the computer you run it on.

If you’re an experienced pen tester or Administrator that would like to test this tool, you can grab from here

It’s advisable you don’t use the tool unless you know what you’re doing.
Source isn’t provided at the moment because it’s too early to release.

If you have questions about it, feel free to e-mail the address provided in README.txt

Password Algorithms: Windows System Key (SYSKEY)

I stumbled upon some forum posts related to System Key recently and read something about 1 of the authentication modes available to Administrators that made me wonder if true or not.

Just to note, there are 3 modes.

  1. Generated by passphrase
  2. Stored in registry
  3. Stored on removable storage device

2 is enabled by default, but you can change this with the syskey.exe utility.

The claim was that if you forgot the passphrase or “startup password” there’s no reliable method of recovery. The “only way” to get back into the system is to restore a backup if one is available or disable completely using something like ntpasswd

In most cases, either way is probably sufficient enough, but there are situations where you would need to know the original passphrase and don’t have a backup available or perhaps you can’t even use a backup which could erase some critical information required.

There are a number of ways to recover the passphrase but I’ll just suggest one for now.
Found this short video which shows someone enabling the startup password

One of the the comments is “BOSS HOW WE HACK SYSKEY!!!” :-)

History of SYSKEY

SYSKEY was Microsoft’s response to pwdump and L0phtCrack.
It was provided as an optional security enhancement with Windows NT SP3 and enabled by default since the release of Windows 2000.

The purpose of this feature was to prevent pwdump working without modifications. Open source offline decryption tools didn’t surface until the release of samdump2 by Nicola Cuomo.

What follows is a short timeline of events related to SYSKEY.

March 1997 Samba developer Jeremy Allison publishes pwdump which enables Administrators to dump LM and NTLM hashes stored in the SAM database.
April 1997 L0pht publishes L0phtcrack which allows Administrators to audit password hashes. It had been in development since the release of pwdump.
May 1997 Microsoft publishes Service Pack 3 for Windows NT which added SYSKEY as an optional feature to prevent pwdump working properly.
December 1999 Todd Sabin documents flaw with SYSKEY. Anyone with access to the SAM database can reveal password hashes without the System key.
April 2000 Todd Sabin releases pwdump2 which dumps password hashes with the obfuscation removed. This also dumps hashes from a domain controller.
February 2004 Nicola Cuomo documents SYSKEY, Releases Samdump2 which enables offline decryption of password hashes stored in SAM database.

Password Generation

When the system boots and auth mode 1 is enabled, windows will display a dialog box waiting for you to enter the password. The following text is displayed on an XP system.

“This computer is configured to require a password in order to start up. Please enter the Startup Password below.”

Blank passwords are acceptable so whether you enter something or not, it gets processed with MD5 and authenticated once you hit OK.

#define MAX_SYSKEY_PWD 260

void pwd2key(wchar_t pwd[], uint8_t syskey[]) {
  MD5_CTX ctx;
  size_t pwd_len = wcslen(pwd);
  pwd_len = (pwd_len > MAX_SYSKEY_PWD) ? MAX_SYSKEY_PWD : pwd_len;
 
  MD5_Init(&ctx);
  MD5_Update(&ctx, pwd, pwd_len);
  MD5_Final(syskey, &ctx);
}

Enter the wrong password 3 times and you’ll receive the following error.

“System error: Lsass.exe”
“When trying to update a password the return status indicates that the value provided as the current password is not correct.”

This message appears because the LSA database key fails to decrypt but I wanted to know how exactly this password was authenticated.

Between XP and Vista, the LSA database got a major upgrade so you may see something else on post-XP systems.

If you were to attempt recovery through the LSA database, it would not only be much slower, it’s more complicated and because there’s a simpler way, I’m not going to cover it.

SAM Database

The SAM database is stored in %SystemRoot%\System32\config\SAM which as you probably know contains local user and group information, including encrypted NTLM/LM hashes.

Windows reads the value of F under SAM\Domains\Account and using the System key, decrypts the Sam key.

The structure of the F value isn’t documented but I’ve put together what I *think* is close enough to the original based on some MSDN documentation and analyzing code in SAMSRV.DLL which is where the decryption occurs.

#define SYSTEM_KEY_LEN   16
 
#define QWERTY "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%"
#define DIGITS "0123456789012345678901234567890123456789"

#define SAM_KEY_LEN      16
#define SAM_SALT_LEN     16
#define SAM_CHECKSUM_LEN 16

typedef struct _SAM_KEY_DATA {
  uint32_t Revision;
  uint32_t Length;
  uint8_t Salt[SAM_SALT_LEN];
  uint8_t Key[SAM_KEY_LEN];
  uint8_t CheckSum[SAM_CHECKSUM_LEN];
  uint32_t Reserved[2];
} SAM_KEY_DATA, *PSAM_KEY_DATA;

typedef enum _DOMAIN_SERVER_ENABLE_STATE {
  DomainServerEnabled = 1,
  DomainServerDisabled
} DOMAIN_SERVER_ENABLE_STATE, *PDOMAIN_SERVER_ENABLE_STATE;

typedef enum _DOMAIN_SERVER_ROLE {
  DomainServerRoleBackup  = 2,
  DomainServerRolePrimary = 3
} DOMAIN_SERVER_ROLE, *PDOMAIN_SERVER_ROLE;

typedef struct _OLD_LARGE_INTEGER {
  unsigned long LowPart;
  long HighPart;
} OLD_LARGE_INTEGER, *POLD_LARGE_INTEGER;

#pragma pack(4)
typedef struct _DOMAIN_ACCOUNT_F {
  uint32_t Revision;
  uint32_t unknown1;
  
  OLD_LARGE_INTEGER CreationTime;
  OLD_LARGE_INTEGER DomainModifiedCount;
  OLD_LARGE_INTEGER MaxPasswordAge;
  OLD_LARGE_INTEGER MinPasswordAge;
  OLD_LARGE_INTEGER ForceLogoff;
  OLD_LARGE_INTEGER LockoutDuration;
  OLD_LARGE_INTEGER LockoutObservationWindow;
  OLD_LARGE_INTEGER ModifiedCountAtLastPromotion;
  
  uint32_t NextRid;
  uint32_t PasswordProperties;
  uint16_t MinPasswordLength;
  uint16_t PasswordHistoryLength;
  uint16_t LockoutThreshold;
  uint16_t unknown2;
  
  DOMAIN_SERVER_ENABLE_STATE ServerState;
  DOMAIN_SERVER_ROLE ServerRole;
  
  uint8_t UasCompatibilityRequired;
  uint32_t unknown3[2]; 
  
  SAM_KEY_DATA keys[2];
  uint32_t unknown4;
} DOMAIN_ACCOUNT_F, *PDOMAIN_ACCOUNT_F;
#pragma pack()

NTSTATUS DecryptSamKey(PSAM_KEY_DATA key_data, uint8_t syskey[]) {
  MD5_CTX ctx;
  RC4_KEY key;
  uint8_t dgst[MD5_DIGEST_LEN];
  
  // create key with salt and decrypt data
  MD5_Init(&ctx);
  MD5_Update(&ctx, key_data->Salt, SAM_SALT_LEN);
  MD5_Update(&ctx, QWERTY, strlen(QWERTY) + 1);
  MD5_Update(&ctx, syskey, SYSTEM_KEY_LEN);
  MD5_Update(&ctx, DIGITS, strlen(DIGITS) + 1);
  MD5_Final(dgst, &ctx);
  
  RC4_set_key(&key, MD5_DIGEST_LEN, dgst);
  RC4(&key, SAM_CHECKSUM_LEN + SAM_KEY_LEN, 
      key_data->Key, key_data->Key);
  
  // verify decryption was successful by generating checksum
  MD5_Init(&ctx);
  MD5_Update(&ctx, key_data->Key, SAM_KEY_LEN);
  MD5_Update(&ctx, DIGITS, strlen(DIGITS) + 1);
  MD5_Update(&ctx, key_data->Key, SAM_KEY_LEN);
  MD5_Update(&ctx, QWERTY, strlen(QWERTY) + 1);
  MD5_Final(dgst, &ctx);
  
  // compare with checksum and return status
  if (memcmp(dgst, key_data->CheckSum, SAM_CHECKSUM_LEN) == 0) {
    return STATUS_SUCCESS;
  }
  return STATUS_WRONG_PASSWORD;
}

NOTE: The strings didn’t format well for the blog but if you plan on using, let me know.

As you can see above, the Sam key is decrypted using System key and then a checksum is generated and compared with that stored in SAM_KEY_DATA
If they match, authentication succeeded, return STATUS_SUCCESS else STATUS_WRONG_PASSWORD

That’s pretty much how you can brute force the System Key when auth mode 1 is selected.

Recovery

Assuming you can read the F value from SAM hive, recovery is straight forward enough with the right libraries/code.

Following is just some pseudo code to demonstrate flow of recovery using dictionary attack.

    sam = openfile("offline_system\Windows\config\SAM");
   data = readreg(sam, "SAM\Domains\Account", "F")
 
  words = openfile("dictionary.txt")
 
  while (readfile(words, pwd)) {
    pwd2key(pwd, syskey)
    if (DecryptSamKey(data->keys[0], syskey) == STATUS_SUCCESS) {
      print "Found password: " + pwd
      break;
    }
  }
  closefile(words)
  closefile(sam)

LSA and NTDS algorithms call a hash function 1000 times during creation
of the encryption/decryption key while SAM algorithm doesn’t use any.

It’s not a vulnerability but could be useful to know some day.

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: Bomgar Remote Desktop Software

Introduction

This will just be a short write up on something I looked at earlier today out of curiosity.
Bomgar is a Remote Desktop application used mainly by corporations.
I’m not entirely sure why it’s preferred over other Remote Desktop solutions; see comparisons here.

One could speculate it’s due to support of multiple operating systems.
In addition to support for Windows and Linux, there’s also iOS, Android, Blackberry, Windows Mobile and Mac OS.
For large corporations and government agencies, Bomgar’s certainly a good choice.

Anyway, I’m not trying to endorse it, just discuss the password algorithm used to protect a technicians credentials.
Just in case there’s any misunderstanding, no corporations were harmed as a result of this research :P

Storage

I’ve installed the trial version on Windows 7 and saved my username and password provided by Bomgar through e-mail.
Depending on where you’ve installed your configuration, this next part may differ for you.
My configuration was stored under:

C:\Users\dietrich\AppData\Local\Bomgar\Representative\<portal domain>\bomgar.ini

I’ve removed the portal domain as it’s not important here.
Inside bomgar.ini there are 2 properties lUsername and lPassword which have 2 strings assigned to them.
The following are just dummy entries to illustrate.

lUsername="@-@-01VGhlcmUgaXMgbm90aGluZyBoZXJl"
lPassword="@-@-01cGFzc3dvcmQgaGVyZQ=="

Note that each entry is padded with random bytes and will be longer than dummy entries above.
The string prefix “@-@-01″ is just an identifier and isn’t important.
You can remove this and pass the string through base64 decoder which leaves you with ciphertext.
At this point, I had to dig into the representative console and anaylse what’s done with the binary once it’s decoded.

Recovery

The encryption/decryption process uses RC4 and a static key which is a little peculiar.

/* just a mishmash of strings */

uint8_t static_key[] = 
{ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
  0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
  0x01, 0x02, 0x03, 0x30, 0x31, 0x32, 0x33, 0x34,
  0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63,
  0x64, 0x65, 0x66, 0x04, 0x77, 0x30, 0x32, 0x6d,
  0x58, 0x30, 0x40, 0x2d, 0x78, 0x31, 0x01, 0x14,
  0x91, 0x0a, 0xd1, 0xb1, 0x52, 0x66, 0x32, 0x39,
  0x31, 0x32, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a,
  0x67, 0x65, 0x74, 0x6d, 0x65, 0x6d, 0x28, 0x69,
  0x6e, 0x74, 0x2c, 0x69, 0x6e, 0x74, 0x2c, 0x69,
  0x6e, 0x74, 0x29 };

The decryption/encryption is the same, using RC4

void bomgar_crypt(uint8_t ciphertext[], size_t len) {
  RC4_KEY key;
  uint8_t plaintext[32];
  
  memset(plaintext, 0, sizeof(plaintext));

  RC4_set_key(&key, sizeof(static_key), static_key);
  RC4(&key, len > 32 ? 32 : len, ciphertext, plaintext);

  for (int i = 0;i < 32;i++) {
    printf("%02x ", plaintext[i]);
  }
}

How to determine the exact length of username/password I leave up to you.
Just a note to the authors, I could recommend using CryptProtectData() on Windows and the Gnome-Keyring for Linux as they both provide more security than the existing algorithm.
The recovery demonstrated here isn’t very significant since the attacker needs Administrator privileges.

Enjoy Labor day! ;)

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: bcrypt File Encryption

Introduction

lxd requested analysis of BFE files generated by the bcrypt file encryption utility which according to the home page, hasn’t been updated in 10 years. Judging by the mailing list, lots of people still use it.

I’ve come across this program in the past while searching for information about bcrypt password algorithm built into OpenBSD. Both are completely unrelated codes, just in case there’s any confusion.

bcrypt will by default compress a file using ZLib and encrypt with Blowfish.

Usage is straight forward enough; supply a list of files to encrypt and specify if you want them overwritten and removed once complete.

# bcrypt
Usage is: bcrypt -[orc][-sN] file1 file2..
  -o Write output to standard out
  -r Do NOT remove input files after processing
  -c Do NOT compress files before encryption
  -sN How many times to overwrite input files with random data

Initial observations

I created a text file with “hello” in it and encrypted with “password” as the key just to see what output was generated.

# echo hello > test.txt
# ./bcrypt -r test.txt
Encryption key:
Again:

# hexdump -C test.txt.bfe
00000000  54 01 7a 73 0e 2e 64 0a  fa 15 34 61 49 b3 be 33  |T.zs..d...4aI..3|
00000010  65 6f 17 d7 9b 60 91 77  10 27 49 e1 42 fc 8a b1  |eo...`.w.'I.B...|
00000020  43 fa 6a fa 13 ea 0e 5d  39 8a d7 7d 95 9a d1 e4  |C.j....]9..}....|
00000030  06 c3 be e8 9e 0c ec 4b  7d 67 ec 6a fc db 2f 95  |.......K}g.j../.|
00000040  81 58 b2 a5 f4 c7 8c 94  84 6e 06 00 00 00        |.X.......n....|

Hmmm…78 bytes for 6 bytes of plaintext, that’s odd!
The first 2 bytes are “tags”; options which indicate the endianess and whether compression is used.

#define BIG_ENDIAN    0x45
#define LITTLE_ENDIAN 0x54

The second byte can be either TRUE (Compressed) or FALSE (not compressed)
The remaining data is ciphertext although it looks slightly bloated for small string like “hello” so I’ve passed the same file again this time without compression.

00000000  54 00 f3 d9 e9 41 1c b4  0e 96 17 d7 9b 60 91 77  |T....A.......`.w|
00000010  10 27 49 e1 42 fc 8a b1  43 fa 6a fa 13 ea 0e 5d  |.'I.B...C.j....]|
00000020  39 8a d7 7d 95 9a d1 e4  06 c3 be e8 9e 0c ec 4b  |9..}...........K|
00000030  7d 67 ec 6a fc db 2f 95  81 58 b2 a5 f4 c7 8c 94  |}g.j../..X......|
00000040  84 6e                                             |.n|

It’s slightly smaller than before at 66 bytes but what’s really weird is the excess bytes are almost the exact same as when compression is used.

The utility calls compress() function in Zlib directly so I pass the file through this separately and dump the results.

# ./comp_test test.txt

Read size = 6
Compressed size = 14

# hexdump comp.bin -C
00000000  78 9c cb 48 cd c9 c9 e7  02 00 08 4b 02 1f        |x..H.......K..|

Only 14 bytes.
I then try an empty file without compression.

# touch test_file.txt
# ./bcrypt -r -c test_file.txt
Encryption key:
Again:

# hexdump -C test_file.txt.bfe
00000000  54 00 69 58 11 36 9e a7  69 1c c8 b8 62 6c 3d f0  |T.iX.6..i...bl=.|
00000010  6e 87 3c 3b c4 59 7e 77  12 88 c8 40 6b 31 98 36  |n.&lt;;.Y~w...@k1.6|
00000020  41 61 85 c4 1f e2 9a 06  36 09 65 c9 63 c8 7a 37  |Aa......6.e.c.z7|
00000030  11 db 92 b5 6d 44 87 be  25 53                    |....mD..%S|

58 bytes for an empty file! :|
After looking in the source code files again, a 56-byte key is attached to plaintext/compressed data before being encrypted.

Generation (summary)

  1. mutateKey() in keys.c takes a password of 8 or more characters and produces 2 56-byte keys, 1 in little endian format, the other in big format.
  2. If compression is used, readfile() in rwfile.c will take the entire contents of input file and pass to compress() function (part of Zlib)
  3. A 56-byte key generated in step 1 is appended to compressed data using attachKey() Null bytes are used as padding if required.
  4. BFEncrypt() in wrapbf.c invokes blowfish functions to encrypt data + key.
  5. If compression is used, the original file size is appended and data is saved to file with BFE extension.

The first problem I see with this app is the storage of encrypted key, it is even stored when there is no data in the original file…

The author does this to verify successful decryption but what it also does is provide a reliable way to recover original password.

Integrity of data is important of course but a hashing algorithm like SHA-256 would be much more suitable for verification process.

There’s also the Zlib signature [ 0x78, 0x9c ] as a chosen plaintext attack but only if the file’s compressed.

Here is snippet of the code from BFDecrypt() that verifies successful decryption.

  sz -= MAXKEYBYTES;

  if (memcmp(*input+sz, mykey, MAXKEYBYTES) != 0)
    return(0);

In attachKey() part of rwfile.c copies the encryption key to the end of the file data.

  memcpy(*input+sz, key, MAXKEYBYTES);
  sz += MAXKEYBYTES;

Recovery

Using the authors own code and some hardcoded values, it’s possible to create a verification routine for demonstration purposes.

Please note this was tested only on x86 hardware. — feel free to donate one of these :)

    char password[] = "password";
    unsigned char ciphertext[] = { 0x69, 0x58, 0x11, 0x36,
                                   0x9e, 0xa7, 0x69, 0x1c };

    unsigned char newkey[MAXKEYBYTES];

    uInt32 L, R;
    BLOWFISH_CTX ctx;
    int i, j;

    j = sizeof(uInt32);

    // create initial key
    Blowfish_Init(&ctx, password, strlen(password));

    memcpy(&L, &password[0], 4);
    memcpy(&R, &password[4], 4);

    // create new 448-bit key
    for (i = 0; i < MAXKEYBYTES; i += sizeof(uInt32) * 2) {
      Blowfish_Encrypt(&ctx, &L, &R);

      memcpy(newkey + i,     &L, j);
      memcpy(newkey + i + j, &R, j);
    }

    // create the decryption/encryption key for file data
    Blowfish_Init(&ctx, newkey, MAXKEYBYTES);

    memcpy(&L, &newkey[0], 4);
    memcpy(&R, &newkey[4], 4);

    // encrypt 64-bits of newkey
    Blowfish_Encrypt(&ctx, &L, &R);

    // compare encrypted result with 64-bits of encrypted key
    if (!memcmp(&L, ciphertext, 4)) {
      printf("\nKey is correct\n");
    } else {
      printf("\nKey is incorrect\n");
    }

The ciphertext used here is from test_file.txt.bfe and works fine, displaying “Key is correct” :)

I wanted to test the same routine using OpenSSL in hopes of achieving performance boost, here’s the structure/defined tags.

typedef struct _BFE_HDR {
  u_int8_t arch;
  u_int8_t compressed;
} BFE_HDR, *PBFE_HDR;

#define BIG_ENDIAN    0x45
#define LITTLE_ENDIAN 0x54

#define MIN_PASS_LEN 8
#define MAX_PASS_LEN 56

A function to read the encrypted key from BFE file.

bool read_bfe_key(char bfe_file[], u_int8_t bfe_key[]) {
  bool valid = false;
  struct stat fs;

  if (stat(bfe_file, &fs) == 0) {
    // should be at least equal to or more than 58 bytes
    if (fs.st_size >= MAX_PASS_LEN + sizeof(BFE_HDR)) {
      // if it's exactly 58 bytes, there's no encrypted data
      // we continue with crack anyway since the encrypted
      // key is what we're attacking.
      if (fs.st_size == MAX_PASS_LEN + sizeof(BFE_HDR)) {
        printf("\n  WARNING: Size of file indicates no encrypted data.");
      }

      // open and read header
      FILE *fd = fopen(bfe_file, "rb");

      if (fd != NULL) {
        BFE_HDR hdr;
        size_t readSize = fread(&hdr, 1, sizeof(BFE_HDR), fd);

        if (readSize == sizeof(BFE_HDR)) {
          // big endian format unsupported
          // hdr.arch == BIG_ENDIAN ||
          if ((hdr.arch == LITTLE_ENDIAN) &&
              (hdr.compressed == 1 || hdr.compressed == 0)) {

            // allocate memory for read
            u_int8_t *bfe_data = (u_int8_t*)malloc(fs.st_size);

            if (bfe_data != NULL) {
              readSize = fread(bfe_data, 1, fs.st_size, fd);

              // if file was compressed before encryption
              // skip the original size value
              if (hdr.compressed == 1) {
                readSize -= sizeof(u_int32_t);
                u_int32_t origSize;

                memcpy(&origSize, &bfe_data[readSize], sizeof(u_int32_t));

                printf("\n  File is compressed."
                       "\n  Original size : %i bytes", origSize);
              } else {
                printf("\n  No compression used."
                       "\n  Original size : unknown");
              }

              // read encrypted key
              for (int i = MAX_PASS_LEN - 1;i >= 0;i--) {
                bfe_key[i] = bfe_data[--readSize];
              }

              valid = true;
              free(bfe_data);
            }
          } else {
            printf("\n  WARNING: Unrecognised BFE header"
                   " - Endian flag: %#02x", hdr.arch);
          }
        }
        fclose(fd);
      }
    }
  }
  return valid;
}

The OpenSSL based function to test if password is valid key.

bool is_valid_key(char password[], u_int8_t bfe_key[]) {
  char *p;
  size_t pass_len;
  u_int8_t newkey[MAX_PASS_LEN + 8] = {0};

  BF_LONG buf1[2], buf2[2];
  BF_KEY key;

  if ((p = strchr(password, 'n')) != 0) *p = 0;
  if ((p = strchr(password, 'r')) != 0) *p = 0;

  pass_len = strlen(password);

  if (pass_len >= MIN_PASS_LEN) {
    memcpy(&buf1[0], &password[0], sizeof(BF_LONG) * 2);
    memcpy(&buf2[0], &bfe_key[48], sizeof(BF_LONG) * 2);

    // 1. create blowfish key from password string
    BF_set_key(&key, pass_len, (u_int8_t*)password);

    // 2. create 56-byte key encrypting 64-bits of password string
    for (int i = 0; i < MAX_PASS_LEN; i += sizeof(BF_LONG) * 2) {
      BF_encrypt(buf1, &key);
      memcpy(&newkey[i], &buf1[0], sizeof(BF_LONG) * 2);
    }

    // 3. create blowfish key from 56-byte key
    BF_set_key(&key, MAX_PASS_LEN, newkey);

    for (int i = 0;i < 8;i++) {
      memcpy(&buf1, &newkey[48 + i], sizeof(BF_LONG) * 2);
      BF_encrypt(buf1, &key);

      // if buffers match, we've found key
      if (buf1[0] == buf2[0] && buf1[1] == buf2[1]) {
        return true;
      }
    }
  }
  return false;
}

The program basically reads encrypted key from *.BFE file, then sequentially reads from a list of words, each one processed with is_valid_key(). Since the keys are 8 characters or more, I didn’t think it was necessary to use some brute force algorithm.

Using example file provided on authors site.

# crack_bfe littleendian.bfe words.txt
  ...
  File is compressed.
  Original size : 1725 bytes

  found key: "eggheads"

  3 seconds elapsed - k/s: 19925

Even on an empty file described earlier, it finds the key..

# crack_bfe empty.txt.bfe words.txt
  ...
  WARNING: Size of file indicates no encrypted data.
  No compression used.
  Original size : unknown

  found key: "password"

The speed is a little disappointing but it does use BF_set_key() twice which internally uses BF_encrypt() a lot and so the key setup accounts for most of time testing key.

Due to the design of Blowfish, it’s unlikely you would achieve a huge performance boost on latest GPU.

For the average joe, a decent password generator should be enough to recover most passwords.

Conclusion

bcrypt is no longer maintained, has some bugs / design flaws and there are much better alternatives available.

Okay, the BFE files are relatively safe providing the initial password is strong but perhaps there are bigger weaknesses in blowfish not publicly known. (not always governments that know of secret weaknesses)

I’d recommend a more robust application like FileEncrypt or indeed 7Zip as alternatives. Both offer more security and functionality.

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. :)