Friday, June 14, 2013

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

0 comments:

Post a Comment