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  }