diff --git a/README.md b/README.md index 4528d72..1f070ce 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,37 @@ hack-browser-data is an open-source tool that could help you export data from br ### Supported Browser +#### Windows | Browser | Password | Cookie | Bookmark | History | | :---------------------------------- | :------: | :----: | :------: | :-----: | -| Chrome <= 80 [Windows] | ✅ | ✅ | ✅ | ✅ | -| Chrome > 80 [Windows] | ✅ | ✅ | ✅ | ✅ | -| Chrome [MacOS]
(require password) | ✅ | ✅ | ✅ | ✅ | -| Edge [Windows] | ✅ | ✅ | ✅ | ✅ | -| Edge [MacOS]
(require password) | ✅ | ✅ | ✅ | ✅ | -| 360 Speed Browser [Windows] | ✅ | ✅ | ✅ | ✅ | -| QQ Browser [Windows] | ✅ | ✅ | ✅ | ✅ | -| FireFox [MacOS] | ✅ | ✅ | ✅ | ✅ | -| FireFox [Windows] | ❌ | ❌ | ❌ | ❌ | -| Safari [MacOS] | ❌ | ❌ | ❌ | ❌ | -| Internet Explorer [Windows] | ❌ | ❌ | ❌ | ❌ | -| 360 Secure Browser [Windows] | ❌ | ❌ | ❌ | ❌ | -| Chrome [Linux] | ❌ | ❌ | ❌ | ❌ | +| Google Chrome (Full Version) | ✅ | ✅ | ✅ | ✅ | +| Firefox | ✅ | ✅ | ✅ | ✅ | +| Microsoft Edge | ✅ | ✅ | ✅ | ✅ | +| 360 Speed Browser | ✅ | ✅ | ✅ | ✅ | +| QQ Browser | ✅ | ✅ | ✅ | ✅ | +| Internet Explorer | ❌ | ❌ | ❌ | ❌ | + +#### MacOS + +Because of the security policies, all those browsers require a password. + +| Browser | Password | Cookie | Bookmark | History | +| :---------------------------------- | :------: | :----: | :------: | :-----: | +| Google Chrome | ✅ | ✅ | ✅ | ✅ | +| Firefox | ✅ | ✅ | ✅ | ✅ | +| Microsoft Edge | ✅ | ✅ | ✅ | ✅ | +| Safari | ❌ | ❌ | ❌ | ❌ | + +#### Linux + +These browsers will be supported in the future. + +| Browser | Password | Cookie | Bookmark | History | +| :---------------------------------- | :------: | :----: | :------: | :-----: | +| Firefox | ❌ | ❌ | ❌ | ❌ | +| Google Chrome | ❌ | ❌ | ❌ | ❌ | +| Microsoft Edge | ❌ | ❌ | ❌ | ❌ | + ### Install diff --git a/cmd/cmd.go b/cmd/cmd.go index f7727f2..33a791a 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "hack-browser-data/core" "hack-browser-data/log" "hack-browser-data/utils" @@ -41,8 +40,6 @@ func Execute() { } else { log.InitLog("error") } - - utils.MakeDir(exportDir) browserDir, key, err := utils.PickBrowser(browser) if err != nil { log.Fatal(err, " Available browsers: "+strings.Join(utils.ListBrowser(), "|")) @@ -65,7 +62,7 @@ func Execute() { dst := filepath.Base(v) err := utils.CopyDB(v, dst) if err != nil { - log.Println(err) + log.Debug(err) continue } core.ParseResult(dst) @@ -74,16 +71,16 @@ func Execute() { fileList := utils.GetDBPath(browserDir, utils.FirefoxLoginData, utils.FirefoxKey4DB, utils.FirefoxCookie, utils.FirefoxData) for _, v := range fileList { dst := filepath.Base(v) - fmt.Println(dst) err := utils.CopyDB(v, dst) if err != nil { - log.Println(err) + log.Debug(err) continue } core.ParseResult(dst) } } core.FullData.Sorted() + utils.MakeDir(exportDir) if outputFormat == "json" { err := core.FullData.OutPutJson(exportDir, browser, outputFormat) if err != nil { diff --git a/core/common.go b/core/common.go index c12835f..dc33409 100644 --- a/core/common.go +++ b/core/common.go @@ -2,17 +2,13 @@ package core import ( "bytes" - "crypto/cipher" - "crypto/des" "crypto/hmac" "crypto/sha1" "database/sql" - "encoding/asn1" "encoding/base64" "encoding/hex" "hack-browser-data/log" "hack-browser-data/utils" - "io" "io/ioutil" "os" "time" @@ -106,7 +102,7 @@ func parseBookmarks() { bookmarks, err := utils.ReadFile(utils.Bookmarks) defer os.Remove(utils.Bookmarks) if err != nil { - log.Println(err) + log.Debug(err) } r := gjson.Parse(bookmarks) if r.Exists() { @@ -128,17 +124,17 @@ func parseLogin() { defer os.Remove(utils.LoginData) defer func() { if err := loginDB.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() if err != nil { - log.Println(err) + log.Debug(err) } err = loginDB.Ping() rows, err := loginDB.Query(queryLogin) defer func() { if err := rows.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() for rows.Next() { @@ -166,7 +162,7 @@ func parseLogin() { login.Password = password if err != nil { - log.Println(err) + log.Debug(err) } loginItemList = append(loginItemList, login) } @@ -182,17 +178,17 @@ func parseCookie() { defer os.Remove(utils.Cookies) defer func() { if err := cookieDB.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() if err != nil { - log.Println(err) + log.Debug(err) } err = cookieDB.Ping() rows, err := cookieDB.Query(queryCookie) defer func() { if err := rows.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() for rows.Next() { @@ -241,17 +237,17 @@ func parseHistory() { defer os.Remove(utils.History) defer func() { if err := historyDB.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() if err != nil { - log.Println(err) + log.Debug(err) } err = historyDB.Ping() rows, err := historyDB.Query(queryHistory) defer func() { if err := rows.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() for rows.Next() { @@ -268,7 +264,7 @@ func parseHistory() { LastVisitTime: utils.TimeEpochFormat(lastVisitTime), } if err != nil { - log.Println(err) + log.Debug(err) continue } historyList = append(historyList, h) @@ -375,179 +371,128 @@ func parseFirefoxData() { } var queryPassword = `SELECT item1, item2 FROM metaData WHERE id = 'password'` +var queryNssPrivate = `SELECT a11, a102 from nssPrivate` -func checkKey1() (b [][]byte) { +func GetDecryptKey() (b [][]byte) { var ( - err error - keyDB *sql.DB - rows *sql.Rows + 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) } }() - if err != nil { - log.Println(err) + log.Debug(err) } err = keyDB.Ping() - rows, err = keyDB.Query(queryPassword) + pwdRows, err = keyDB.Query(queryPassword) defer func() { - if err := rows.Close(); err != nil { - log.Println(err) + if err := pwdRows.Close(); err != nil { + log.Debug(err) } }() - for rows.Next() { + for pwdRows.Next() { var ( item1, item2 []byte ) - if err := rows.Scan(&item1, &item2); err != nil { + if err := pwdRows.Scan(&item1, &item2); err != nil { log.Error(err) continue } b = append(b, item1, item2) } - return b -} - -var queryNssPrivate = `SELECT a11, a102 from nssPrivate;` - -func checkA102() (b [][]byte) { - keyDB, err := sql.Open("sqlite3", utils.FirefoxKey4DB) - - defer func() { - if err := os.Remove(utils.FirefoxKey4DB); err != nil { - log.Error(err) - } - }() - defer func(closer io.Closer) { - if err := keyDB.Close(); err != nil { - log.Error(err) - } - }(keyDB) - if err != nil { log.Error(err) } - rows, err := keyDB.Query(queryNssPrivate) + nssRows, err = keyDB.Query(queryNssPrivate) defer func() { - if err := rows.Close(); err != nil { - log.Println(err) + if err := nssRows.Close(); err != nil { + log.Debug(err) } }() - for rows.Next() { + for nssRows.Next() { var ( a11, a102 []byte ) - if err := rows.Scan(&a11, &a102); err != nil { - log.Println(err) + if err := nssRows.Scan(&a11, &a102); err != nil { + log.Debug(err) } b = append(b, a11, a102) } return b } -/* -ASN1 PBE Structures -SEQUENCE (2 elem) - SEQUENCE (2 elem) - OBJECT IDENTIFIER - SEQUENCE (2 elem) - OCTET STRING (20 byte) - INTEGER 1 - OCTET STRING (16 byte) -*/ - -type PBEAlgorithms struct { - SequenceA - CipherText []byte -} - -type SequenceA struct { - DecryptMethod asn1.ObjectIdentifier - SequenceB -} - -type SequenceB struct { - EntrySalt []byte - Len int -} - -/* -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 PasswordAsn1 struct { - CipherText []byte - SequenceIV - Encrypted []byte -} - -type SequenceIV struct { - asn1.ObjectIdentifier - Iv []byte -} - -func decryptPBE(decodeItem []byte) (pbe PBEAlgorithms) { - _, err := asn1.Unmarshal(decodeItem, &pbe) - if err != nil { - log.Error(err) - } - return -} - func parseFirefoxKey4() { - h1 := checkKey1() + h1 := GetDecryptKey() globalSalt := h1[0] decodedItem := h1[1] + a11 := h1[2] + a102 := h1[3] keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - pbe := decryptPBE(decodedItem) - m := checkPassword(globalSalt, pbe.EntrySalt, pbe.CipherText) - var finallyKey []byte + pbe, err := utils.DecodeMeta(decodedItem) + if err != nil { + log.Error("decrypt meta data failed", err) + return + } + var masterPwd []byte + m, err := checkPassword(globalSalt, masterPwd, pbe) + if err != nil { + log.Error("decrypt firefox failed", err) + return + } if bytes.Contains(m, []byte("password-check")) { - log.Println("password-check success") - h2 := checkA102() - a11 := h2[0] - a102 := h2[1] + log.Debugf("password-check success") m := bytes.Compare(a102, keyLin) if m == 0 { - pbe2 := decryptPBE(a11) + pbe2, err := utils.DecodeMeta(a11) + if err != nil { + log.Error(err) + return + } log.Debugf("decrypt asn1 pbe success") - finallyKey = checkPassword(globalSalt, pbe2.EntrySalt, pbe2.CipherText)[:24] + finallyKey, err := checkPassword(globalSalt, masterPwd, pbe2) + finallyKey = finallyKey[:24] + if err != nil { + log.Error(err) + return + } 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) + if err != nil { + log.Error(err) + return + } + p, err := utils.Des3Decrypt(finallyKey, pwd.Iv, pwd.Encrypted) + if err != nil { + log.Error(err) + return + } + FullData.LoginDataSlice = append(FullData.LoginDataSlice, loginData{ + LoginUrl: v.LoginUrl, + UserName: string(u), + Password: string(p), + CreateDate: v.CreateDate, + }) + } } } - allLogins := GetLoginData() - for _, v := range allLogins { - log.Warn(hex.EncodeToString(v.encryptUser)) - s1 := decryptLogin(v.encryptUser) - log.Warn(hex.EncodeToString(v.encryptPass)) - s2 := decryptLogin(v.encryptPass) - log.Println(s1, s1.CipherText, s1.Encrypted, s1.Iv) - block, err := des.NewTripleDESCipher(finallyKey) - if err != nil { - log.Println(err) - } - blockMode := cipher.NewCBCDecrypter(block, s1.Iv) - sq := make([]byte, len(s1.Encrypted)) - blockMode.CryptBlocks(sq, s1.Encrypted) - blockMode2 := cipher.NewCBCDecrypter(block, s2.Iv) - sq2 := make([]byte, len(s2.Encrypted)) - blockMode2.CryptBlocks(sq2, s2.Encrypted) - FullData.LoginDataSlice = append(FullData.LoginDataSlice, loginData{ - LoginUrl: v.LoginUrl, - UserName: string(utils.PKCS5UnPadding(sq)), - Password: string(utils.PKCS5UnPadding(sq2)), - CreateDate: v.CreateDate, - }) - } } var queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies` @@ -559,17 +504,17 @@ func parseFirefoxCookie() { defer os.Remove(utils.FirefoxCookie) defer func() { if err := cookieDB.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() if err != nil { - log.Println(err) + log.Debug(err) } err = cookieDB.Ping() rows, err := cookieDB.Query(queryFirefoxCookie) defer func() { if err := rows.Close(); err != nil { - log.Println(err) + log.Debug(err) } }() for rows.Next() { @@ -600,7 +545,7 @@ func parseFirefoxCookie() { } -func checkPassword(globalSalt, entrySalt, encryptText []byte) []byte { +func checkPassword(globalSalt, masterPwd []byte, pbe utils.MetaPBE) ([]byte, error) { //byte[] GLMP; // GlobalSalt + MasterPassword //byte[] HP; // SHA1(GLMP) //byte[] HPES; // HP + EntrySalt @@ -611,51 +556,24 @@ func checkPassword(globalSalt, entrySalt, encryptText []byte) []byte { //byte[] tk; //byte[] k2; //byte[] k; // final value conytaining key and iv - sha1.New() - hp := sha1.Sum(globalSalt) - log.Warn(hex.EncodeToString(hp[:])) - log.Println(len(entrySalt)) - s := append(hp[:], entrySalt...) - log.Warn(hex.EncodeToString(s)) + glmp := append(globalSalt, masterPwd...) + hp := sha1.Sum(glmp) + s := append(hp[:], pbe.EntrySalt...) chp := sha1.Sum(s) - log.Warn(hex.EncodeToString(chp[:])) - pes := paddingZero(entrySalt, 20) + pes := utils.PaddingZero(pbe.EntrySalt, 20) tk := hmac.New(sha1.New, chp[:]) tk.Write(pes) - pes = append(pes, entrySalt...) - log.Warn(hex.EncodeToString(pes)) + pes = append(pes, pbe.EntrySalt...) k1 := hmac.New(sha1.New, chp[:]) k1.Write(pes) - log.Warn(hex.EncodeToString(k1.Sum(nil))) - log.Warn(hex.EncodeToString(tk.Sum(nil))) - tkPlus := append(tk.Sum(nil), entrySalt...) + tkPlus := append(tk.Sum(nil), pbe.EntrySalt...) k2 := hmac.New(sha1.New, chp[:]) k2.Write(tkPlus) - log.Warn(hex.EncodeToString(k2.Sum(nil))) k := append(k1.Sum(nil), k2.Sum(nil)...) iv := k[len(k)-8:] key := k[:24] log.Warn("key=", hex.EncodeToString(key), "iv=", hex.EncodeToString(iv)) - block, err := des.NewTripleDESCipher(key) - if err != nil { - log.Println(err) - } - blockMode := cipher.NewCBCDecrypter(block, iv) - sq := make([]byte, len(encryptText)) - blockMode.CryptBlocks(sq, encryptText) - return sq -} - -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 - } + return utils.Des3Decrypt(key, iv, pbe.Encrypted) } func GetLoginData() (l []loginData) { @@ -676,7 +594,7 @@ func GetLoginData() (l []loginData) { u, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String()) m.encryptUser = u if err != nil { - log.Println(err) + log.Debug(err) } p, err = base64.StdEncoding.DecodeString(v.Get("encryptedPassword").String()) m.encryptPass = p @@ -686,11 +604,3 @@ func GetLoginData() (l []loginData) { } return } - -func decryptLogin(s []byte) (pbe PasswordAsn1) { - _, err := asn1.Unmarshal(s, &pbe) - if err != nil { - log.Println(err) - } - return pbe -} diff --git a/core/output.go b/core/output.go index b727c71..150d88b 100644 --- a/core/output.go +++ b/core/output.go @@ -101,7 +101,7 @@ func (b BrowserData) OutPutJson(dir, browser, format string) error { enc.SetIndent("", "\t") err = enc.Encode(b.CookieMap) if err != nil { - log.Println(err) + log.Debug(err) } file.Write(w.Bytes()) fmt.Printf("%s Get %d cookies, filename is %s \n", log.Prefix, len(b.CookieMap), filename) @@ -119,7 +119,7 @@ func (b BrowserData) OutPutJson(dir, browser, format string) error { enc.SetIndent("", "\t") err = enc.Encode(b.HistorySlice) if err != nil { - log.Println(err) + log.Debug(err) } file.Write(w.Bytes()) fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(b.HistorySlice), filename) @@ -137,7 +137,7 @@ func (b BrowserData) OutPutJson(dir, browser, format string) error { enc.SetIndent("", "\t") err = enc.Encode(b.LoginDataSlice) if err != nil { - log.Println(err) + log.Debug(err) } file.Write(w.Bytes()) fmt.Printf("%s Get %d login data, filename is %s \n", log.Prefix, len(b.LoginDataSlice), filename) diff --git a/utils/utils.go b/utils/utils.go index 61dd3ee..5b1be48 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,9 @@ package utils import ( + "crypto/cipher" + "crypto/des" + "encoding/asn1" "errors" "fmt" "hack-browser-data/log" @@ -84,11 +87,11 @@ func CopyDB(src, dst string) error { } sourceFile, err := ioutil.ReadFile(src) if err != nil { - log.Println(err.Error()) + log.Debug(err.Error()) } err = ioutil.WriteFile(dst, sourceFile, 0777) if err != nil { - log.Println(err.Error()) + log.Debug(err.Error()) } return err } @@ -164,7 +167,7 @@ func MakeDir(dirName string) { } } -func paddingZero(s []byte, l int) []byte { +func PaddingZero(s []byte, l int) []byte { h := l - len(s) if h <= 0 { return s @@ -180,4 +183,44 @@ func PKCS5UnPadding(src []byte) []byte { length := len(src) unpadding := int(src[length-1]) return src[:(length - unpadding)] -} \ No newline at end of file +} + +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 +} + +/* +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/utils/utils_darwin.go b/utils/utils_darwin.go index 9755e97..8dba0fc 100644 --- a/utils/utils_darwin.go +++ b/utils/utils_darwin.go @@ -5,6 +5,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/sha1" + "encoding/asn1" "errors" "hack-browser-data/log" "os/exec" @@ -68,12 +69,12 @@ func InitKey(key string) error { cmd.Stderr = &stderr err := cmd.Run() if err != nil { - log.Println(err) + log.Error(err) return err } if stderr.Len() > 0 { err = errors.New(stderr.String()) - log.Println(err) + log.Error(err) } temp := stdout.Bytes() chromePass := temp[:len(temp)-1] @@ -110,5 +111,36 @@ func aes128CBCDecrypt(encryptPass []byte) (string, error) { return string(dst), nil } +/* +SEQUENCE (2 elem) + SEQUENCE (2 elem) + OBJECT IDENTIFIER + SEQUENCE (2 elem) + OCTET STRING (20 byte) + INTEGER 1 + OCTET STRING (16 byte) +*/ +type MetaPBE struct { + SequenceA + Encrypted []byte +} + +type SequenceA struct { + DecryptMethod asn1.ObjectIdentifier + SequenceB +} + +type SequenceB struct { + EntrySalt []byte + Len int +} +func DecodeMeta(decodeItem []byte) (pbe MetaPBE, err error) { + _, err = asn1.Unmarshal(decodeItem, &pbe) + if err != nil { + log.Error(err) + return + } + return +}