github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/domainf.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package samreg 16 17 import ( 18 "bytes" 19 "crypto/aes" 20 "crypto/cipher" 21 "crypto/md5" 22 "crypto/rc4" 23 "encoding/binary" 24 "errors" 25 "slices" 26 ) 27 28 const ( 29 fDomStructKeyOffset = 0x68 30 ) 31 32 var ( 33 errDomainFTooShort = errors.New("domain F structure is too short") 34 errInvalidChecksum = errors.New("error while deriving syskey: invalid checksum") 35 errInvalidRevision = errors.New("error while deriving syskey: invalid revision") 36 ) 37 38 // domainF is a lazy-parsed domain F structure containing the domain's information in the 39 // SAM hive. 40 // Very important: Do not confuse the SAMUSerF and SAMDomainF structures, the first one refers to 41 // each user's information (is account active, locked, when was password changed, etc.) while the 42 // second one refers to the domain's information (password policy, portion of the syskey). 43 // See https://web.archive.org/web/20190717124313/http://www.beginningtoseethelight.org/ntsecurity/index.htm#BB4F910C0FFA1E43 44 // for more information about the F domain structure. 45 type domainF struct { 46 buffer []byte 47 } 48 49 // newDomainF creates a new lazy-parsed domain F structure from the SAM hive. 50 func newDomainF(data []byte) *domainF { 51 return &domainF{ 52 buffer: data, 53 } 54 } 55 56 func (s *domainF) deriveSyskeyRC4(origSyskey []byte, fDomKeyPart []byte) ([]byte, error) { 57 keyData := samSyskeyData{} 58 if err := binary.Read(bytes.NewBuffer(fDomKeyPart), binary.LittleEndian, &keyData); err != nil { 59 return nil, err 60 } 61 62 qwerty := []byte("!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00") 63 digits := []byte("0123456789012345678901234567890123456789\x00") 64 rc4key := md5.Sum(slices.Concat(keyData.Salt[:], qwerty, origSyskey, digits)) 65 cipher, err := rc4.NewCipher(rc4key[:]) 66 if err != nil { 67 return nil, err 68 } 69 70 derivedKey := make([]byte, 32) 71 cipher.XORKeyStream(derivedKey, slices.Concat(keyData.Key[:], keyData.Checksum[:])) 72 checksum := md5.Sum(slices.Concat(derivedKey[:16], digits, derivedKey[:16], qwerty)) 73 if slices.Compare(checksum[:], derivedKey[16:]) != 0 { 74 return nil, errInvalidChecksum 75 } 76 77 return derivedKey[:16], nil 78 } 79 80 func (s *domainF) deriveSyskeyAES(origSyskey []byte, fDomKeyPart []byte) ([]byte, error) { 81 keyData := samSyskeyDataAES{} 82 if err := binary.Read(bytes.NewBuffer(fDomKeyPart), binary.LittleEndian, &keyData); err != nil { 83 return nil, err 84 } 85 86 dataOffset := 0x20 87 dataLimit := int(keyData.DataLength) + dataOffset 88 if len(fDomKeyPart) < dataLimit { 89 return nil, errDomainFTooShort 90 } 91 92 data := fDomKeyPart[dataOffset:dataLimit] 93 block, err := aes.NewCipher(origSyskey) 94 if err != nil { 95 return nil, err 96 } 97 98 mode := cipher.NewCBCDecrypter(block, keyData.Salt[:]) 99 derivedKey := make([]byte, keyData.DataLength) 100 mode.CryptBlocks(derivedKey, data) 101 return derivedKey[:16], nil 102 } 103 104 // DeriveSyskey derives the syskey (aka bootkey) that was retrieved in the SYSTEM registry hive 105 // using information from the domain F structure found in the SAM hive. 106 func (s *domainF) DeriveSyskey(origSyskey []byte) ([]byte, error) { 107 if len(s.buffer) < fDomStructKeyOffset+1 { 108 return nil, errDomainFTooShort 109 } 110 111 fDomKeyPart := s.buffer[fDomStructKeyOffset:] 112 113 // the first byte defines the encryption algorithm used 114 switch fDomKeyPart[0] { 115 case 1: 116 // the key was encrypted using RC4 117 return s.deriveSyskeyRC4(origSyskey, fDomKeyPart) 118 case 2: 119 // the key was encrypted using AES 120 return s.deriveSyskeyAES(origSyskey, fDomKeyPart) 121 default: 122 return nil, errInvalidRevision 123 } 124 } 125 126 type samSyskeyData struct { 127 Revision uint32 128 Length uint32 129 Salt [16]byte 130 Key [16]byte 131 Checksum [16]byte 132 } 133 134 type samSyskeyDataAES struct { 135 Revision uint32 136 Length uint32 137 ChecksumLength uint32 138 DataLength uint32 139 Salt [16]byte 140 }