Skip to content

Commit 76e2615

Browse files
authored
refactor(windows): clean up Chrome ABE module (#574)
* refactor(abe): remove --abe-key flag and its global state * refactor(abe): rework scratch protocol and Go/C structure
1 parent c3d30b9 commit 76e2615

26 files changed

Lines changed: 1149 additions & 344 deletions

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ linters:
122122
- G117 # struct field matches secret pattern — false positive on Password fields
123123
- G204 # exec.Command with variable — required for macOS `security` command
124124
- G304 # file inclusion via variable — required for dynamic browser paths
125+
- G703 # path traversal via taint analysis — same false-positive class as G304 (gosec 2.22+ / golangci-lint 2.11+)
125126
- G401 # weak crypto SHA1 — required for Chromium PBKDF2 key derivation
126127
- G402 # TLS MinVersion — not applicable (no TLS in this tool)
127128
- G405 # weak crypto DES — required for Firefox 3DES decryption

CLAUDE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,24 @@ go mod tidy
4242
go mod verify
4343
```
4444

45+
## Chrome ABE Payload (Windows only)
46+
47+
Chrome 127+ cookies (v20) decrypt via a C payload reflectively injected into `chrome.exe`. Sources in `crypto/windows/abe_native/`; design in [RFC-010](rfcs/010-chrome-abe-integration.md).
48+
49+
```bash
50+
make payload # build the DLL (needs zig: brew install zig)
51+
make build-windows # cross-compile hack-browser-data.exe with payload embedded
52+
make gen-layout # regenerate Go layout constants from bootstrap_layout.h
53+
make payload-clean # rm crypto/*.bin
54+
```
55+
56+
- Default `go build` links a stub that errors at runtime when ABE is needed — contributors not touching Windows ABE need no zig.
57+
- `-tags abe_embed` embeds the payload via `//go:embed` (see `crypto/abe_embed_windows.go` / `crypto/abe_stub_windows.go`).
58+
4559
## Code Conventions
4660

4761
- **Platform code**: use build tags (`_darwin.go`, `_windows.go`, `_linux.go`)
62+
- **C payload**: all sources in `crypto/windows/abe_native/` are first-party (no vendored C). See RFC-010 §9.2 for why Stephen Fewer's reflective loader was rejected.
4863
- **Error handling**: `fmt.Errorf("context: %w", err)` for wrapping, never `_ =` to ignore errors
4964
- **Logging**: `log.Debugf` for record-level diagnostics, `log.Infof` for user-facing progress/status, `log.Warnf` for unexpected conditions. Extract methods should return errors, not log them.
5065
- **Naming**: follow Go conventions — `Config` not `BrowserConfig`, `Extract` not `BrowsingData`

browser/chromium/decrypt.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ 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-
return crypto.DecryptChromium(masterKey, ciphertext)
23+
// v20 is cross-platform AES-GCM; routed through a dedicated function so
24+
// Linux/macOS CI can exercise the same decryption path as Windows.
25+
return crypto.DecryptChromiumV20(masterKey, ciphertext)
2426
case crypto.CipherDPAPI:
2527
return crypto.DecryptDPAPI(ciphertext)
2628
default:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package chromium
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/moond4rk/hackbrowserdata/crypto"
10+
)
11+
12+
// TestDecryptValue_V20 is cross-platform because v20's ciphertext format
13+
// (AES-GCM with 12-byte nonce) is platform-independent; only the key source
14+
// (Chrome ABE on Windows) differs by OS. Running on Linux/macOS CI protects
15+
// the routing in decryptValue + crypto.DecryptChromiumV20 from regressions.
16+
func TestDecryptValue_V20(t *testing.T) {
17+
plaintext := []byte("v20_test_value")
18+
nonce := []byte("v20_nonce_12") // 12-byte AES-GCM nonce
19+
20+
gcm, err := crypto.AESGCMEncrypt(testAESKey, nonce, plaintext)
21+
require.NoError(t, err)
22+
23+
// v20 layout: "v20" (3B) + nonce (12B) + ciphertext+tag
24+
ciphertext := append([]byte("v20"), append(nonce, gcm...)...)
25+
26+
got, err := decryptValue(testAESKey, ciphertext)
27+
require.NoError(t, err)
28+
assert.Equal(t, plaintext, got)
29+
}
30+
31+
func TestDecryptValue_V20_ShortCiphertext(t *testing.T) {
32+
// Missing nonce (prefix only) must error, not panic.
33+
_, err := decryptValue(testAESKey, []byte("v20"))
34+
require.Error(t, err)
35+
}

