feat/library: Add support for use as library for macos chrome

- Implement `Browser` interface and related functions
- Add `Passwords` method to `chromium` for exporting and returning passwords
- Ignore `/docs` directory
feat/library
moonD4rk 1 year ago
parent 3eaa2dd2bf
commit aa2e1af5b5
No known key found for this signature in database
GPG Key ID: 5AB6217E08D39ABA
  1. 2
      .gitignore
  2. 86
      browser.go
  3. 60
      browsingdata.go
  4. 139
      chromium.go
  5. 18
      chromium_test.go
  6. 38
      consts.go
  7. 15
      cookie.go
  8. 12
      errors.go
  9. 50
      examples/export.go
  10. 25
      firefox.go
  11. 33
      options.go
  12. 111
      password.go

2
.gitignore vendored

@ -194,3 +194,5 @@ hack-browser-data
!/browingdata/history !/browingdata/history
!/browingdata/history/history.go !/browingdata/history/history.go
!/browingdata/history/history_test.go !/browingdata/history/history_test.go
/docs

@ -0,0 +1,86 @@
package hackbrowserdata
type Browser interface {
BrowserData
Init() error
}
func NewBrowser(b browser, options ...BrowserOption) (Browser, error) {
browser := browsers[b]
if setter, ok := browser.(browserOptionsSetter); ok {
for _, option := range options {
option(setter)
}
}
if err := browser.Init(); err != nil {
return nil, err
}
return browser, nil
}
type browser string
type BrowserData interface {
Passwords() ([]Password, error)
Cookies() ([]Cookie, error)
}
func (c *chromium) BrowsingData(items []browserDataType) ([]BrowserData, error) {
for _, item := range items {
_ = item
}
return nil, nil
}
func (c *chromium) AllBrowsingData() ([]BrowserData, error) {
return nil, nil
}
func (f *firefox) BrowsingData(items []browserDataType) (BrowserData, error) {
return nil, nil
}
const (
Chrome browser = "chrome"
Firefox browser = "firefox"
Yandex browser = "yandex"
)
type browserType int
const (
browserTypeChromium browserType = iota + 1
browserTypeFirefox
browserTypeYandex
)
func (b browser) Type() browserType {
switch b {
case Firefox:
return browserTypeYandex
case Yandex:
return browserTypeFirefox
default:
return browserTypeChromium
}
}
var (
browsers = map[browser]Browser{
Chrome: &chromium{
name: Chrome,
storage: chromeStorageName,
profilePath: chromeProfilePath,
supportedData: []browserDataType{TypePassword},
},
Firefox: &firefox{
name: "",
storage: "",
profilePath: "",
},
Yandex: &chromium{},
}
)

@ -0,0 +1,60 @@
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,139 @@
package hackbrowserdata
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"io/fs"
"os/exec"
"path/filepath"
"strings"
"golang.org/x/crypto/pbkdf2"
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
)
type chromium struct {
name browser
storage string
profilePath string
profilePaths []string
disableFindAllUser bool
masterKey []byte
supportedData []browserDataType
supportedDataMap map[browserDataType]struct{}
}
func (c *chromium) Init() error {
if err := c.initBrowserData(); err != nil {
return err
}
if err := c.initProfile(); err != nil {
return fmt.Errorf("profile path '%s' does not exist %w", c.profilePath, ErrBrowserNotExists)
}
if err := c.initMasterKey(); err != nil {
return err
}
return nil
}
func (c *chromium) initBrowserData() error {
if c.supportedDataMap == nil {
c.supportedDataMap = make(map[browserDataType]struct{})
}
for _, v := range c.supportedData {
c.supportedDataMap[v] = struct{}{}
}
return nil
}
func (c *chromium) initProfile() error {
if !fileutil.IsDirExists(c.profilePath) {
return ErrBrowserNotExists
}
if !c.disableFindAllUser {
profilesPaths, err := c.findAllProfiles()
if err != nil {
return err
}
c.profilePaths = profilesPaths
}
return nil
}
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, "History") {
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
}
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
}
salt := []byte("saltysalt")
// @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157
key := pbkdf2.Key(secret, salt, 1003, 16, sha1.New)
if key == nil {
return ErrWrongSecurityCommand
}
c.masterKey = key
return nil
}
func (c *chromium) setProfilePath(p string) {
c.profilePath = p
}
func (c *chromium) setEnableAllUsers(e bool) {
c.disableFindAllUser = e
}
func (c *chromium) setStorageName(s string) {
c.storage = s
}

