github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/samreg.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 provides a wrapper around the SAM registry. 16 package samreg 17 18 import ( 19 "errors" 20 "fmt" 21 22 "github.com/google/osv-scalibr/common/windows/registry" 23 ) 24 25 const ( 26 samRegistryPathUsers = `SAM\Domains\Account\Users` 27 samRegistryPathDomains = `SAM\Domains\Account` 28 ) 29 30 var ( 31 errFailedToParseUsers = errors.New("SAM hive: failed to parse users") 32 errFailedToParseDomainF = errors.New("SAM hive: failed to parse domain F structure") 33 errFailedToOpenDomain = errors.New("SAM hive: failed to open the account domain registry") 34 ) 35 36 // SAMRegistry is a wrapper around a loaded SAM registry. 37 type SAMRegistry struct { 38 registry.Registry 39 } 40 41 // NewFromFile creates a new SAMRegistry from a file. 42 // Note that it is the responsibility of the caller to close the registry once it is no longer 43 // needed. 44 func NewFromFile(path string) (*SAMRegistry, error) { 45 opener := registry.NewOfflineOpener(path) 46 reg, err := opener.Open() 47 if err != nil { 48 return nil, err 49 } 50 51 return &SAMRegistry{reg}, nil 52 } 53 54 // UsersRIDs returns the list of local user RIDs. 55 func (s *SAMRegistry) UsersRIDs() ([]string, error) { 56 key, err := s.OpenKey("HKLM", samRegistryPathUsers) 57 if err != nil { 58 return nil, errFailedToParseUsers 59 } 60 61 users := []string{} 62 subkeys, err := key.Subkeys() 63 if err != nil { 64 return nil, err 65 } 66 67 for _, subkey := range subkeys { 68 if subkey.Name() == "Names" { 69 continue 70 } 71 72 users = append(users, subkey.Name()) 73 } 74 75 return users, nil 76 } 77 78 // UserInfo returns the UserInfo for a given user RID. 79 func (s *SAMRegistry) UserInfo(userRID string) (*UserInfo, error) { 80 keyPath := fmt.Sprintf(`%s\%s`, samRegistryPathUsers, userRID) 81 key, err := s.OpenKey("HKLM", keyPath) 82 if err != nil { 83 return nil, fmt.Errorf("SAM hive: failed to load user registry for RID %q", userRID) 84 } 85 86 values, err := key.Values() 87 if err != nil { 88 return nil, err 89 } 90 91 var userV *userV 92 var userF *userF 93 for _, value := range values { 94 if userV != nil && userF != nil { 95 break 96 } 97 98 if value.Name() == "V" { 99 data, err := value.Data() 100 if err != nil { 101 return nil, err 102 } 103 104 if userV, err = newUserV(data, userRID); err != nil { 105 return nil, err 106 } 107 } 108 109 if value.Name() == "F" { 110 data, err := value.Data() 111 if err != nil { 112 return nil, err 113 } 114 115 userF = newUserF(data, userRID) 116 } 117 } 118 119 if userV == nil || userF == nil { 120 return nil, fmt.Errorf("SAM hive: failed to find V or F structures for RID %q", userRID) 121 } 122 123 return &UserInfo{ 124 rid: userRID, 125 userV: userV, 126 userF: userF, 127 }, nil 128 } 129 130 // DeriveSyskey loads the domain F structure from the SAM hive and then uses it to derive the 131 // syskey. 132 func (s *SAMRegistry) DeriveSyskey(syskey []byte) ([]byte, error) { 133 key, err := s.OpenKey("HKLM", samRegistryPathDomains) 134 if err != nil { 135 return nil, errFailedToOpenDomain 136 } 137 138 values, err := key.Values() 139 if err != nil { 140 return nil, err 141 } 142 143 for _, value := range values { 144 if value.Name() == "F" { 145 data, err := value.Data() 146 if err != nil { 147 return nil, err 148 } 149 150 return newDomainF(data).DeriveSyskey(syskey) 151 } 152 } 153 154 return nil, errFailedToParseDomainF 155 }