mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-07-04 21:37:47 +02:00
feat(restore): cross-platform restore via dump engine rebuild (#606)
Restore previously required the dump's origin OS, overlaying keys onto locally-discovered browsers. It now rebuilds Chromium engines from the dump's vaults (v2 adds engine kind), so copied data or an archive zip decrypts on any OS.
This commit is contained in:
@@ -4,39 +4,47 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/browser"
|
||||
"github.com/moond4rk/hackbrowserdata/log"
|
||||
"github.com/moond4rk/hackbrowserdata/masterkey"
|
||||
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
|
||||
)
|
||||
|
||||
func restoreCmd() *cobra.Command {
|
||||
var (
|
||||
keysPath string
|
||||
dataDir string
|
||||
dataZip string
|
||||
browserName string
|
||||
category string
|
||||
outputFormat string
|
||||
outputDir string
|
||||
profilePath string
|
||||
compress bool
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "restore",
|
||||
Short: "Decrypt a copied profile using exported master keys",
|
||||
Example: ` hack-browser-data restore -i keys.json -b chrome -p /path/to/copied/User\ Data
|
||||
hack-browser-data restore -i keys.json -b edge -p /path -c cookie -f csv
|
||||
ssh origin "hack-browser-data dumpkeys" | hack-browser-data restore -i - -b chrome -p /path`,
|
||||
Short: "Decrypt copied profile data using exported master keys",
|
||||
Example: ` hack-browser-data restore --keys keys.json --data-zip data.zip
|
||||
hack-browser-data restore --keys keys.json --data-dir ./data -b chrome -c cookie
|
||||
hack-browser-data restore --keys keys.json --data-dir ./chrome-userdata -b chrome
|
||||
ssh origin "hack-browser-data dumpkeys" | hack-browser-data restore --keys - --data-zip data.zip`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
browsers, err := loadAndApplyKeys(browserName, profilePath, keysPath)
|
||||
resolvedDir, cleanup, err := resolveDataDir(dataDir, dataZip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
browsers, err := loadRestoreBrowsers(keysPath, resolvedDir, browserName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(browsers) == 0 {
|
||||
log.Warnf("no browsers found")
|
||||
log.Warnf("no browsers to restore from the supplied keys and data")
|
||||
return nil
|
||||
}
|
||||
categories, err := parseCategories(category)
|
||||
@@ -47,31 +55,27 @@ func restoreCmd() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&keysPath, "input", "i", "", "input keys file (use - for stdin)")
|
||||
cmd.Flags().StringVarP(&browserName, "browser", "b", "", "target browser (single, required): "+browser.Names())
|
||||
cmd.Flags().StringVar(&keysPath, "keys", "", "keys file from dumpkeys (use - for stdin)")
|
||||
cmd.Flags().StringVar(&dataDir, "data-dir", "", "copied profile data dir (archive layout, or one browser's User Data with -b)")
|
||||
cmd.Flags().StringVar(&dataZip, "data-zip", "", "archive zip from `archive` (alternative to --data-dir)")
|
||||
cmd.Flags().StringVarP(&browserName, "browser", "b", "", "restore only this browser (optional; must match a vault in --keys)")
|
||||
cmd.Flags().StringVarP(&category, "category", "c", "all", "data categories (comma-separated): all|"+categoryNames())
|
||||
cmd.Flags().StringVarP(&outputFormat, "format", "f", "json", "output format: csv|json|cookie-editor")
|
||||
cmd.Flags().StringVarP(&outputDir, "dir", "d", "results", "output directory")
|
||||
cmd.Flags().StringVarP(&profilePath, "profile-path", "p", "", "copied profile dir path (required)")
|
||||
cmd.Flags().BoolVar(&compress, "zip", false, "compress output to zip")
|
||||
|
||||
_ = cmd.MarkFlagRequired("input")
|
||||
_ = cmd.MarkFlagRequired("browser")
|
||||
_ = cmd.MarkFlagRequired("profile-path")
|
||||
_ = cmd.MarkFlagRequired("keys")
|
||||
cmd.MarkFlagsMutuallyExclusive("data-dir", "data-zip")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func loadAndApplyKeys(browserName, profilePath, keysPath string) ([]browser.Browser, error) {
|
||||
if profilePath == "" {
|
||||
return nil, fmt.Errorf("requires -p <copied-profile-dir>")
|
||||
}
|
||||
name := strings.ToLower(browserName)
|
||||
if name == "" || name == "all" {
|
||||
return nil, fmt.Errorf(`requires -b <browser> (single, not "all")`)
|
||||
}
|
||||
func loadRestoreBrowsers(keysPath, dataDir, browserName string) ([]browser.Browser, error) {
|
||||
if keysPath == "" {
|
||||
return nil, fmt.Errorf("requires -i <keys-file> (or - for stdin)")
|
||||
return nil, fmt.Errorf("requires --keys <file> (or - for stdin)")
|
||||
}
|
||||
if dataDir == "" {
|
||||
return nil, fmt.Errorf("requires --data-dir <dir>")
|
||||
}
|
||||
|
||||
var r io.Reader = os.Stdin
|
||||
@@ -88,22 +92,32 @@ func loadAndApplyKeys(browserName, profilePath, keysPath string) ([]browser.Brow
|
||||
return nil, fmt.Errorf("read keys file %q: %w", keysPath, err)
|
||||
}
|
||||
|
||||
browsers, err := browser.DiscoverBrowsers(browser.DiscoverOptions{
|
||||
Name: browserName,
|
||||
ProfilePath: profilePath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
browser.ApplyDump(browsers, dump)
|
||||
|
||||
for _, b := range browsers {
|
||||
if _, ok := b.(browser.KeychainPasswordReceiver); ok {
|
||||
log.Infof("Safari has no portable master key; run `dump -b safari` separately for full extraction")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return browsers, nil
|
||||
return browser.BuildFromDump(dump, dataDir, browserName)
|
||||
}
|
||||
|
||||
// resolveDataDir returns the directory restore reads from: --data-dir as-is, or --data-zip extracted
|
||||
// into a temp dir (removed by the returned cleanup). Exactly one of the two must be set.
|
||||
func resolveDataDir(dataDir, dataZip string) (string, func(), error) {
|
||||
noop := func() {}
|
||||
if (dataDir == "") == (dataZip == "") {
|
||||
return "", noop, fmt.Errorf("exactly one of --data-dir or --data-zip is required")
|
||||
}
|
||||
if dataDir != "" {
|
||||
return dataDir, noop, nil
|
||||
}
|
||||
tmp, err := os.MkdirTemp("", "hbd-restore-*")
|
||||
if err != nil {
|
||||
return "", noop, fmt.Errorf("create temp dir: %w", err)
|
||||
}
|
||||
if err := fileutil.Unzip(dataZip, tmp); err != nil {
|
||||
removeTempDir(tmp)
|
||||
return "", noop, fmt.Errorf("extract %s: %w", dataZip, err)
|
||||
}
|
||||
return tmp, func() { removeTempDir(tmp) }, nil
|
||||
}
|
||||
|
||||
func removeTempDir(dir string) {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
log.Warnf("restore: remove temp dir %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user