Try to crack the password stored in Chrome Try to output the login data saved in IE / Edge Continuing from, this time I tried to find out how Firefox stores login data for websites.
After some research, I found that Firefox uses a library called NSS (Network Security Services) to encrypt usernames and passwords, Base64-encode them and store them in the profile folder logins.json.
The profile is User folder \ AppData \ Roaming \ Mozilla \ Firefox \ Profiles It is managed for each folder in. Also, the NSS library is stored in the folder where Firefox is installed \ nss3.dll.
Let's implement a program that reads login data. I used to use C # before, but this time it didn't work so I'll use Python.
First, load the NSS library required for decryption. The PK11SlotInfo structure is Opaque and therefore has no members defined.
#DLL loading
dllpath = os.path.join(os.environ["programfiles"], "Mozilla Firefox\\nss3.dll")
nss3 = ct.CDLL(dllpath)
#Load function
def getfunc(restype, name, *argtypes):
res = getattr(nss3, name)
res.restype = restype
res.argtypes = argtypes
return res
class SECItem(ct.Structure):
_fields_ = [
('type', ct.c_uint),
('data', ct.c_char_p),
('len', ct.c_uint),
]
class PK11SlotInfo(ct.Structure):
#Opaque structure
pass
SlotInfoPtr = ct.POINTER(PK11SlotInfo)
SECItemPtr = ct.POINTER(SECItem)
NSS_Init = getfunc(ct.c_int, "NSS_Init", ct.c_char_p)
NSS_Shutdown = getfunc(ct.c_int, "NSS_Shutdown")
PK11_GetInternalKeySlot = getfunc(SlotInfoPtr, "PK11_GetInternalKeySlot")
PK11_FreeSlot = getfunc(None, "PK11_FreeSlot", SlotInfoPtr)
PK11_CheckUserPassword = getfunc(ct.c_int, "PK11_CheckUserPassword", SlotInfoPtr, ct.c_char_p)
PK11SDR_Decrypt = getfunc(ct.c_int, "PK11SDR_Decrypt", SECItemPtr, SECItemPtr, ct.c_void_p)
SECITEM_ZfreeItem = getfunc(None, "SECITEM_ZfreeItem", SECItemPtr, ct.c_int)
Then enumerate the profiles stored on your computer and extract only the profiles that have login data stored (with logins.json).
#Enumerate profiles
def getprofiles():
profdir = os.path.join(os.environ["appdata"], "Mozilla\\Firefox\\Profiles")
files = os.listdir(profdir)
profiles = [os.path.join(profdir, f) for f in files if os.path.isfile(os.path.join(profdir, f, "logins.json"))]
return profiles
Select one profile and initialize NSS with that profile.
profiles = getprofiles()
print("Please enter the profile number.")
for i in range(len(profiles)):
print("%d: %s" % (i, profiles[i]))
number = int(input("number: "))
encprof = profiles[number].encode("utf8")
#NSS initialization
e = NSS_Init(b"sql:" + encprof)
if e != 0:
raise Exception("Failed to initialize NSS.")
Firefox allows you to set a master password on your profile to protect your personal information. If a master password is set, you need to authenticate with the PK11_CheckUserPassword function.
keyslot = PK11_GetInternalKeySlot()
if not keyslot:
raise Exception("Could not get Keyslot.")
askpass = input("Please enter the master password: ")
if askpass:
e = PK11_CheckUserPassword(keyslot, askpass.encode("utf8"))
if e != 0:
raise Exception("The master password is incorrect.")
else:
print("No password")
PK11_FreeSlot(keyslot)
Then load logins.json. Although omitted considerably, the structure of logins.json is as follows.
{
"logins": [
{
"hostname": "https://id.unity.com",
"encryptedUsername": "Encrypted username 1",
"encryptedPassword": "Encrypted password 1",
"encType": 1
},
{
"hostname": "https://accounts.google.com",
"encryptedUsername": "Encrypted username 2",
"encryptedPassword": "Encrypted password 2",
"encType": 1
},
]
}
Load logins.json and
def getcreds(profile):
db = os.path.join(profile, "logins.json")
with open(db) as fh:
data = json.load(fh)
try:
logins = data["logins"]
except Exception:
raise Exception("{0}Cannot be read.".format(db))
for i in logins:
yield (i["hostname"], i["encryptedUsername"],
i["encryptedPassword"], i["encType"])
Finally, decrypt and display it, and close NSS.
for url, user, passw, enctype in getcreds(profiles[number]):
if enctype:
user = decode(user)
passw = decode(passw)
print("Url: " + url)
print("Username: " + user)
print("Password: " + passw)
NSS_Shutdown()
The contents of the essential decoding function decode are as follows.
def decode(data64):
data = b64decode(data64)
inp = SECItem(0, data, len(data))
out = SECItem(0, None, 0)
e = PK11SDR_Decrypt(inp, out, None)
try:
if e == -1:
print("Decryption failed.")
exit()
res = ct.string_at(out.data, out.len).decode("utf8")
finally:
#Release SECItem
SECITEM_ZfreeItem(out, 0)
return res
If you put Base64-decoded data in the SECItem structure and pass it to the PK11SDR_Decrypt function, the decrypted data will be returned.
The program that outputs Firefox login data is as follows.
import ctypes as ct
import os
import json
from base64 import b64decode
#DLL loading
dllpath = os.path.join(os.environ["programfiles"], "Mozilla Firefox\\nss3.dll")
nss3 = ct.CDLL(dllpath)
#Function load
def getfunc(restype, name, *argtypes):
res = getattr(nss3, name)
res.restype = restype
res.argtypes = argtypes
return res
class SECItem(ct.Structure):
_fields_ = [
('type', ct.c_uint),
('data', ct.c_char_p),
('len', ct.c_uint),
]
class PK11SlotInfo(ct.Structure):
#Opaque structure
pass
SlotInfoPtr = ct.POINTER(PK11SlotInfo)
SECItemPtr = ct.POINTER(SECItem)
NSS_Init = getfunc(ct.c_int, "NSS_Init", ct.c_char_p)
NSS_Shutdown = getfunc(ct.c_int, "NSS_Shutdown")
PK11_GetInternalKeySlot = getfunc(SlotInfoPtr, "PK11_GetInternalKeySlot")
PK11_FreeSlot = getfunc(None, "PK11_FreeSlot", SlotInfoPtr)
PK11_CheckUserPassword = getfunc(ct.c_int, "PK11_CheckUserPassword", SlotInfoPtr, ct.c_char_p)
PK11SDR_Decrypt = getfunc(ct.c_int, "PK11SDR_Decrypt", SECItemPtr, SECItemPtr, ct.c_void_p)
SECITEM_ZfreeItem = getfunc(None, "SECITEM_ZfreeItem", SECItemPtr, ct.c_int)
#Decryption process
def decode(data64):
data = b64decode(data64)
inp = SECItem(0, data, len(data))
out = SECItem(0, None, 0)
e = PK11SDR_Decrypt(inp, out, None)
try:
if e == -1:
print("Decryption failed.")
exit()
res = ct.string_at(out.data, out.len).decode("utf8")
finally:
#Release SECItem
SECITEM_ZfreeItem(out, 0)
return res
#Enumerate profiles
def getprofiles():
profdir = os.path.join(os.environ["appdata"], "Mozilla\\Firefox\\Profiles")
files = os.listdir(profdir)
profiles = [os.path.join(profdir, f) for f in files if os.path.isfile(os.path.join(profdir, f, "logins.json"))]
return profiles
#Read Json
def getcreds(profile):
db = os.path.join(profile, "logins.json")
with open(db) as fh:
data = json.load(fh)
try:
logins = data["logins"]
except Exception:
raise Exception("{0}Cannot be read.".format(db))
for i in logins:
yield (i["hostname"], i["encryptedUsername"],
i["encryptedPassword"], i["encType"])
def main():
profiles = getprofiles()
print("Please enter the profile number.")
for i in range(len(profiles)):
print("%d: %s" % (i, profiles[i]))
number = int(input("number: "))
#NSS initialization
encprof = profiles[number].encode("utf8")
e = NSS_Init(b"sql:" + encprof)
if e != 0:
raise Exception("Failed to initialize NSS.")
#Password authentication
keyslot = PK11_GetInternalKeySlot()
if not keyslot:
raise Exception("Could not get Keyslot.")
askpass = input("Please enter the master password: ")
if askpass:
e = PK11_CheckUserPassword(keyslot, askpass.encode("utf8"))
if e != 0:
raise Exception("The master password is incorrect.")
else:
print("No password")
PK11_FreeSlot(keyslot)
#Decrypt and output
for url, user, passw, enctype in getcreds(profiles[number]):
if enctype:
user = decode(user)
passw = decode(passw)
print("Url: " + url)
print("Username: " + user)
print("Password: " + passw)
NSS_Shutdown()
main()
Now let's run the program.
When you run it, you will be asked for the profile to load, so enter that number.
Please enter the profile number.
0: C:\Users\admin\AppData\Roaming\Mozilla\Firefox\Profiles\aaaaaaaa.TestProfile
1: C:\Users\admin\AppData\Roaming\Mozilla\Firefox\Profiles\bbbbbbbb.default-release
number:
Then enter the master password. If it is not set, just press the Enter key.
Please enter the master password:
Then the login data will be output.
Url: https://id.unity.com
Username: admin
Password: SecurePass9999
Url: https://accounts.google.com
Username: [email protected]
Password: passwd314159
So, this time I tried to analyze the login data of Firefox. I feel that it was a simpler mechanism than Chrome and Edge / IE, but I think that setting a master password will provide stronger security than any browser.
Currently, there seems to be no way to crack the master password directly, so in conclusion, it seems safest to set and use the master password in Firefox.
The following code is the code I tried to implement in C #.
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using Newtonsoft.Json;
namespace FirefoxDecrypt
{
struct SECItem
{
public uint type;
public byte[] data;
public uint len;
}
public class Logins
{
public Credential[] logins { get; set; }
}
public class Credential
{
public string hostname { get; set; }
public string encryptedUsername { get; set; }
public string encryptedPassword { get; set; }
public int encType { get; set; }
}
class Program
{
const string nss = "C:\\Program Files\\Mozilla Firefox\\nss3.dll";
const string profile = "C:\\Users\\user01\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\xxxxxxxx.default-release";
[DllImport(nss)]
static extern int NSS_Init(string s);
[DllImport(nss)]
static extern int NSS_Shutdown();
[DllImport(nss)]
static extern int PK11SDR_Decrypt(ref SECItem s1, ref SECItem s2, IntPtr ptr);
[DllImport(nss)]
static extern void SECITEM_ZfreeItem(ref SECItem s, int i);
static void Main(string[] args)
{
int e = NSS_Init("sql:" + profile);
Console.WriteLine("NSS_Init: " + e);
string db = Path.Combine(profile, "logins.json");
string v = File.ReadAllText(db);
var data = JsonConvert.DeserializeObject<Logins>(v);
foreach(var login in data.logins)
{
if(login.encType == 1)
{
var user = Decode(login.encryptedUsername);
var pass = Decode(login.encryptedPassword);
Console.WriteLine("URL: " + login.hostname);
Console.WriteLine("Username: " + user);
Console.WriteLine("Password: " + pass);
}
}
NSS_Shutdown();
Console.ReadKey(true);
}
static string Decode(string b64)
{
var bin = Convert.FromBase64String(b64);
//Maybe there is a problem around here
SECItem inp = new SECItem { data = bin, len = (uint)bin.Length, type = 0 };
SECItem outs = new SECItem { data = null, len = 0, type = 0 };
var e = PK11SDR_Decrypt(ref inp, ref outs, IntPtr.Zero);
Console.WriteLine("PK11SDR_Decrypt: " + e);
var res = Encoding.UTF8.GetString(outs.data);
SECITEM_ZfreeItem(ref outs, 0);
return res;
}
}
}
It should be doing almost the same as the Python version, but when I call PK11SDR_Decrypt of the Decode method, error code -1 is inevitably returned. There was no problem with the Base64 decoded data, so I think there is a problem with the SECItem structure, but in the end I gave up without knowing the cause. If anyone knows the cause, please let me know in the comments. ~~ Isn't it in the stone ... ~~
Recommended Posts