The Secrets of Google Chrome Credentials
Chrome stores all passwords and other credentials in an encrypted database but guess what: they can be retrieved by anyone with the proper knowledge. This article will show you how.
The Profile Folder
Chrome stores the user’s credentials inside a special folder called the “profile folder”. The first step would be understanding where to find that folder. To understand better, note that Chrome has a built in mechanism for displaying important information about its version and installation location. To view this information just type: "chrome://version/" in the Address Bar.
Among the many pieces of information displayed, please refer to "Profile Path", which is where the sensitive data is kept.
To get this path programmatically we need to do as follow:
#define FORENSICS_CHROMECREDENTIALS_PATH _T("\\Google\\Chrome\\User Data\\Default\\")
bool result = false;
result = SHGetSpecialFolderPath(0, szProfileFolderPath, CSIDL_LOCAL_APPDATA, 0);
First we obtained the user specific path of his or her Windows' user account. That can be c:\users\john\ or c:\users\myself\, and since we don't know it, we don't use any hardcoded value but obtain it during runtime.
The second part is the relative path of the Google data, which is constant so we do keep that part hardcoded. When we combine the two, we get the path.
Now that we have the path, we need to focus in one specific file which is in fact a SQLite3 database: Login Data.
To get that file we add "\" (_T("\\") to the path and then the string _T("Login Data") and when we do, szProfileFolderPath will hold the full path of the database we need to open.
#define FORENSICS_CHROMECREDENTIALS_DB _T("\\Login Data")
Before we open this database it is advised to copy it to another file so it won't intervene with any instance of Google Chrome currently opened, and by doing so, there is no need to shut down Chrome before we operate.
#define TEMP_CHROME_DB _T("temp.db")
CopyFile(szProfileFolderPath, TEMP_CHROME_DB, FALSE);
Some notes about SQLite3
During the day to day development work at Secured Globe, Inc. we use SQLite3 a lot. Most of forensics information out there is associated one way or another with SQLite3. Sqlite3 can be used at the source code level (adding sqlite3.c and sqlite3.h to your program, or as a static / dynamic library).
We use it with two additional enhancements:
- CppSqlite3 - a wrapper which can ensure better handling with UNICODE test (without it, sqlite3 can hardly deal with international characters within its databases). The first versions were published here at Code Project.
- SEE - a paid library ($2,000 per license) sold by Sqlite3 which allows an application to read and write encrypted database files. Four different encryption algorithms are supported:
AES-128 in OFB mode
AES-128 in CCM mode
AES-256 in OFB mode
The following article is very handy when it comes to helping people using Sqlite3 in their programs, and especially Windows programs.
Fetching the credentials from the SQLite3 Database
Next we open the database and run a query on the specific table we need for the stored credentials. This table is called "logins". We predefine the query to be executed on this table as follow:
#define CHROME_CRED_SQL_QUERY "SELECT signon_realm,username_value,password_value,date_created FROM logins"
Then we can open the database and run this query:
CppSQLite3DB CredentialsDB; // we define a CppSQLite3DB object
catch (CppSQLite3Exception &e)
// Handle exceptions here
sql = CHROME_CRED_SQL_QUERY;
SqlQuery = CredentialsDB.execQuery(sql);
catch (CppSQLite3Exception &e)
// Handle query execution's exceptions here
int count = 0;
int size = 0;
The SGBrowserCredentials Data Structure
The SGBrowserCredentials (Secured Globe Browser Credentials) is used for any procecss of fetching browser credentials and is capable of holding the relevant fields which are common to all browsers.
We define the single element and a CSimpleArray for the purpose of collecting the results of our program. We also use CTime for the date/time stamp of each entry. We do so to be able to compare credentials from different sources (browsers), as each browser use a different method for storing date and time. The importance of date/time for the scope of our program is to be able to use it later for filtering the results. For example: being able to tell which new credentials were created since 1.1.2017 or since last time we checked.
typedef struct _SGBrowserCredentials
int Browser; // 0 = chrome, 1 = ie, 2 = firefox,
Site = 0;
UserName = 0;
Password = 0;
DateCreated = NULL;
typedef CSimpleArray<SGBrowserCredentials> SGBrowserCredentialsArray;
Reading the data
After running the SQL query, we should expect to get a number of records. Each record is a single entry such as a stored credentials. We use the following local variables:
WCHAR *Site, *User, *CreationDate;
DATA_BLOB DataIn, DataOut;
to store a single record. We read also encrypted data which we will decrypted later.
#define CHROME_DB_FIELD_SITE 0
#define CHROME_DB_FIELD_USER 1
#define CHROME_DB_ENC_FIELD_PASS 2
#define CHROME_DB_FIELD_DATE 3
Then the loop we run over the results looks like this:
Site = (WCHAR *)SqlQuery.fieldValue(CHROME_DB_FIELD_SITE);
User = (WCHAR *)SqlQuery.fieldValue(CHROME_DB_FIELD_USER);
DataIn.pbData = (LPBYTE)SqlQuery.getBlobField(CHROME_DB_ENC_FIELD_PASS, size);
DataIn.cbData = size;
CreationDate = (WCHAR *)SqlQuery.fieldValue(CHROME_DB_FIELD_DATE);
Now we need to decrypt the encrypted part and that can be done smoothly as long as you run the program from where the database was originally stored. The reason for that is that the encryption is based on the Windows logon system. If you copy the database and try to open it from another machine, you won't be able to decrypt the password but just to see the other fields.
Decrypting encrypted data
To decrypt the data use CryptUnprotectData which decrypts password (in a DATA_BLOB structure).
bool bDecrypted = CryptUnprotectData(&DataIn, 0, 0, 0, 0, 8, &DataOut);
If it has succeed, we will have the decrypted password in DataOut.
Handling Chrome's Date/Time format
Before we create the single record and add it to our dynamic array we need to interpret the way date and time of each entry are kept by Chrome. It would be easiest to convert the date/time (fetched as string from the database) into a FILETIME object. The FILETIME structure contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC). First, we need to grab the first 10 digits of the long number used by Chrome and store it in a ULONGLONG variable (using _wcstoui64). Then we multiple it by 10 and assign the "LowPart" and "HighPart" to the FILETIME object.
ULONGLONG lltm = _wcstoui64(ChromeTime.GetString(), NULL, 10);
uLarge.QuadPart = lltm * 10;
ftTime.dwHighDateTime = uLarge.HighPart;
ftTime.dwLowDateTime = uLarge.LowPart;
From FILETIME we can always convert to SYSTEMTIME and to CTime easily.
When you start the program, you will see all stored credentials on your machine. It should look like this: