github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/systemreg/systemreg.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 systemreg provides a wrapper around the SYSTEM registry.
    16  package systemreg
    17  
    18  import (
    19  	"encoding/hex"
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/google/osv-scalibr/common/windows/registry"
    25  	"golang.org/x/text/encoding/unicode"
    26  )
    27  
    28  var (
    29  	syskeyPaths = []string{"JD", "Skew1", "GBG", "Data"}
    30  
    31  	errNoCurrentControlSet = errors.New("system hive: failed to find CurrentControlSet")
    32  )
    33  
    34  // SystemRegistry is a wrapper around a SYSTEM registry.
    35  type SystemRegistry struct {
    36  	registry.Registry
    37  }
    38  
    39  // NewFromFile creates a new SystemRegistry from a file.
    40  // Note that it is the responsibility of the caller to close the registry once it is no longer
    41  // needed.
    42  func NewFromFile(path string) (*SystemRegistry, error) {
    43  	opener := registry.NewOfflineOpener(path)
    44  	reg, err := opener.Open()
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	return &SystemRegistry{reg}, nil
    50  }
    51  
    52  // Syskey returns the syskey used to decrypt user hashes.
    53  // The syskey is stored as UTF16-le encoded hexadecimal in the class name of the 4 registry keys
    54  // denoted by `syskeyPaths`. Once the hexadecimal is decoded, the result is still obfuscated and
    55  // the order of the bytes needs to be swapped using the indexes detonated in the `transforms` table.
    56  func (s *SystemRegistry) Syskey() ([]byte, error) {
    57  	currentSet, err := s.currentControlSet()
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	var syskey strings.Builder
    63  	currentControlSet := fmt.Sprintf(`ControlSet%03d\Control\Lsa\`, currentSet)
    64  	for _, k := range syskeyPaths {
    65  		key, err := s.OpenKey("HKLM", currentControlSet+k)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  
    70  		class, err := key.ClassName()
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  
    75  		syskey.Write(class)
    76  	}
    77  
    78  	decodedKey, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder().String(syskey.String())
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	unhexKey, err := hex.DecodeString(decodedKey)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	transforms := []int{8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7}
    89  	var resultKey []byte
    90  
    91  	for i := range unhexKey {
    92  		resultKey = append(resultKey, unhexKey[transforms[i]])
    93  	}
    94  
    95  	return resultKey, nil
    96  }
    97  
    98  func (s *SystemRegistry) currentControlSet() (uint32, error) {
    99  	key, err := s.OpenKey("HKLM", `Select`)
   100  	if err != nil {
   101  		return 0, err
   102  	}
   103  
   104  	values, err := key.Values()
   105  	if err != nil {
   106  		return 0, err
   107  	}
   108  
   109  	for _, value := range values {
   110  		if value.Name() == "Current" {
   111  			data, err := value.Data()
   112  			if err != nil {
   113  				return 0, err
   114  			}
   115  
   116  			return uint32(data[0]), nil
   117  		}
   118  	}
   119  
   120  	return 0, errNoCurrentControlSet
   121  }