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 }