- Add new files `bookmark.go`, `chromium_darwin.go`, `browserdata/cookie.go`, `browser_windows.go`, `browser_linux.go`, `datatype.go`, `password.go`, `browserdata/browserdata.go`, `chromium_linux.go`, `chromium_windows.go` - Refactor `NewChromium` function to accept an `Options` argument - Refactor `options` code to use a struct instead of multiple arguments - Modify `filterExistDataPaths` to return data paths instead of `BrowserData` - Add `Passwords` and `Cookies` functions to `chromium` - Delete unused files and functions ` errors.go`, `cookie.go`, `browsingdata.go`, `consts.go`, `firefox.go`, `password.go`, `chromium_test.go`, `firefox_test.go` - Improve error messages for keychain related issues - Change `BrowserData.Passwords()` to `Passwords()` for simplification - Remove unused code for browsers `Yandex` and `Edge`, and unused map `browsers` and methods `BrowsingData()` and `AllBrowsingData()` from chromium and firefox structs - Add `DefaultDBHandler` and `DefaultJSONHandler` functions for SQLite3 queriesfeat/library
parent
0d6642ead9
commit
24c90f5b92
@ -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 |
||||||
|
} |
@ -1,60 +0,0 @@ |
|||||||
package hackbrowserdata |
|
||||||
|
|
||||||
type browserDataType int |
|
||||||
|
|
||||||
const ( |
|
||||||
TypePassword browserDataType = iota + 1 |
|
||||||
TypeCookie |
|
||||||
TypeHistory |
|
||||||
TypeBookmark |
|
||||||
TypeCreditCard |
|
||||||
TypeDownload |
|
||||||
TypeExtensions |
|
||||||
TypeSessionStorage |
|
||||||
TypeLocalStorage |
|
||||||
) |
|
||||||
|
|
||||||
func (i browserDataType) Filename(b browser) string { |
|
||||||
switch b.Type() { |
|
||||||
case browserTypeChromium: |
|
||||||
return i.chromiumFilename() |
|
||||||
case browserTypeFirefox: |
|
||||||
return i.firefoxFilename() |
|
||||||
case browserTypeYandex: |
|
||||||
return i.yandexFilename() |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func (i browserDataType) chromiumFilename() string { |
|
||||||
switch i { |
|
||||||
case TypePassword: |
|
||||||
return "Login Data" |
|
||||||
case TypeCookie: |
|
||||||
return "Cookies" |
|
||||||
case TypeHistory: |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func (i browserDataType) yandexFilename() string { |
|
||||||
switch i { |
|
||||||
case TypePassword: |
|
||||||
return "Login State" |
|
||||||
case TypeCookie: |
|
||||||
return "Cookies" |
|
||||||
case TypeHistory: |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func (i browserDataType) firefoxFilename() string { |
|
||||||
switch i { |
|
||||||
case TypePassword: |
|
||||||
return "logins.json" |
|
||||||
case TypeCookie: |
|
||||||
return "cookies.sqlite" |
|
||||||
case TypeHistory: |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
@ -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 |
@ -1,17 +0,0 @@ |
|||||||
package hackbrowserdata |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestChromium_Init(_ *testing.T) { |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkChromium_Init(b *testing.B) { |
|
||||||
chromium := browsers[Chrome] |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
if err := chromium.Init(); err != nil { |
|
||||||
b.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1 @@ |
|||||||
|
package hackbrowserdata |
@ -1,15 +0,0 @@ |
|||||||
package hackbrowserdata |
|
||||||
|
|
||||||
type Cookie struct { |
|
||||||
Domain string |
|
||||||
Expiration float64 |
|
||||||
Value string |
|
||||||
} |
|
||||||
|
|
||||||
func (c *chromium) Cookies() ([]Cookie, error) { |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (f *firefox) Cookies() ([]Cookie, error) { |
|
||||||
return nil, nil |
|
||||||
} |
|
@ -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" |
||||||
|
) |
@ -1,18 +1,17 @@ |
|||||||
package hackbrowserdata |
package hackbrowserdata |
||||||
|
|
||||||
import ( |
import ( |
||||||
"fmt" |
|
||||||
"testing" |
"testing" |
||||||
) |
) |
||||||
|
|
||||||
func TestFirefox_Init(t *testing.T) { |
func TestFirefox_Init(t *testing.T) { |
||||||
firefox := browsers[Firefox] |
// firefox := browsers[Firefox]
|
||||||
if err := firefox.Init(); err != nil { |
// if err := firefox.Init(); err != nil {
|
||||||
t.Fatal(err) |
// t.Fatal(err)
|
||||||
} |
// }
|
||||||
passwords, err := firefox.Passwords() |
// passwords, err := firefox.Passwords()
|
||||||
if err != nil { |
// if err != nil {
|
||||||
t.Fatal(err) |
// t.Fatal(err)
|
||||||
} |
// }
|
||||||
fmt.Println(passwords) |
// fmt.Println(passwords)
|
||||||
} |
} |
||||||
|
@ -1,33 +1,36 @@ |
|||||||
package hackbrowserdata |
package hackbrowserdata |
||||||
|
|
||||||
import ( |
type Options struct { |
||||||
"path/filepath" |
Name browser |
||||||
) |
Storage string |
||||||
|
ProfilePath string |
||||||
type BrowserOption func(browserOptionsSetter) |
IsEnableAllUser bool |
||||||
|
DataTypes []DataType |
||||||
type browserOptionsSetter interface { |
NewBrowserFunc func(*Options) (Browser, error) |
||||||
setProfilePath(string) |
} |
||||||
|
|
||||||
setDisableAllUsers(bool) |
type BrowserOption func(*Options) |
||||||
|
|
||||||
setStorageName(string) |
func WithBrowserName(p string) BrowserOption { |
||||||
|
return func(o *Options) { |
||||||
|
o.Name = browser(p) |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
func WithProfilePath(p string) BrowserOption { |
func WithProfilePath(p string) BrowserOption { |
||||||
return func(b browserOptionsSetter) { |
return func(o *Options) { |
||||||
b.setProfilePath(filepath.Clean(p)) |
o.ProfilePath = p |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
func WithDisableAllUsers(e bool) BrowserOption { |
func WithEnableAllUsers(e bool) BrowserOption { |
||||||
return func(b browserOptionsSetter) { |
return func(o *Options) { |
||||||
b.setDisableAllUsers(e) |
o.IsEnableAllUser = e |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
func WithStorageName(s string) BrowserOption { |
func WithStorageName(s string) BrowserOption { |
||||||
return func(b browserOptionsSetter) { |
return func(o *Options) { |
||||||
b.setStorageName(s) |
o.Storage = s |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,185 +0,0 @@ |
|||||||
package hackbrowserdata |
|
||||||
|
|
||||||
import ( |
|
||||||
"database/sql" |
|
||||||
"encoding/base64" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"time" |
|
||||||
|
|
||||||
// import sqlite3 driver
|
|
||||||
_ "github.com/mattn/go-sqlite3" |
|
||||||
"github.com/tidwall/gjson" |
|
||||||
|
|
||||||
"github.com/moond4rk/hackbrowserdata/crypto" |
|
||||||
"github.com/moond4rk/hackbrowserdata/item" |
|
||||||
"github.com/moond4rk/hackbrowserdata/log" |
|
||||||
"github.com/moond4rk/hackbrowserdata/utils/fileutil" |
|
||||||
"github.com/moond4rk/hackbrowserdata/utils/typeutil" |
|
||||||
) |
|
||||||
|
|
||||||
type Password struct { |
|
||||||
Profile string |
|
||||||
Username string |
|
||||||
Password string |
|
||||||
encryptPass []byte |
|
||||||
encryptUser []byte |
|
||||||
LoginURL string |
|
||||||
CreateDate time.Time |
|
||||||
} |
|
||||||
|
|
||||||
func (c *chromium) Passwords() ([]Password, error) { |
|
||||||
if _, ok := c.supportedDataMap[TypePassword]; !ok { |
|
||||||
// TODO: Error handle more gracefully
|
|
||||||
return nil, errors.New("password for c.name is not supported") |
|
||||||
} |
|
||||||
var fullPass []Password |
|
||||||
for _, profile := range c.profilePaths { |
|
||||||
passFile := filepath.Join(profile, TypePassword.Filename(c.name)) |
|
||||||
if !fileutil.IsFileExists(passFile) { |
|
||||||
return nil, errors.New("password file does not exist") |
|
||||||
} |
|
||||||
if err := fileutil.CopyFile(passFile, item.TempChromiumPassword); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
passwords, err := c.exportPasswords(profile, item.TempChromiumPassword) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if len(passwords) > 0 { |
|
||||||
fullPass = append(fullPass, passwords...) |
|
||||||
} |
|
||||||
} |
|
||||||
return fullPass, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *chromium) exportPasswords(profile, dbfile string) ([]Password, error) { |
|
||||||
db, err := sql.Open("sqlite3", dbfile) |
|
||||||
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(c.masterKey) == 0 { |
|
||||||
password, err = crypto.DPAPI(encryptPass) |
|
||||||
} else { |
|
||||||
password, err = crypto.DecryptPass(c.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 ( |
|
||||||
queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` |
|
||||||
) |
|
||||||
|
|
||||||
func (f *firefox) Passwords() ([]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 []Password |
|
||||||
for profile, masterKey := range f.profilePathKeys { |
|
||||||
passFile := filepath.Join(profile, TypePassword.Filename(f.name)) |
|
||||||
if !fileutil.IsFileExists(passFile) { |
|
||||||
fmt.Println(passFile) |
|
||||||
return nil, errors.New("password file does not exist") |
|
||||||
} |
|
||||||
if err := fileutil.CopyFile(passFile, item.TempFirefoxPassword); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
passwords, err := f.exportPasswords(masterKey, item.TempFirefoxPassword) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if len(passwords) > 0 { |
|
||||||
fullPass = append(fullPass, passwords...) |
|
||||||
} |
|
||||||
} |
|
||||||
return fullPass, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (f *firefox) exportPasswords(masterKey []byte, loginFile string) ([]Password, error) { |
|
||||||
s, err := os.ReadFile(loginFile) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
defer os.Remove(loginFile) |
|
||||||
loginsJSON := gjson.GetBytes(s, "logins") |
|
||||||
var passwords []Password |
|
||||||
if loginsJSON.Exists() { |
|
||||||
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.CreateDate = typeutil.TimeStamp(v.Get("timeCreated").Int() / 1000) |
|
||||||
passwords = append(passwords, p) |
|
||||||
} |
|
||||||
} |
|
||||||
return passwords, nil |
|
||||||
} |
|
Loading…
Reference in new issue