cmd/hack-browser-data/dump.go

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

1010
"github.com/moond4rk/hackbrowserdata/browser"
11-
"github.com/moond4rk/hackbrowserdata/crypto"
1211
"github.com/moond4rk/hackbrowserdata/log"
1312
"github.com/moond4rk/hackbrowserdata/output"
1413
"github.com/moond4rk/hackbrowserdata/types"
@@ -23,7 +22,6 @@ func dumpCmd() *cobra.Command {
2322
outputDir string
2423
profilePath string
2524
keychainPw string
26-
abeKey string
2725
compress bool
2826
)
2927

@@ -36,12 +34,6 @@ func dumpCmd() *cobra.Command {
3634
hack-browser-data dump -f cookie-editor
3735
hack-browser-data dump --zip`,
3836
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-
4537
browsers, err := browser.PickBrowsers(browser.PickOptions{
4638
Name: browserName,
4739
ProfilePath: profilePath,
@@ -94,9 +86,6 @@ func dumpCmd() *cobra.Command {
9486
cmd.Flags().StringVarP(&outputDir, "dir", "d", "results", "output directory")
9587
cmd.Flags().StringVarP(&profilePath, "profile-path", "p", "", "custom profile dir path, get with chrome://version")
9688
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.")
10089
cmd.Flags().BoolVar(&compress, "zip", false, "compress output to zip")
10190

10291
return cmd

crypto/abe_embed_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
//go:embed abe_extractor_amd64.bin
1313
var abePayloadAmd64 []byte
1414

15-
func getPayloadForArch(arch string) ([]byte, error) {
15+
func ABEPayload(arch string) ([]byte, error) {
1616
switch arch {
1717
case "amd64":
1818
if len(abePayloadAmd64) == 0 {

crypto/abe_stub_other.go

Lines changed: 0 additions & 7 deletions
This file was deleted.

crypto/abe_stub_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package crypto
44

55
import "fmt"
66

7-
func getPayloadForArch(arch string) ([]byte, error) {
7+
func ABEPayload(arch string) ([]byte, error) {
88
return nil, fmt.Errorf(
99
"abe: payload not embedded in this build (rebuild with -tags abe_embed; arch=%s)",
1010
arch,

crypto/abe_windows.go

Lines changed: 0 additions & 46 deletions
This file was deleted.

crypto/crypto.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@ func DES3Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
4545
return cbcDecrypt(block, iv, ciphertext)
4646
}
4747

48+
// gcmNonceSize is the AES-GCM standard nonce size used by Chromium's v10/v20
49+
// cipher formats. Cross-platform because the v20 ciphertext layout is the
50+
// same regardless of host OS (only Windows currently produces v20).
51+
const gcmNonceSize = 12
52+
53+
// DecryptChromiumV20 decrypts a Chromium v20 (App-Bound Encryption) ciphertext.
54+
// Format: "v20" prefix (3B) + nonce (12B) + AES-GCM(payload + 16B tag).
55+
//
56+
// Cross-platform: v20 is only produced by Chrome on Windows today, but the
57+
// decryption math is platform-neutral. Keeping it here rather than in
58+
// crypto_windows.go ensures the routing in browser/chromium/decrypt.go stays
59+
// testable on Linux/macOS CI.
60+
func DecryptChromiumV20(key, ciphertext []byte) ([]byte, error) {
61+
if len(ciphertext) < versionPrefixLen+gcmNonceSize {
62+
return nil, errShortCiphertext
63+
}
64+
nonce := ciphertext[versionPrefixLen : versionPrefixLen+gcmNonceSize]
65+
payload := ciphertext[versionPrefixLen+gcmNonceSize:]
66+
return AESGCMDecrypt(key, nonce, payload)
67+
}
68+
4869
// AESGCMEncrypt encrypts data using AES-GCM mode.
4970
func AESGCMEncrypt(key, nonce, plaintext []byte) ([]byte, error) {
5071
block, err := aes.NewCipher(key)

0 commit comments

Comments
 (0)