Try to decipher the login data stored in Firefox

Introduction

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.

How to save Firefox login data

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.

Try reading login data

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.

Program to read login data

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

in conclusion

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.

Digression: About the matter that could not be implemented in C

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 ... ~~

References

Recommended Posts

Try to decipher the login data stored in Firefox
Try to put data in MongoDB
Cython to try in the shortest
[Cloudian # 5] Try to list the objects stored in the bucket with Python (boto3)
Try to display the railway data of national land numerical information in 3D
Login to website in Python
Try scraping the data of COVID-19 in Tokyo with Python
Various ways to calculate the similarity between data in python
Try to extract the keywords that are popular in COTOHA
Try to decipher the garbled attachment file name with Python
The minimum methods to remember when aggregating data in Pandas
Try to extract the features of the sensor data with CNN
Programming to fight in the world ~ 5-1
Programming to fight in the world 5-3
[Cloudian # 9] Try to display the metadata of the object in Python (boto3)
Programming to fight in the world-Chapter 4
In the python command python points to python3.8
[Cloudian # 2] Try to display the object storage bucket in Python (boto3)
Try to introduce the theme to Pelican
Try to calculate Trace in Python
Try to model the cumulative return of rollovers in futures trading
Check the data summary in CASTable
The fastest way to try EfficientNet
Try to solve the shortest path with Python + NetworkX + social data
Programming to fight in the world ~ 5-2
The easiest way to try PyQtGraph
Try the book "Introduction to Natural Language Processing Application Development in 15 Steps" --Chapter 4 Step 15 Memo "Data Collection"
Try to get the road surface condition using big data of road surface management
Try to extract specific data from JSON format data in object storage Cloudian/S3
Try logging in to qiita with Python
Try using the Wunderlist API in Python
Try using the Kraken API in Python
Try using the HL band in order
Try to face the integration by parts
Try working with binary data in Python
In Jupyter, add IPerl to the kernel.
I saved the scraped data in CSV!
Try converting to tidy data with pandas
Python amateurs try to summarize the list ①
Try the new scheduler chaining in PyTorch 1.4
Various comments to write in the program
Try hitting the YouTube API in Python
Try hitting the Spotify API in Django.
Books on data science to read in 2020
[Django memo] I want to set the login user information in the form in advance
Try to display the Fibonacci sequence in various languages in the name of algorithm practice
Use PIL in Python to extract only the data you want from Exif
I'm addicted to the difference in how Flask and Django receive JSON data
The first step to log analysis (how to format and put log data in Pandas)
Try to solve the fizzbuzz problem with Keras
How to use the C library in Python
[Introduction to SEIR model] Try fitting COVID-19 data ♬
Log in to the remote server with SSH
Try to implement Oni Maitsuji Miserable in python
Try adding fisheye lens distortion to the image
Try to calculate a statistical problem in Python
3.14 π day, so try to output in Python
Try to aggregate doujin music data with pandas
Try to decompose the daimyo procession into Tucker
Try to solve the Python class inheritance problem
Just add the python array to the json data