github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/userinfo.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 "encoding/hex" 19 "errors" 20 "slices" 21 ) 22 23 // UserInfo contains the information about a user in the SAM hive. 24 type UserInfo struct { 25 rid string 26 userV *userV 27 userF *userF 28 } 29 30 // Username returns the username of the user. 31 func (s *UserInfo) Username() (string, error) { 32 return s.userV.Username() 33 } 34 35 // Enabled returns whether the user is enabled or not. 36 func (s *UserInfo) Enabled() (bool, error) { 37 return s.userF.Enabled() 38 } 39 40 func (s *UserInfo) handleRC4Hash(syskey, lmData, ntData, ridBytes []byte) ([]byte, []byte, error) { 41 var ntHash, lmHash []byte 42 var err error 43 44 if len(ntData) != 20 && len(lmData) != 20 { 45 return nil, nil, errNoHashInfoFound 46 } 47 48 if len(ntData) == 20 { 49 ntHash, err = decryptRC4Hash(ridBytes, syskey, ntData[4:], []byte(NTLMHashConstant)) 50 if err != nil { 51 return nil, nil, err 52 } 53 } 54 55 if len(lmData) == 20 { 56 lmHash, err = decryptRC4Hash(ridBytes, syskey, lmData[4:], []byte(LMHashConstant)) 57 if err != nil { 58 return nil, nil, err 59 } 60 } 61 62 return lmHash, ntHash, nil 63 } 64 65 func (s *UserInfo) handleAESHash(syskey, lmData, ntData, ridBytes []byte) ([]byte, []byte, error) { 66 var ntHash, lmHash []byte 67 var err error 68 69 if len(ntData) <= 24 && len(lmData) <= 24 { 70 return nil, nil, errNoHashInfoFound 71 } 72 73 if len(ntData) > 24 { 74 ntHash, err = decryptAESHash(ridBytes, syskey, ntData[24:], ntData[8:24]) 75 if err != nil { 76 return nil, nil, err 77 } 78 } 79 80 if len(lmData) > 24 { 81 lmHash, err = decryptAESHash(ridBytes, syskey, lmData[24:], lmData[8:24]) 82 if err != nil { 83 return nil, nil, err 84 } 85 } 86 87 return lmHash, ntHash, nil 88 } 89 90 func (s *UserInfo) decryptHashes(syskey, lmData, ntData []byte) ([]byte, []byte, error) { 91 ridBytes, err := hex.DecodeString(s.rid) 92 if err != nil { 93 return nil, nil, err 94 } 95 96 // note: reversing the slice to get the right endianness 97 slices.Reverse(ridBytes) 98 99 if len(ntData) < 3 { 100 return nil, nil, errors.New("failed to decrypt hashes: NT data is too short") 101 } 102 103 // If the third-byte is set to 0x1, the hashes are encrypted using RC4. 104 if ntData[2] == 0x1 { 105 return s.handleRC4Hash(syskey, lmData, ntData, ridBytes) 106 } 107 108 return s.handleAESHash(syskey, lmData, ntData, ridBytes) 109 } 110 111 // Hashes returns the LM and NT hashes of the user. 112 // Note that the syskey is expected to be already derived. 113 func (s *UserInfo) Hashes(syskey []byte) ([]byte, []byte, error) { 114 lmHash, ntHash, err := s.userV.EncryptedHashes() 115 if err != nil { 116 return nil, nil, err 117 } 118 119 return s.decryptHashes(syskey, lmHash, ntHash) 120 }