diff --git a/cmd/cmd.go b/cmd/cmd.go index f2b86ad..541a0e8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -11,11 +11,13 @@ import ( ) var ( - browser string - exportDir string - outputFormat string - verbose bool - compress bool + browserName string + exportDir string + outputFormat string + verbose bool + compress bool + customProfilePath string + customKeyPath string ) func Execute() { @@ -23,25 +25,38 @@ func Execute() { Name: "hack-browser-data", Usage: "Export passwords/cookies/history/bookmarks from browser", UsageText: "[hack-browser-data -b chrome -f json -dir results -cc]\n Get all data(password/cookie/history/bookmark) from chrome", - Version: "0.3.0", + Version: "0.3.1", Flags: []cli.Flag{ - &cli.BoolFlag{Name: "verbose", Aliases: []string{"vv"}, Destination: &verbose, Value: false, Usage: "Verbose"}, - &cli.BoolFlag{Name: "compress", Aliases: []string{"cc"}, Destination: &compress, Value: false, Usage: "Compress result to zip"}, - &cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Destination: &browser, Value: "all", Usage: "Available browsers: all|" + strings.Join(core.ListBrowser(), "|")}, - &cli.StringFlag{Name: "results-dir", Aliases: []string{"dir"}, Destination: &exportDir, Value: "results", Usage: "Export dir"}, - &cli.StringFlag{Name: "format", Aliases: []string{"f"}, Destination: &outputFormat, Value: "csv", Usage: "Format, csv|json|console"}, + &cli.BoolFlag{Name: "verbose", Aliases: []string{"vv"}, Destination: &verbose, Value: false, Usage: "verbose"}, + &cli.BoolFlag{Name: "compress", Aliases: []string{"cc"}, Destination: &compress, Value: false, Usage: "compress result to zip"}, + &cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Destination: &browserName, Value: "all", Usage: "available browsers: all|" + strings.Join(core.ListBrowser(), "|")}, + &cli.StringFlag{Name: "results-dir", Aliases: []string{"dir"}, Destination: &exportDir, Value: "results", Usage: "export dir"}, + &cli.StringFlag{Name: "format", Aliases: []string{"f"}, Destination: &outputFormat, Value: "csv", Usage: "format, csv|json|console"}, + &cli.StringFlag{Name: "profile-dir-path", Aliases: []string{"p"}, Destination: &customProfilePath, Value: "", Usage: "custom profile dir path, get with chrome://version"}, + &cli.StringFlag{Name: "key-file-path", Aliases: []string{"k"}, Destination: &customKeyPath, Value: "", Usage: "custom key file path"}, }, HideHelpCommand: true, Action: func(c *cli.Context) error { + var ( + browsers []core.Browser + err error + ) if verbose { log.InitLog("debug") } else { log.InitLog("error") } - // default select all browsers - browsers, err := core.PickBrowser(browser) - if err != nil { - log.Error(err) + if customProfilePath != "" { + browsers, err = core.PickCustomBrowser(browserName, customProfilePath, customKeyPath) + if err != nil { + log.Error(err) + } + } else { + // default select all browsers + browsers, err = core.PickBrowser(browserName) + if err != nil { + log.Error(err) + } } err = utils.MakeDir(exportDir) if err != nil { diff --git a/core/browser.go b/core/browser.go index 08991d7..57caf60 100644 --- a/core/browser.go +++ b/core/browser.go @@ -3,6 +3,7 @@ package core import ( "errors" "fmt" + "os" "path/filepath" "strings" @@ -49,10 +50,10 @@ const ( ) var ( - errItemNotSupported = errors.New(`item not supported, default is "all", choose from history|password|bookmark|cookie`) - errBrowserNotSupported = errors.New("browser not supported") - errChromeSecretIsEmpty = errors.New("chrome secret is empty") - errDbusSecretIsEmpty = errors.New("dbus secret key is empty") + errItemNotSupported = errors.New(`item not supported, default is "all", choose from history|password|bookmark|cookie`) + errBrowserNotSupported = errors.New("browser not supported") + errChromeSecretIsEmpty = errors.New("chrome secret is empty") + errDbusSecretIsEmpty = errors.New("dbus secret key is empty") ) var ( @@ -170,7 +171,7 @@ func NewFirefox(profile, key, name, storage string) (Browser, error) { return &Firefox{profilePath: profile, keyPath: key, name: name}, nil } -// +// GetAllItems return all item with firefox func (f *Firefox) GetAllItems() ([]data.Item, error) { var items []data.Item for item, choice := range firefoxItems { @@ -259,8 +260,43 @@ func PickBrowser(name string) ([]Browser, error) { return nil, errBrowserNotSupported } +// PickCustomBrowser pick single browser with custom browser profile path and key file path (windows only). +// If custom key file path is empty, but the current browser requires key file (chromium for windows version > 80) +// key file path will be automatically found in the profile path's parent directory. +func PickCustomBrowser(browserName, cusProfile, cusKey string) ([]Browser, error) { + var ( + browsers []Browser + ) + browserName = strings.ToLower(browserName) + supportBrowser := strings.Join(ListBrowser(), "|") + if browserName == "all" { + return nil, fmt.Errorf("can't select all browser, pick one from %s with -b flag\n", supportBrowser) + } + if choice, ok := browserList[browserName]; ok { + // if this browser need key path + if choice.KeyPath != "" { + var err error + // if browser need key path and cusKey is empty, try to get key path with profile dir + if cusKey == "" { + cusKey, err = getKeyPath(cusProfile) + if err != nil { + return nil, err + } + } + } + if err := checkKeyPath(cusKey); err != nil { + return nil, err + } + b, err := choice.New(cusProfile, cusKey, choice.Name, choice.Storage) + browsers = append(browsers, b) + return browsers, err + } else { + return nil, fmt.Errorf("%s not support, pick one from %s with -b flag\n", browserName, supportBrowser) + } +} + func getItemPath(profilePath, file string) (string, error) { - p, err := filepath.Glob(profilePath + file) + p, err := filepath.Glob(filepath.Join(profilePath, file)) if err != nil { return "", err } @@ -270,6 +306,41 @@ func getItemPath(profilePath, file string) (string, error) { return "", fmt.Errorf("find %s failed", file) } +// getKeyPath try get key file path with browser's profile path +// default key file path is in the parent directory of the profile dir, and name is [Local State] +func getKeyPath(profilePath string) (string, error) { + if _, err := os.Stat(filepath.Clean(profilePath)); os.IsNotExist(err) { + return "", err + } + parentDir := getParentDirectory(profilePath) + keyPath := filepath.Join(parentDir, "Local State") + return keyPath, nil +} + +// check key file path is exist +func checkKeyPath(keyPath string) error { + if _, err := os.Stat(keyPath); os.IsNotExist(err) { + return fmt.Errorf("secret key path not exist, please check %s", keyPath) + } + return nil +} + +func getParentDirectory(dir string) string { + var ( + length int + ) + // filepath.Clean(dir) auto remove + dir = strings.ReplaceAll(filepath.Clean(dir), `\`, `/`) + length = strings.LastIndex(dir, "/") + if length > 0 { + if length > len([]rune(dir)) { + length = len([]rune(dir)) + } + return string([]rune(dir)[:length]) + } + return "" +} + func ListBrowser() []string { var l []string for k := range browserList { diff --git a/core/browser_windows.go b/core/browser_windows.go index eb1bbec..ff55833 100644 --- a/core/browser_windows.go +++ b/core/browser_windows.go @@ -3,6 +3,7 @@ package core import ( "encoding/base64" "errors" + "fmt" "os" "hack-browser-data/core/decrypt" @@ -109,6 +110,9 @@ func (c *Chromium) InitSecretKey() error { if c.keyPath == "" { return nil } + if _, err := os.Stat(c.keyPath); os.IsNotExist(err) { + return fmt.Errorf("%s secret key path is empty", c.name) + } keyFile, err := utils.ReadFile(c.keyPath) if err != nil { return err