Firefox is the most secured web browser. Finding a way to fetch its stored credentials wasn't easy, especially when it comes to the more recent versions. However, there is a way and it is shown in this article.
Introduction
This article is the fourth of several articles covering the secrets of obtaining stored (and encrypted) credentials stored by browses (and other applications, for example: MS Outlook). The first article covered Wi-Fi credentials. The second one covers Chrome's credentials. The third article covers Internet Explorer's credentials. This article is the most challenging one. It covers Firefox and explains how credentials are stored by it and can be fetched from it.
What Makes Firefox Secure
Firefox has become much more secure in recent versions compared to previous versions but also compared to other browsers. In the past, it was enough to fetch the signons.txt file located in the Firefox profile directory and find all stored credentials there. From version 3.5, the textual format has been replaced with a combination of SQLite database and JSON file (namely ‘logins.json’). In addition, if a “master key” is set, there is no way to decrypt the stored credentials without knowing this key first. When a master key isn't set, you can find the data in logins.json and decrypt the credentials. Both user and password for each credential are encrypted using PK#11.
Locating Firefox Profile Path
First, we need to find the location of the data we are going to fetch, which is the Firefox Profile path. That would normally be in c:\users\<your user account>\appdata\local\Mozilla\Firefox\\profiles.ini.
std::wstring GetFirefoxProfilePath()
{
wchar_t strAppData[MAX_PATH];
SHGetSpecialFolderPath(NULL, strAppData, CSIDL_APPDATA, TRUE);
wchar_t strIniPath[MAX_PATH];
swprintf_s(strIniPath, MAX_PATH, L"%s\\Mozilla\\Firefox\\profiles.ini", strAppData);
wchar_t strRelativePath[MAX_PATH];
GetPrivateProfileString(L"Profile0", L"Path",
L"", strRelativePath, MAX_PATH, strIniPath);
wchar_t strFullPath[MAX_PATH];
swprintf_s(strFullPath, MAX_PATH,
L"%s\\Mozilla\\Firefox\\%s", strAppData, strRelativePath);
wchar_t strShortPath[MAX_PATH];
GetShortPathName(strFullPath, strShortPath, MAX_PATH);
if (!PathFileExists(strShortPath))
{
return L"";
}
return ((std::wstring)strShortPath);
}
Mapping the nss3 DLL
The nss3.dll is used by a set of libraries called Network Security Services (NSS). These libraries were designed to support cross-platform development of communications applications that support SSL, S/MIME, and other Internet security standards. For a general overview of NSS and the standards it supports, see this page.
Since this article is about implementation for Windows, I will only mention the Windows used libraries:
- nss3.dll - Windows shared library
- nss3.lib - Windows import library binding to nss3.dll
- nss.lib - Windows static library
So first, we need to map the functions in nss3.dll which we intend to use.
fpNSS_Init = (NSS_Init_p)GetProcAddress(moduleNSS, "NSS_Init");
fpNSS_Shutdown = (NSS_Shutdown_p)GetProcAddress(moduleNSS, "NSS_Shutdown");
fpPL_ArenaFinish = (PL_ArenaFinish_p)GetProcAddress(moduleNSS, "PL_ArenaFinish");
fpPR_Cleanup = (PR_Cleanup_p)GetProcAddress(moduleNSS, "PR_Cleanup");
fpPK11_GetInternalKeySlot =
(PK11_GetInternalKeySlot_p)GetProcAddress(moduleNSS, "PK11_GetInternalKeySlot");
fpPK11_FreeSlot = (PK11_FreeSlot_p)GetProcAddress(moduleNSS, "PK11_FreeSlot");
fpPK11SDR_Decrypt = (PK11SDR_Decrypt_p)GetProcAddress(moduleNSS, "PK11SDR_Decrypt");
PK11_CheckUserPassword =
(PK11CheckUserPassword)GetProcAddress(moduleNSS, "PK11_CheckUserPassword");
The Master Password
Firefox brings a new security measure named the Master Password. The Master Password is a centralized encryption key used to encrypt all stored credentials. This key is hashed but not stored anywhere so it's impossible to find it.
Decrypting Credentials Programmatically When the Master Password Is Known
I have made some tests trying to see what needed to be done to decrypt credentials stored by Firefox when a Master Password is set. First, it should be explained how to decrypt credentials programmatically when the Master Password is known.
Guessing the Master Password
Alternatively, a hacker might use brute-force techniques trying to guess the Master Password. That can be done by either running over all possible combination of strings (or alpha numeric strings) having the length of 1-7 characters (a longer password will be almost impossible to break using this method). Another method would be running over a list of commonly used password. This method is called a "dictionary".
The Code Needed to Examine a Given String
The following code checks if "MyGuess
" is indeed the Master Password. If it is, running the rest of the code of our tool will decrypt all stored passwords.
bool GuessMasterPassword(char *MyGuess)
{
bool result = FALSE;
PK11SlotInfo *pK11Slot = fpPK11_GetInternalKeySlot();
if (PK11_CheckUserPassword(pK11Slot, MyGuess) == SECSuccess)
{
result = true;
}
(*PK11FreeSlot) (pK11Slot);
return result;
}
If the return result of GuessMasterPassword
is true
, then we can proceed.
Decrypting the Credentials
The next part of our code will decrypt any encrypted part of a credentials entry and should be used regardless of the Master Password. If there is no Master Password set, this code still needs to be executed, and if there is a Master Password set, we assume that GuessMasterPassword
has returned 'true
' for any reason (you know it, you guessed it or you brute forced it).
LPSTR DecryptString(LPSTR strCryptData)
{
if (strCryptData[0] == 0x0)
return FALSE;
DWORD dwOut;
LPSTR strClearData = "";
LPBYTE lpBuffer = base64_decode((char*)strCryptData, strlen(strCryptData), (int *)&dwOut);
PK11SlotInfo *pK11Slot = fpPK11_GetInternalKeySlot();
if (pK11Slot)
{
SECItem pInSecItem, pOutSecItem;;
pInSecItem.data = lpBuffer;
pInSecItem.len = dwOut;
pOutSecItem.data = 0;
pOutSecItem.len = 0;
if (fpPK11SDR_Decrypt(&pInSecItem, &pOutSecItem, NULL) == 0)
{
strClearData = (LPSTR)malloc(pOutSecItem.len + 1);
memcpy(strClearData, pOutSecItem.data, pOutSecItem.len);
*(strClearData+pOutSecItem.len) = '\0';
}
fpPK11_FreeSlot(pK11Slot);
}
return strClearData;
}
Handling Firefox Date/Time
To be able to process records from various browsers, each of them uses its own method for storing dates and times. We need to convert each date/time record into a single format which we can later use for manipulations such as finding all credentials from a given date, or since the last time we checked. When it comes to Firefox, it stores its dates in large integer. This number is called Epoch Time. You can enter an Epoch Time to this web site and see the date it represents and vice versa.
The code used for our tool intends to generate a CTime, so our Windows MFC based tool can process the various dates after converting them to this Windows date/time format. Doing that is possible by converting any given date/time (or long integer) into two intermediate formats: FILETIME
and SYSTEMTIME
. The input of our function will be std::string since it is easier to read an XML or JSON items to a string
first.
SYSTEMTIME FirefoxTimeToSysTime(string FirefoxTime)
{
__int64 unixtime = atol(FirefoxTime.c_str());
__int64 longLongVar = unixtime + EPOCH_DIFFERENCE;
longLongVar = longLongVar*TICKS_PER_SECOND;
FILETIME ftTime;
ftTime.dwLowDateTime = (DWORD)longLongVar;
ftTime.dwHighDateTime = longLongVar >> 32;
SYSTEMTIME stTime;
FileTimeToSystemTime(&ftTime, &stTime);
return stTime;
}
The Parson Library
Parson in a JSON parser which was created by Krzysztof Gabis. I found it suitable for our project as it is light and easy to integrate.
Converting Firefox Date/Time from JSON to CTime
First, we read the date/time from a JSON element, but only use the first 10 (of 13) characters.
double tempnum = json_object_get_number(commit, strDateTime);
string datetime;
datetime = std::to_string(tempnum);
datetime = datetime.substr(0, 10);
Notes
- The
Parson
library provides a function for reading a number as a "double
", which is why we first read the date/time into a double variable. - Next, we convert the
double
into std::string. - We then cut the last 3 digits leaving the length of the
string
10 characters. - We call
FirefoxTimeToSysTime()
sending our string
to it.
Converting the SYSTEMTIME into CTime
is straight forward:
SYSTEMTIME newtime = utils::FirefoxTimeToSysTime(datetime);
CTime ct(newtime);
We can later display the data in a report or on screen converting the CTime
variable into a user friendly date format. In Secured Globe, Inc. we use the following:
#define SG_FRIEDLY_DATEFORMAT L"%d-%m-%Y, %H:%M:%S"
So when we need to use the stored credentials and display one of its elements, we would use:
credentials[i].DateCreated.FormatGmt(SG_FRIEDLY_DATEFORMAT).GetBuffer();
FormatGmt
is important if you wish to process data from users in different countries. For local use, the code would be:
credentials[i].DateCreated.Format(SG_FRIEDLY_DATEFORMAT).GetBuffer();
The Firefox Credentials Viewer tool
When you start Firefox Credentials Viewer, a split of a second later, you will see a screen similar to this one. All your stored credentials will be displayed.
You can download this tool from Source Forge.
History
- 30th January, 2017: Initial version