Skip to content

Commit c3d30b9

Browse files
authored
feat(windows): Chrome App-Bound Encryption implementation (#573)
* build(abe): add zig-cc payload build system + C reflective loader * feat(abe): add reflective injector and Go ABE key-retriever primitives * feat(abe): wire ABERetriever into DefaultRetriever chain + --abe-key CLI * feat(abe): route Chromium v20 ciphertext through AES-GCM with ABE key
1 parent eb58ebb commit c3d30b9

24 files changed

Lines changed: 1481 additions & 14 deletions

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
!go.mod
1212
!go.sum
1313

14+
# === Native (C) source for embedded ABE payload ===
15+
# Only source files are tracked; compiled binaries (*.bin) are
16+
# intentionally ignored and rebuilt by CI via `make payload`.
17+
!*.c
18+
!*.h
19+
!Makefile
20+
!Makefile.frag
21+
1422
# === Project root config ===
1523
!.gitattributes
1624
!.gitignore

Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
GO ?= go
2+
GOEXE ?= hack-browser-data
3+
4+
include crypto/windows/abe_native/Makefile.frag
5+
6+
.PHONY: build build-windows clean
7+
8+
build:
9+
$(GO) build -o $(GOEXE) ./cmd/hack-browser-data
10+
11+
build-windows: $(ABE_BIN)
12+
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
13+
$(GO) build -tags abe_embed -trimpath -ldflags="-s -w" \
14+
-o $(GOEXE).exe ./cmd/hack-browser-data
15+
16+
clean: payload-clean
17+
rm -f $(GOEXE) $(GOEXE).exe

browser/browser_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ func platformBrowsers() []types.BrowserConfig {
1313
Key: "chrome",
1414
Name: chromeName,
1515
Kind: types.Chromium,
16+
Storage: "chrome",
1617
UserDataDir: homeDir + "/AppData/Local/Google/Chrome/User Data",
1718
},
1819
{
1920
Key: "edge",
2021
Name: edgeName,
2122
Kind: types.Chromium,
23+
Storage: "edge",
2224
UserDataDir: homeDir + "/AppData/Local/Microsoft/Edge/User Data",
2325
},
2426
{
@@ -31,6 +33,7 @@ func platformBrowsers() []types.BrowserConfig {
3133
Key: "chrome-beta",
3234
Name: chromeBetaName,
3335
Kind: types.Chromium,
36+
Storage: "chrome-beta",
3437
UserDataDir: homeDir + "/AppData/Local/Google/Chrome Beta/User Data",
3538
},
3639
{
@@ -55,12 +58,14 @@ func platformBrowsers() []types.BrowserConfig {
5558
Key: "coccoc",
5659
Name: coccocName,
5760
Kind: types.Chromium,
61+
Storage: "coccoc",
5862
UserDataDir: homeDir + "/AppData/Local/CocCoc/Browser/User Data",
5963
},
6064
{
6165
Key: "brave",
6266
Name: braveName,
6367
Kind: types.Chromium,
68+
Storage: "brave",
6469
UserDataDir: homeDir + "/AppData/Local/BraveSoftware/Brave-Browser/User Data",
6570
},
6671
{

browser/chromium/decrypt.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ func decryptValue(masterKey, ciphertext []byte) ([]byte, error) {
2020
// v11 is Linux-only and shares v10's AES-CBC path; only the key source differs.
2121
return crypto.DecryptChromium(masterKey, ciphertext)
2222
case crypto.CipherV20:
23-
// TODO: implement App-Bound Encryption (Chrome 127+)
24-
return nil, fmt.Errorf("v20 App-Bound Encryption not yet supported")
23+
return crypto.DecryptChromium(masterKey, ciphertext)
2524
case crypto.CipherDPAPI:
2625
return crypto.DecryptDPAPI(ciphertext)
2726
default:

browser/chromium/decrypt_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,3 @@ func TestDecryptValue_V11(t *testing.T) {
6363
require.NoError(t, err)
6464
assert.Equal(t, plaintext, got)
6565
}
66-
67-
func TestDecryptValue_V20(t *testing.T) {
68-
// v20 App-Bound Encryption is not yet implemented.
69-
// TODO: add successful decryption cases when implemented.
70-
ciphertext := append([]byte("v20"), make([]byte, 32)...)
71-
_, err := decryptValue(nil, ciphertext)
72-
require.Error(t, err)
73-
assert.Contains(t, err.Error(), "v20")
74-
}

cmd/hack-browser-data/dump.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/spf13/cobra"
99

1010
"github.com/moond4rk/hackbrowserdata/browser"
11+
"github.com/moond4rk/hackbrowserdata/crypto"
1112
"github.com/moond4rk/hackbrowserdata/log"
1213
"github.com/moond4rk/hackbrowserdata/output"
1314
"github.com/moond4rk/hackbrowserdata/types"
@@ -22,6 +23,7 @@ func dumpCmd() *cobra.Command {
2223
outputDir string
2324
profilePath string
2425
keychainPw string
26+
abeKey string
2527
compress bool
2628
)
2729

@@ -34,6 +36,12 @@ func dumpCmd() *cobra.Command {
3436
hack-browser-data dump -f cookie-editor
3537
hack-browser-data dump --zip`,
3638
RunE: func(cmd *cobra.Command, args []string) error {
39+
if abeKey != "" {
40+
if err := crypto.SetABEMasterKeyFromHex(abeKey); err != nil {
41+
return fmt.Errorf("--abe-key: %w", err)
42+
}
43+
}
44+
3745
browsers, err := browser.PickBrowsers(browser.PickOptions{
3846
Name: browserName,
3947
ProfilePath: profilePath,
@@ -86,6 +94,9 @@ func dumpCmd() *cobra.Command {
8694
cmd.Flags().StringVarP(&outputDir, "dir", "d", "results", "output directory")
8795
cmd.Flags().StringVarP(&profilePath, "profile-path", "p", "", "custom profile dir path, get with chrome://version")
8896
cmd.Flags().StringVar(&keychainPw, "keychain-pw", "", "macOS keychain password")
97+
cmd.Flags().StringVarP(&abeKey, "abe-key", "k", "",
98+
"Windows only: pre-decrypted Chrome ABE master key (64 hex chars / 32 bytes). "+
99+
"When set, skips the in-process elevation_service injection.")
89100
cmd.Flags().BoolVar(&compress, "zip", false, "compress output to zip")
90101

91102
return cmd

crypto/abe_embed_windows.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build windows && abe_embed
2+
3+
package crypto
4+
5+
import (
6+
_ "embed"
7+
"fmt"
8+
)
9+
10+
//go:generate make -C ../.. payload
11+
12+
//go:embed abe_extractor_amd64.bin
13+
var abePayloadAmd64 []byte
14+
15+
func getPayloadForArch(arch string) ([]byte, error) {
16+
switch arch {
17+
case "amd64":
18+
if len(abePayloadAmd64) == 0 {
19+
return nil, fmt.Errorf("abe: amd64 payload is empty (build system bug)")
20+
}
21+
return abePayloadAmd64, nil
22+
default:
23+
return nil, fmt.Errorf("abe: arch %q not supported in this build", arch)
24+
}
25+
}

crypto/abe_stub_other.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !windows
2+
3+
package crypto
4+
5+
func SetABEMasterKeyFromHex(_ string) error { return nil }
6+
7+
func GetABEMasterKey() []byte { return nil }

crypto/abe_stub_windows.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build windows && !abe_embed
2+
3+
package crypto
4+
5+
import "fmt"
6+
7+
func getPayloadForArch(arch string) ([]byte, error) {
8+
return nil, fmt.Errorf(
9+
"abe: payload not embedded in this build (rebuild with -tags abe_embed; arch=%s)",
10+
arch,
11+
)
12+
}

crypto/abe_windows.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//go:build windows
2+
3+
package crypto
4+
5+
import (
6+
"encoding/hex"
7+
"fmt"
8+
"sync"
9+
)
10+
11+
var (
12+
abeCLIKeyMu sync.RWMutex
13+
abeCLIKey []byte
14+
)
15+
16+
func SetABEMasterKeyFromHex(hexKey string) error {
17+
if hexKey == "" {
18+
return fmt.Errorf("abe: empty hex key")
19+
}
20+
b, err := hex.DecodeString(hexKey)
21+
if err != nil {
22+
return fmt.Errorf("abe: decode hex key: %w", err)
23+
}
24+
if len(b) != 32 {
25+
return fmt.Errorf("abe: key must be 32 bytes (got %d)", len(b))
26+
}
27+
abeCLIKeyMu.Lock()
28+
abeCLIKey = b
29+
abeCLIKeyMu.Unlock()
30+
return nil
31+
}
32+
33+
func GetABEMasterKey() []byte {
34+
abeCLIKeyMu.RLock()
35+
defer abeCLIKeyMu.RUnlock()
36+
if len(abeCLIKey) == 0 {
37+
return nil
38+
}
39+
out := make([]byte, len(abeCLIKey))
40+
copy(out, abeCLIKey)
41+
return out
42+
}
43+
44+
func ABEPayload(arch string) ([]byte, error) {
45+
return getPayloadForArch(arch)
46+
}

0 commit comments

Comments
 (0)