github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/samreg_test.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 "errors" 19 "runtime" 20 "strings" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/osv-scalibr/common/windows/registry" 25 "github.com/google/osv-scalibr/testing/mockregistry" 26 ) 27 28 func TestNewFromFile(t *testing.T) { 29 tests := []struct { 30 name string 31 filepath string 32 onGOOS string 33 wantErr bool 34 wantErrText string 35 }{ 36 { 37 name: "file_does_not_exist_returns_error_on_windows", 38 filepath: "/some/path/that/does/not/exist", 39 onGOOS: "windows", 40 wantErr: true, 41 wantErrText: "The system cannot find the path specified", 42 }, 43 { 44 name: "file_does_not_exist_returns_error_on_linux", 45 filepath: "/some/path/that/does/not/exist", 46 onGOOS: "linux", 47 wantErr: true, 48 wantErrText: "no such file or directory", 49 }, 50 { 51 name: "file_not_a_registry_returns_error_on_windows", 52 filepath: "C:\\Windows\\System32\\cmd.exe", 53 onGOOS: "windows", 54 wantErr: true, 55 wantErrText: "File does not have registry magic.", 56 }, 57 { 58 name: "file_not_a_registry_returns_error_on_linux", 59 filepath: "/dev/zero", 60 onGOOS: "linux", 61 wantErr: true, 62 wantErrText: "File does not have registry magic.", 63 }, 64 } 65 66 for _, tc := range tests { 67 t.Run(tc.name, func(t *testing.T) { 68 if tc.onGOOS != runtime.GOOS { 69 t.Skipf("Skipping test %q for %q", tc.name, tc.onGOOS) 70 } 71 72 _, err := NewFromFile(tc.filepath) 73 if (err != nil) != tc.wantErr { 74 t.Errorf("NewFromFile(%q) unexpected error: %v", tc.filepath, err) 75 } 76 77 if tc.wantErr { 78 if !strings.Contains(err.Error(), tc.wantErrText) { 79 t.Errorf("NewFromFile(%q) unexpected error, got: %v, want: %v", tc.filepath, err, tc.wantErrText) 80 } 81 82 return 83 } 84 }) 85 } 86 } 87 88 func TestUserRIDs(t *testing.T) { 89 tests := []struct { 90 name string 91 registry *mockregistry.MockRegistry 92 want []string 93 wantErr error 94 }{ 95 { 96 name: "list_users_from_SAM_succeeds", 97 registry: &mockregistry.MockRegistry{ 98 Keys: map[string]registry.Key{ 99 `SAM\Domains\Account\Users`: &mockregistry.MockKey{ 100 KSubkeys: []registry.Key{ 101 &mockregistry.MockKey{ 102 KName: "Names", 103 }, 104 &mockregistry.MockKey{ 105 KName: "000003E9", 106 }, 107 &mockregistry.MockKey{ 108 KName: "000001F4", 109 }, 110 &mockregistry.MockKey{ 111 KName: "000003EA", 112 }, 113 }, 114 }, 115 }, 116 }, 117 want: []string{"000003E9", "000001F4", "000003EA"}, 118 }, 119 { 120 name: "no_users_in_SAM_returns_empty_list", 121 registry: &mockregistry.MockRegistry{ 122 Keys: map[string]registry.Key{ 123 `SAM\Domains\Account\Users`: &mockregistry.MockKey{ 124 KSubkeys: []registry.Key{ 125 &mockregistry.MockKey{ 126 KName: "Names", 127 }, 128 }, 129 }, 130 }, 131 }, 132 want: []string{}, 133 }, 134 { 135 name: "missing_user_key_returns_error", 136 registry: &mockregistry.MockRegistry{}, 137 wantErr: errFailedToParseUsers, 138 }, 139 } 140 141 for _, tc := range tests { 142 t.Run(tc.name, func(t *testing.T) { 143 sam := SAMRegistry{tc.registry} 144 145 got, err := sam.UsersRIDs() 146 if !errors.Is(err, tc.wantErr) { 147 t.Fatalf("UserRIDs() returned an unexpected error: %v", err) 148 } 149 150 if tc.wantErr != nil { 151 return 152 } 153 154 if diff := cmp.Diff(tc.want, got); diff != "" { 155 t.Errorf("UserRIDs() returned an unexpected diff (-want +got): %v", diff) 156 } 157 }) 158 } 159 } 160 161 func TestUserInfo(t *testing.T) { 162 tests := []struct { 163 name string 164 registry *mockregistry.MockRegistry 165 rid string 166 wantErr bool 167 wantErrText string 168 }{ 169 { 170 name: "user_info_structure_parses_correctly", 171 rid: "000001F4", 172 registry: &mockregistry.MockRegistry{ 173 Keys: map[string]registry.Key{ 174 `SAM\Domains\Account\Users\000001F4`: &mockregistry.MockKey{ 175 KValues: []registry.Value{ 176 &mockregistry.MockValue{ 177 VName: "V", 178 VData: []byte(strings.Repeat("\x00", 0xCC)), 179 }, 180 &mockregistry.MockValue{ 181 VName: "F", 182 VData: []byte(""), 183 }, 184 }, 185 }, 186 }, 187 }, 188 wantErr: false, 189 }, 190 { 191 name: "user_specific_key_missing_returns_error", 192 rid: "000001F4", 193 registry: &mockregistry.MockRegistry{}, 194 wantErr: true, 195 wantErrText: "SAM hive: failed to load user registry for RID", 196 }, 197 { 198 name: "missing_v_structure_returns_error", 199 rid: "000001F4", 200 registry: &mockregistry.MockRegistry{ 201 Keys: map[string]registry.Key{ 202 `SAM\Domains\Account\Users\000001F4`: &mockregistry.MockKey{ 203 KValues: []registry.Value{ 204 &mockregistry.MockValue{ 205 VName: "F", 206 VData: []byte(""), 207 }, 208 }, 209 }, 210 }, 211 }, 212 wantErr: true, 213 wantErrText: "SAM hive: failed to find V or F structures for RID", 214 }, 215 { 216 name: "V_structure_parse_failure_returns_error", 217 rid: "000001F4", 218 registry: &mockregistry.MockRegistry{ 219 Keys: map[string]registry.Key{ 220 `SAM\Domains\Account\Users\000001F4`: &mockregistry.MockKey{ 221 KValues: []registry.Value{ 222 &mockregistry.MockValue{ 223 VName: "V", 224 VData: []byte("\x00"), 225 }, 226 &mockregistry.MockValue{ 227 VName: "F", 228 VData: []byte(""), 229 }, 230 }, 231 }, 232 }, 233 }, 234 wantErr: true, 235 wantErrText: "unexpected EOF", 236 }, 237 { 238 name: "missing_F_structure_returns_error", 239 rid: "000001F4", 240 registry: &mockregistry.MockRegistry{ 241 Keys: map[string]registry.Key{ 242 `SAM\Domains\Account\Users\000001F4`: &mockregistry.MockKey{ 243 KValues: []registry.Value{ 244 &mockregistry.MockValue{ 245 VName: "V", 246 VData: []byte(strings.Repeat("\x00", 0xCC)), 247 }, 248 }, 249 }, 250 }, 251 }, 252 wantErr: true, 253 wantErrText: "SAM hive: failed to find V or F structures for RID", 254 }, 255 } 256 257 for _, tc := range tests { 258 t.Run(tc.name, func(t *testing.T) { 259 sam := SAMRegistry{tc.registry} 260 261 _, err := sam.UserInfo(tc.rid) 262 if (err != nil) != tc.wantErr { 263 t.Fatalf("UserInfo(%q) returned an unexpected error: %v", tc.rid, err) 264 } 265 266 if tc.wantErr { 267 if !strings.Contains(err.Error(), tc.wantErrText) { 268 t.Errorf("UserInfo(%q) unexpected error, got: %v, want: %v", tc.rid, err, tc.wantErrText) 269 } 270 271 return 272 } 273 }) 274 } 275 } 276 277 func TestDeriveSyskey(t *testing.T) { 278 tests := []struct { 279 name string 280 registry *mockregistry.MockRegistry 281 syskey []byte 282 want []byte 283 wantErr error 284 }{ 285 { 286 name: "syskey_derivation_succeeds", 287 registry: &mockregistry.MockRegistry{ 288 Keys: map[string]registry.Key{ 289 `SAM\Domains\Account`: &mockregistry.MockKey{ 290 KValues: []registry.Value{ 291 &mockregistry.MockValue{ 292 VName: "F", 293 VData: []byte("\x02\x00\x01\x00\x00\x00\x00\x00\x40\x15\x3b\x97\x46\x9f\xce\x01\x26\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa6\x0a\xff\xde\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\xcc\x1d\xcf\xfb\xff\xff\xff\x00\xcc\x1d\xcf\xfb\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x38\x00\x00\x00\x23\x7e\xe9\x12\xa7\x34\xbf\x93\x18\x6e\xaa\xc1\x83\x07\x59\xa1\xd6\x96\xa6\x99\x6b\xa9\x41\x61\x44\x92\xb0\xfb\xd0\x0a\xe9\xa6\x37\xd6\x7c\xc6\x99\x2b\xc7\x12\xfe\x22\xa0\x17\x71\xce\xd3\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x38\x00\x00\x00\x3d\xfe\xe0\xd7\x20\xeb\x39\xc1\x44\x1c\x8d\x05\x29\xd6\x83\x47\x92\xa2\x29\x38\xfc\x9e\xa7\x29\xa9\x36\x7d\x4a\xfc\x6c\xe1\xb3\xd3\xac\xd4\xac\xe2\x5b\xab\xf9\xf8\x3f\x09\xe1\x91\x1a\x7d\xda\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"), 294 }, 295 }, 296 }, 297 }, 298 }, 299 syskey: []byte("\x88\x93\xae\x93\x45\x13\xbd\xdd\x25\x47\x35\x16\x3e\x9d\x33\x00"), 300 want: []byte("\x3d\x21\x2c\xe8\xa2\xda\x83\x43\xbd\xad\x1e\xf2\xcf\xb6\xb3\x1c"), 301 }, 302 { 303 name: "missing_domain_key_returns_error", 304 registry: &mockregistry.MockRegistry{ 305 Keys: map[string]registry.Key{}, 306 }, 307 wantErr: errFailedToOpenDomain, 308 }, 309 { 310 name: "missing_domainF_structure_returns_error", 311 registry: &mockregistry.MockRegistry{ 312 Keys: map[string]registry.Key{ 313 `SAM\Domains\Account`: &mockregistry.MockKey{ 314 KValues: []registry.Value{}, 315 }, 316 }, 317 }, 318 wantErr: errFailedToParseDomainF, 319 }, 320 { 321 name: "error_from_derivation_propagates", 322 registry: &mockregistry.MockRegistry{ 323 Keys: map[string]registry.Key{ 324 `SAM\Domains\Account`: &mockregistry.MockKey{ 325 KValues: []registry.Value{ 326 &mockregistry.MockValue{ 327 VName: "F", 328 VData: []byte(""), 329 }, 330 }, 331 }, 332 }, 333 }, 334 wantErr: errDomainFTooShort, 335 }, 336 } 337 338 for _, tc := range tests { 339 t.Run(tc.name, func(t *testing.T) { 340 sam := SAMRegistry{tc.registry} 341 342 got, err := sam.DeriveSyskey(tc.syskey) 343 if !errors.Is(err, tc.wantErr) { 344 t.Errorf("DeriveSyskey(%x) unexpected error, got: %v, want: %v", tc.syskey, err, tc.wantErr) 345 } 346 347 if tc.wantErr != nil { 348 return 349 } 350 351 if diff := cmp.Diff(tc.want, got); diff != "" { 352 t.Errorf("DeriveSyskey(%x) returned an unexpected diff (-want +got): %v", tc.syskey, diff) 353 } 354 }) 355 } 356 }