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  }