Friday, June 14, 2013

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 toCryptUnprotectData()
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

0 comments:

Post a Comment