@ -1,19 +1,168 @@
package hackbrowserdata
import (
"bytes"
"database/sql"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
_ "github.com/mattn/go-sqlite3"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
)
type firefox struct {
name string
name browser
storage string
profilePath string
enableAllUser bool
masterKey [ ] byte
profilePaths [ ] string
profilePathKeys map [ string ] [ ] byte
disableFindAllUser bool
firefoxPassword [ ] byte
supportedData [ ] browserDataType
supportedDataMap map [ browserDataType ] struct { }
}
func ( f * firefox ) Init ( ) error {
if err := f . initBrowserData ( ) ; err != nil {
return err
}
if err := f . initProfile ( ) ; err != nil {
return fmt . Errorf ( "profile path '%s' does not exist %w" , f . profilePath , ErrBrowserNotExists )
}
return f . initMasterKey ( )
}
func ( f * firefox ) initBrowserData ( ) error {
if f . supportedDataMap == nil {
f . supportedDataMap = make ( map [ browserDataType ] struct { } )
}
for _ , v := range f . supportedData {
f . supportedDataMap [ v ] = struct { } { }
}
return nil
}
func ( f * firefox ) initProfile ( ) 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 ) setEnableAllUsers ( e bool ) {
f . enableAllUser = e
func ( f * firefox ) findMasterKey ( profile string ) ( [ ] byte , error ) {
keyPath := filepath . Join ( profile , "key4.db" )
if ! fileutil . IsFileExists ( keyPath ) {
return nil , ErrBrowserNotExists
}
if err := fileutil . CopyFile ( keyPath , "key4-copy.db" ) ; err != nil {
return nil , err
}
defer os . Remove ( "key4-copy.db" )
globalSalt , metaBytes , nssA11 , nssA102 , err := getFirefoxDecryptKey ( "key4-copy.db" )
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
}
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 nil , err
}
masterKey , err := nssPBE . Decrypt ( globalSalt )
if err != nil {
return nil , err
}
return masterKey , nil
}
}
return nil , nil
}
const (
queryMetaData = ` SELECT item1, item2 FROM metaData WHERE id = 'password' `
queryNssPrivate = ` SELECT a11, a102 from nssPrivate `
)
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 ( )
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 ) {