mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-08-17 23:57:12 +02:00
feat: refactor firefox get master key retrieval and decryption functions. (#300)
* refactor: Simplify Firefox master key retrieval and decryption functions. - Simplify variable names and functions in browsingdata/password/password.go - Modify Decrypt function for each PBE type to have only one parameter named globalSalt in crypto/crypto.go - Implement functions to retrieve master key from Firefox's key4.db file and query metadata and private NSS data in browser/firefox/firefox.go * chore: Add dependencies and tests to Firefox package. - Add go-sqlmock and github.com/kisielk/sqlstruct dependencies - Add tests for Firefox package metadata and nssPrivate query - Add test for Firefox's processMasterKey function (currently commented out) * refactor: Refactor Firefox test functions, remove unused code - Remove unused test function in firefox_test.go file - Clean up code by removing unnecessary changes - Simplify file structure for easier maintenance and readability
This commit is contained in:
@@ -1,12 +1,18 @@
|
|||||||
package firefox
|
package firefox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite" // sqlite3 driver TODO: replace with chooseable driver
|
||||||
|
|
||||||
"github.com/moond4rk/hackbrowserdata/browsingdata"
|
"github.com/moond4rk/hackbrowserdata/browsingdata"
|
||||||
|
"github.com/moond4rk/hackbrowserdata/crypto"
|
||||||
"github.com/moond4rk/hackbrowserdata/item"
|
"github.com/moond4rk/hackbrowserdata/item"
|
||||||
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
|
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
|
||||||
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
|
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
|
||||||
@@ -68,8 +74,82 @@ func firefoxWalkFunc(items []item.Item, multiItemPaths map[string]map[item.Item]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMasterKey returns master key of Firefox. from key4.db
|
||||||
func (f *Firefox) GetMasterKey() ([]byte, error) {
|
func (f *Firefox) GetMasterKey() ([]byte, error) {
|
||||||
return f.masterKey, nil
|
tempFilename := item.FirefoxKey4.TempFilename()
|
||||||
|
|
||||||
|
// Open and defer close of the database.
|
||||||
|
keyDB, err := sql.Open("sqlite", tempFilename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open key4.db error: %w", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tempFilename)
|
||||||
|
defer keyDB.Close()
|
||||||
|
|
||||||
|
globalSalt, metaBytes, err := queryMetaData(keyDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query metadata error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nssA11, nssA102, err := queryNssPrivate(keyDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query NSS private error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return processMasterKey(globalSalt, metaBytes, nssA11, nssA102)
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryMetaData(db *sql.DB) ([]byte, []byte, error) {
|
||||||
|
const query = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
|
||||||
|
var globalSalt, metaBytes []byte
|
||||||
|
if err := db.QueryRow(query).Scan(&globalSalt, &metaBytes); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return globalSalt, metaBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {
|
||||||
|
const query = `SELECT a11, a102 from nssPrivate`
|
||||||
|
var nssA11, nssA102 []byte
|
||||||
|
if err := db.QueryRow(query).Scan(&nssA11, &nssA102); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nssA11, nssA102, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processMasterKey process master key of Firefox.
|
||||||
|
// Process the metaBytes and nssA11 with the corresponding cryptographic operations.
|
||||||
|
func processMasterKey(globalSalt, metaBytes, nssA11, nssA102 []byte) ([]byte, error) {
|
||||||
|
metaPBE, err := crypto.NewASN1PBE(metaBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := metaPBE.Decrypt(globalSalt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Contains(k, []byte("password-check")) {
|
||||||
|
return nil, errors.New("password-check not found")
|
||||||
|
}
|
||||||
|
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||||
|
if !bytes.Equal(nssA102, keyLin) {
|
||||||
|
return nil, errors.New("nssA102 not equal keyLin")
|
||||||
|
}
|
||||||
|
nssPBE, err := crypto.NewASN1PBE(nssA11)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
finallyKey, err := nssPBE.Decrypt(globalSalt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(finallyKey) < 24 {
|
||||||
|
return nil, errors.New("finallyKey length less than 24")
|
||||||
|
}
|
||||||
|
finallyKey = finallyKey[:24]
|
||||||
|
return finallyKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firefox) Name() string {
|
func (f *Firefox) Name() string {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package firefox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueryMetaData(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows([]string{"item1", "item2"}).
|
||||||
|
AddRow([]byte("globalSalt"), []byte("metaBytes"))
|
||||||
|
mock.ExpectQuery("SELECT item1, item2 FROM metaData WHERE id = 'password'").WillReturnRows(rows)
|
||||||
|
|
||||||
|
globalSalt, metaBytes, err := queryMetaData(db)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte("globalSalt"), globalSalt)
|
||||||
|
assert.Equal(t, []byte("metaBytes"), metaBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryNssPrivate(t *testing.T) {
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows([]string{"a11", "a102"}).
|
||||||
|
AddRow([]byte("nssA11"), []byte("nssA102"))
|
||||||
|
mock.ExpectQuery("SELECT a11, a102 from nssPrivate").WillReturnRows(rows)
|
||||||
|
|
||||||
|
nssA11, nssA102, err := queryNssPrivate(db)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte("nssA11"), nssA11)
|
||||||
|
assert.Equal(t, []byte("nssA102"), nssA102)
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package password
|
package password
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -169,87 +168,42 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (f *FirefoxPassword) Parse(masterKey []byte) error {
|
func (f *FirefoxPassword) Parse(masterKey []byte) error {
|
||||||
globalSalt, metaBytes, nssA11, nssA102, err := getFirefoxDecryptKey(item.FirefoxKey4.TempFilename())
|
logins, err := getFirefoxLoginData()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
metaPBE, err := crypto.NewASN1PBE(metaBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
k, err := metaPBE.Decrypt(globalSalt, masterKey)
|
for _, v := range logins {
|
||||||
if err != nil {
|
userPBE, err := crypto.NewASN1PBE(v.encryptUser)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
if bytes.Contains(k, []byte("password-check")) {
|
|
||||||
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
|
||||||
if bytes.Equal(nssA102, keyLin) {
|
|
||||||
nssPBE, err := crypto.NewASN1PBE(nssA11)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
finallyKey, err := nssPBE.Decrypt(globalSalt, masterKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
finallyKey = finallyKey[:24]
|
|
||||||
logins, err := getFirefoxLoginData()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range logins {
|
|
||||||
userPBE, err := crypto.NewASN1PBE(v.encryptUser)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pwdPBE, err := crypto.NewASN1PBE(v.encryptPass)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user, err := userPBE.Decrypt(finallyKey, masterKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pwd, err := pwdPBE.Decrypt(finallyKey, masterKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, loginData{
|
|
||||||
LoginURL: v.LoginURL,
|
|
||||||
UserName: string(user),
|
|
||||||
Password: string(pwd),
|
|
||||||
CreateDate: v.CreateDate,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
pwdPBE, err := crypto.NewASN1PBE(v.encryptPass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user, err := userPBE.Decrypt(masterKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pwd, err := pwdPBE.Decrypt(masterKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*f = append(*f, loginData{
|
||||||
|
LoginURL: v.LoginURL,
|
||||||
|
UserName: string(user),
|
||||||
|
Password: string(pwd),
|
||||||
|
CreateDate: v.CreateDate,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(*f, func(i, j int) bool {
|
sort.Slice(*f, func(i, j int) bool {
|
||||||
return (*f)[i].CreateDate.After((*f)[j].CreateDate)
|
return (*f)[i].CreateDate.After((*f)[j].CreateDate)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFirefoxDecryptKey(key4file string) (item1, item2, a11, a102 []byte, err error) {
|
|
||||||
keyDB, err := sql.Open("sqlite", key4file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
defer os.Remove(key4file)
|
|
||||||
defer keyDB.Close()
|
|
||||||
|
|
||||||
if err = keyDB.QueryRow(queryMetaData).Scan(&item1, &item2); err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = keyDB.QueryRow(queryNssPrivate).Scan(&a11, &a102); err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return item1, item2, a11, a102, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFirefoxLoginData() ([]loginData, error) {
|
func getFirefoxLoginData() ([]loginData, error) {
|
||||||
s, err := os.ReadFile(item.FirefoxPassword.TempFilename())
|
s, err := os.ReadFile(item.FirefoxPassword.TempFilename())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+5
-6
@@ -20,7 +20,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ASN1PBE interface {
|
type ASN1PBE interface {
|
||||||
Decrypt(globalSalt, masterPwd []byte) (key []byte, err error)
|
Decrypt(globalSalt []byte) (key []byte, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
|
func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
|
||||||
@@ -60,9 +60,8 @@ type nssPBE struct {
|
|||||||
Encrypted []byte
|
Encrypted []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n nssPBE) Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) {
|
func (n nssPBE) Decrypt(globalSalt []byte) (key []byte, err error) {
|
||||||
glmp := append(globalSalt, masterPwd...)
|
hp := sha1.Sum(globalSalt)
|
||||||
hp := sha1.Sum(glmp)
|
|
||||||
s := append(hp[:], n.salt()...)
|
s := append(hp[:], n.salt()...)
|
||||||
chp := sha1.Sum(s)
|
chp := sha1.Sum(s)
|
||||||
pes := paddingZero(n.salt(), 20)
|
pes := paddingZero(n.salt(), 20)
|
||||||
@@ -134,7 +133,7 @@ type slatAttr struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m metaPBE) Decrypt(globalSalt, _ []byte) (key2 []byte, err error) {
|
func (m metaPBE) Decrypt(globalSalt []byte) (key2 []byte, err error) {
|
||||||
k := sha1.Sum(globalSalt)
|
k := sha1.Sum(globalSalt)
|
||||||
key := pbkdf2.Key(k[:], m.salt(), m.iterationCount(), m.keySize(), sha256.New)
|
key := pbkdf2.Key(k[:], m.salt(), m.iterationCount(), m.keySize(), sha256.New)
|
||||||
iv := append([]byte{4, 14}, m.iv()...)
|
iv := append([]byte{4, 14}, m.iv()...)
|
||||||
@@ -177,7 +176,7 @@ type loginPBE struct {
|
|||||||
Encrypted []byte
|
Encrypted []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l loginPBE) Decrypt(globalSalt, _ []byte) (key []byte, err error) {
|
func (l loginPBE) Decrypt(globalSalt []byte) (key []byte, err error) {
|
||||||
return des3Decrypt(globalSalt, l.iv(), l.encrypted())
|
return des3Decrypt(globalSalt, l.iv(), l.encrypted())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module github.com/moond4rk/hackbrowserdata
|
|||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||||
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
|
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/otiai10/copy v1.14.0
|
github.com/otiai10/copy v1.14.0
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -23,6 +25,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
|||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
|
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
|||||||
Reference in New Issue
Block a user