@ -0,0 +1,18 @@
package hackbrowserdata
import (
"testing"
)
func TestChromium_Init(t *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,38 @@
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/"
)

@ -0,0 +1,15 @@
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,12 @@
package hackbrowserdata
import (
"errors"
)
var (
ErrBrowserNotExists = errors.New("browser not exists")
ErrWrongSecurityCommand = errors.New("wrong security command")
ErrNoPasswordInOutput = errors.New("no password in output")
ErrCouldNotFindInKeychain = errors.New("could not be find in keychain")
)

@ -0,0 +1,50 @@
package main
import (
"fmt"
hkb "github.com/moond4rk/hackbrowserdata"
)
func main() {
chrome, err := hkb.NewBrowser(hkb.Chrome)
if err != nil {
panic(err)
}
passwords, err := chrome.Passwords()
if err != nil {
panic(err)
}
for _, pass := range passwords {
fmt.Printf("%+v\n", pass)
}
// cookies, err := chrome.Cookies()
// if err != nil {
// panic(err)
// }
// creditCards, err := browser.CreditCards()
// bookmarks, err := browser.Bookmarks()
// downloads, err := browser.Downloads()
// extensions, err := browser.Extensions()
// history, err := browser.History()
// localStorage, err := browser.LocalStorage()
// sessionStorage, err := browser.SessionStorage()
// items := []hkb.browsingDataType{hkb.Cookie, hkb.Password, hkb.History, hkb.Bookmark}
// browsingData, err := browser.BrowsingDatas(items)
// if err != nil {
// panic(err)
// }
//
// if err != nil {
// panic(err)
// }
// _ = cookies
// items := []hkb.browsingDataType{hkb.Cookie}
// datas, err := browser.BrowsingDatas(items)
// if err != nil {
// panic(err)
// }
// _ = datas
}

@ -0,0 +1,25 @@
package hackbrowserdata
type firefox struct {
name string
storage string
profilePath string
enableAllUser bool
masterKey []byte
}
func (f *firefox) Init() error {
return nil
}
func (f *firefox) setEnableAllUsers(e bool) {
f.enableAllUser = e
}
func (f *firefox) setProfilePath(p string) {
f.profilePath = p
}
func (f *firefox) setStorageName(s string) {
f.storage = s
}

@ -0,0 +1,33 @@
package hackbrowserdata
import (
"path/filepath"
)
type BrowserOption func(browserOptionsSetter)
type browserOptionsSetter interface {
setProfilePath(string)
setEnableAllUsers(bool)
setStorageName(string)
}
func WithProfilePath(p string) BrowserOption {
return func(b browserOptionsSetter) {
b.setProfilePath(filepath.Clean(p))
}
}
func WithEnableAllUsers(e bool) BrowserOption {
return func(b browserOptionsSetter) {
b.setEnableAllUsers(e)
}
}
func WithStorageName(s string) BrowserOption {
return func(b browserOptionsSetter) {
b.setStorageName(s)
}
}

@ -0,0 +1,111 @@
package hackbrowserdata
import (
"database/sql"
"errors"
"fmt"
"path/filepath"
"time"
// import sqlite3 driver
_ "github.com/mattn/go-sqlite3"
"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 {
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) {
fmt.Println(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 f.masterKey != nil {
return nil, nil
}
return nil, nil
}
Loading…
Cancel
Save