You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
5.1 KiB
202 lines
5.1 KiB
package hackbrowserdata
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
// import sqlite3 driver
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
"github.com/moond4rk/hackbrowserdata/browserdata"
|
|
"github.com/moond4rk/hackbrowserdata/crypto"
|
|
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
|
|
)
|
|
|
|
type firefox struct {
|
|
name browser
|
|
storage string
|
|
profilePath string
|
|
profilePaths []string
|
|
profilePathKeys map[string][]byte
|
|
disableFindAllUser bool
|
|
firefoxPassword []byte
|
|
supportedData []DataType
|
|
supportedDataMap map[DataType]struct{}
|
|
}
|
|
|
|
func NewFirefox(options *Options) (Browser, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *firefox) Init() error {
|
|
if err := f.initBrowserData(); err != nil {
|
|
return err
|
|
}
|
|
if err := f.initProfiles(); err != nil {
|
|
return fmt.Errorf("profile path '%s' does not exist %w", f.profilePath, ErrBrowserNotExists)
|
|
}
|
|
return f.initMasterKey()
|
|
}
|
|
|
|
func (f *firefox) Passwords() ([]browserdata.Password, error) {
|
|
if _, ok := f.supportedDataMap[TypePassword]; !ok {
|
|
// TODO: Error handle more gracefully
|
|
return nil, errors.New("password for c.name is not supported")
|
|
}
|
|
var fullPass []browserdata.Password
|
|
for profile, masterKey := range f.profilePathKeys {
|
|
passFile := filepath.Join(profile, TypePassword.Filename(f.name))
|
|
if !fileutil.IsFileExists(passFile) {
|
|
return nil, errors.New("password file does not exist")
|
|
}
|
|
passwords, err := browserdata.ExportPasswords(masterKey, passFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(passwords) > 0 {
|
|
fullPass = append(fullPass, passwords...)
|
|
}
|
|
}
|
|
return fullPass, nil
|
|
}
|
|
|
|
func (f *firefox) initBrowserData() error {
|
|
if f.supportedDataMap == nil {
|
|
f.supportedDataMap = make(map[DataType]struct{})
|
|
}
|
|
for _, v := range f.supportedData {
|
|
f.supportedDataMap[v] = struct{}{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *firefox) initProfiles() error {
|
|
if !fileutil.IsDirExists(f.profilePath) {
|
|
return ErrBrowserNotExists
|
|
}
|
|
if !f.disableFindAllUser {
|
|
profilesPaths, err := f.findAllProfiles()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.profilePaths = profilesPaths
|
|
} else {
|
|
f.profilePaths = []string{f.profilePath}
|
|
}
|
|
f.profilePathKeys = make(map[string][]byte)
|
|
return nil
|
|
}
|
|
|
|
func (f *firefox) findAllProfiles() ([]string, error) {
|
|
var profiles []string
|
|
root := fileutil.ParentDir(f.profilePath)
|
|
|
|
err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.HasSuffix(path, "key4.db") {
|
|
profiles = append(profiles, filepath.Dir(path))
|
|
}
|
|
depth := len(strings.Split(path, string(filepath.Separator))) - len(strings.Split(root, string(filepath.Separator)))
|
|
|
|
// if the depth is more than 2 and it's a directory, skip it
|
|
if info.IsDir() && path != root && depth >= 3 {
|
|
return filepath.SkipDir
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return profiles, err
|
|
}
|
|
|
|
func (f *firefox) initMasterKey() error {
|
|
for _, profile := range f.profilePaths {
|
|
key, err := f.findMasterKey(profile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.profilePathKeys[profile] = key
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *firefox) findMasterKey(profile string) ([]byte, error) {
|
|
keyFile := "key4.db"
|
|
keyPath := filepath.Join(profile, keyFile)
|
|
if !fileutil.IsFileExists(keyPath) {
|
|
// TODO: handle error with more details
|
|
return nil, ErrBrowserNotExists
|
|
}
|
|
tempFile := filepath.Join(os.TempDir(), keyFile)
|
|
if err := fileutil.CopyFile(keyPath, tempFile); err != nil {
|
|
return nil, err
|
|
}
|
|
defer os.Remove(tempFile)
|
|
globalSalt, metaBytes, nssA11, nssA102, err := getFirefoxDecryptKey(tempFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
metaPBE, err := crypto.NewASN1PBE(metaBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k, err := metaPBE.Decrypt(globalSalt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
|
if !bytes.Equal(k, []byte("password-check")) || !bytes.Equal(nssA102, keyLin) {
|
|
return nil, fmt.Errorf("invalid master key")
|
|
}
|
|
nssPBE, err := crypto.NewASN1PBE(nssA11)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
masterKey, err := nssPBE.Decrypt(globalSalt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return masterKey, nil
|
|
}
|
|
|
|
func getFirefoxDecryptKey(key4file string) (item1, item2, a11, a102 []byte, err error) {
|
|
db, err := sql.Open("sqlite3", key4file)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
defer db.Close()
|
|
var (
|
|
queryMetaData = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
|
|
queryNssPrivate = `SELECT a11, a102 from nssPrivate`
|
|
)
|
|
if err = db.QueryRow(queryMetaData).Scan(&item1, &item2); err != nil {
|
|
return nil, nil, nil, nil, fmt.Errorf("query metaData failed: %w, query: %s", err, queryMetaData)
|
|
}
|
|
|
|
if err = db.QueryRow(queryNssPrivate).Scan(&a11, &a102); err != nil {
|
|
return nil, nil, nil, nil, fmt.Errorf("query nssPrivate failed: %w, query: %s", err, queryNssPrivate)
|
|
}
|
|
return item1, item2, a11, a102, nil
|
|
}
|
|
|
|
func (f *firefox) setDisableAllUsers(e bool) {
|
|
f.disableFindAllUser = e
|
|
}
|
|
|
|
func (f *firefox) setProfilePath(p string) {
|
|
f.profilePath = p
|
|
}
|
|
|
|
func (f *firefox) setStorageName(s string) {
|
|
f.storage = s
|
|
}
|
|
|