github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/userv_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 "slices" 20 "strings" 21 "testing" 22 ) 23 24 func TestNewUserV(t *testing.T) { 25 tests := []struct { 26 name string 27 buffer []byte 28 wantErr bool 29 wantErrText string 30 }{ 31 { 32 name: "valid_userV_succeeds", 33 buffer: []byte(strings.Repeat("\x00", 0xCC)), 34 }, 35 { 36 name: "invalid_userV_returns_error", 37 buffer: []byte("A"), 38 wantErr: true, 39 wantErrText: "unexpected EOF", 40 }, 41 } 42 43 for _, tc := range tests { 44 t.Run(tc.name, func(t *testing.T) { 45 _, err := newUserV(tc.buffer, "irrelevant") 46 if (err != nil) != tc.wantErr { 47 t.Errorf("NewUserV(...): Unexpected error: %v", err) 48 } 49 50 if tc.wantErr { 51 if !strings.Contains(err.Error(), tc.wantErrText) { 52 t.Errorf("NewUserV(...): Invalid error: got: %v, want: %v", err, tc.wantErrText) 53 } 54 } 55 }) 56 } 57 } 58 59 func TestUserVRead(t *testing.T) { 60 tests := []struct { 61 name string 62 buffer []byte 63 offset uint32 64 size uint32 65 wantData []byte 66 wantErr error 67 }{ 68 { 69 name: "valid_userV_succeeds", 70 buffer: []byte(strings.Repeat("A", 0xCC) + "BCDE"), 71 offset: 0, 72 size: 4, 73 wantData: []byte("BCDE"), 74 }, 75 { 76 name: "out_of_bounds_read_returns_error", 77 buffer: []byte(strings.Repeat("A", 0xCC)), 78 offset: 100, 79 size: 10, 80 wantErr: errReadOutOfBounds, 81 }, 82 } 83 84 for _, tc := range tests { 85 t.Run(tc.name, func(t *testing.T) { 86 userV, err := newUserV(tc.buffer, "irrelevant") 87 if err != nil { 88 t.Fatalf("Failed to create userV for tests: %v", err) 89 } 90 91 got, gotErr := userV.read(tc.offset, tc.size) 92 if !errors.Is(gotErr, tc.wantErr) { 93 t.Errorf("Read(): got error: %v, want: %v", gotErr, tc.wantErr) 94 } 95 96 if tc.wantErr != nil { 97 return 98 } 99 100 if !slices.Equal(got, tc.wantData) { 101 t.Errorf("Read(): got %v, want %v", got, tc.wantData) 102 } 103 }) 104 } 105 } 106 107 func TestUserVUsername(t *testing.T) { 108 tests := []struct { 109 name string 110 buffer []byte 111 wantUsername string 112 wantErr bool 113 wantErrText string 114 }{ 115 { 116 name: "valid_structure_returns_username", 117 buffer: []byte(strings.Repeat("\x00", 16) + "\x08" + strings.Repeat("\x00", 187) + "\x42\x00\x43\x00\x44\x00\x45\x00"), 118 wantUsername: "BCDE", 119 }, 120 { 121 name: "out_of_bounds_read_returns_error", 122 buffer: []byte(strings.Repeat("\xFF", 0xCC)), 123 wantErr: true, 124 wantErrText: errReadOutOfBounds.Error(), 125 }, 126 } 127 128 for _, tc := range tests { 129 t.Run(tc.name, func(t *testing.T) { 130 userV, err := newUserV(tc.buffer, "irrelevant") 131 if err != nil { 132 t.Fatalf("Failed to create userV for tests: %v", err) 133 } 134 135 got, gotErr := userV.Username() 136 if (gotErr != nil) != tc.wantErr { 137 t.Errorf("Username(): unexpected error: %v", gotErr) 138 } 139 140 if tc.wantErr { 141 if !strings.Contains(gotErr.Error(), tc.wantErrText) { 142 t.Errorf("Username(): got error: %v, want: %v", gotErr, tc.wantErrText) 143 } 144 145 return 146 } 147 148 if strings.Compare(got, tc.wantUsername) != 0 { 149 t.Errorf("Username(): got %v, want %v", got, tc.wantUsername) 150 } 151 }) 152 } 153 } 154 155 func TestUserVEncryptedHashes(t *testing.T) { 156 tests := []struct { 157 name string 158 buffer []byte 159 wantLM []byte 160 wantNT []byte 161 wantErr error 162 }{ 163 { 164 name: "valid_structure_returns_hashes", 165 buffer: []byte("\x00\x00\x00\x00\xd4\x00\x00\x00\x02\x00\x01\x00\xd4\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\xfc\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x14\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x14\x80\xb4\x00\x00\x00\xc4\x00\x00\x00\x14\x00\x00\x00\x44\x00\x00\x00\x02\x00\x30\x00\x02\x00\x00\x00\x02\xc0\x14\x00\x44\x00\x05\x01\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x02\xc0\x14\x00\xff\xff\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x07\x00\x00\x00\x02\x00\x70\x00\x04\x00\x00\x00\x00\x00\x14\x00\x5b\x03\x02\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x18\x00\xff\x07\x0f\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00\x00\x00\x18\x00\xff\x07\x0f\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x24\x02\x00\x00\x00\x00\x24\x00\x44\x00\x02\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xee\x9d\xd5\xc6\x43\xed\x3f\x53\x2e\xc8\x5e\x0a\xe9\x03\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00\x6c\x00\x6d\x00\x75\x00\x73\x00\x65\x00\x72\x00\x01\x02\x00\x00\x07\x00\x00\x00\x03\x00\x01\x00\x0c\x4b\x53\xdf\x15\xed\xf9\xeb\x41\x05\xfe\xca\x48\x02\x70\x55\x03\x00\x01\x00\x72\x10\xad\x76\x18\xf1\x1f\x38\x49\x24\x6d\x13\x38\x15\x8b\x03\x03\x00\x01\x00\x03\x00\x01\x00"), 166 wantLM: []byte("\x03\x00\x01\x00\x0c\x4b\x53\xdf\x15\xed\xf9\xeb\x41\x05\xfe\xca\x48\x02\x70\x55"), 167 wantNT: []byte("\x03\x00\x01\x00\x72\x10\xad\x76\x18\xf1\x1f\x38\x49\x24\x6d\x13\x38\x15\x8b\x03"), 168 }, 169 { 170 name: "when_only_NTLM_returns_only_NTLM", 171 buffer: []byte("\x00\x00\x00\x00\xbc\x00\x00\x00\x02\x00\x01\x00\xbc\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\xd8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd8\x00\x00\x00\x6c\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x01\x00\x00\x15\x00\x00\x00\xa8\x00\x00\x00\x5c\x01\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x64\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x68\x01\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x7c\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x14\x80\x9c\x00\x00\x00\xac\x00\x00\x00\x14\x00\x00\x00\x44\x00\x00\x00\x02\x00\x30\x00\x02\x00\x00\x00\x02\xc0\x14\x00\x44\x00\x05\x01\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x02\xc0\x14\x00\xff\xff\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x07\x00\x00\x00\x02\x00\x58\x00\x03\x00\x00\x00\x00\x00\x14\x00\x5b\x03\x02\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x18\x00\xff\x07\x0f\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00\x00\x00\x24\x00\x44\x00\x02\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xee\x9d\xd5\xc6\x43\xed\x3f\x53\x2e\xc8\x5e\x0a\xf4\x01\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00\x41\x00\x64\x00\x6d\x00\x69\x00\x6e\x00\x69\x00\x73\x00\x74\x00\x72\x00\x61\x00\x74\x00\x6f\x00\x72\x00\x64\x00\x42\x00\x75\x00\x69\x00\x6c\x00\x74\x00\x2d\x00\x69\x00\x6e\x00\x20\x00\x61\x00\x63\x00\x63\x00\x6f\x00\x75\x00\x6e\x00\x74\x00\x20\x00\x66\x00\x6f\x00\x72\x00\x20\x00\x61\x00\x64\x00\x6d\x00\x69\x00\x6e\x00\x69\x00\x73\x00\x74\x00\x65\x00\x72\x00\x69\x00\x6e\x00\x67\x00\x20\x00\x74\x00\x68\x00\x65\x00\x20\x00\x63\x00\x6f\x00\x6d\x00\x70\x00\x75\x00\x74\x00\x65\x00\x72\x00\x2f\x00\x64\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x01\x00\x01\x02\x00\x00\x07\x00\x00\x00\x03\x00\x01\x00\x03\x00\x01\x00\xed\x92\x87\x92\x78\x3b\x69\x2c\x21\x37\x49\xbc\xdb\xe3\x1a\xf5\x03\x00\x01\x00\x03\x00\x01\x00"), 172 wantLM: []byte("\x03\x00\x01\x00"), 173 wantNT: []byte("\x03\x00\x01\x00\xed\x92\x87\x92\x78\x3b\x69\x2c\x21\x37\x49\xbc\xdb\xe3\x1a\xf5"), 174 }, 175 { 176 name: "missing_hash_info_returns_error", 177 buffer: []byte(strings.Repeat("\x00", 0xCC)), 178 wantErr: errNoHashInfoFound, 179 }, 180 { 181 name: "out_of_bounds_LM_read_returns_error", 182 buffer: []byte(strings.Repeat("\x00", 0x9C) + strings.Repeat("\xFF", 8) + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00" + strings.Repeat("\x00", 0xCC)), 183 wantErr: errReadOutOfBounds, 184 }, 185 { 186 name: "out_of_bounds_NT_read_returns_error", 187 buffer: []byte(strings.Repeat("\xFF", 0xCC)), 188 wantErr: errReadOutOfBounds, 189 }, 190 } 191 192 for _, tc := range tests { 193 t.Run(tc.name, func(t *testing.T) { 194 userV, err := newUserV(tc.buffer, "irrelevant") 195 if err != nil { 196 t.Fatalf("Failed to create userV for tests: %v", err) 197 } 198 199 gotLM, gotNT, gotErr := userV.EncryptedHashes() 200 if !errors.Is(gotErr, tc.wantErr) { 201 t.Errorf("EncryptedHashes(): unexpected error, got: %v, want: %v", gotErr, tc.wantErr) 202 } 203 204 if tc.wantErr != nil { 205 return 206 } 207 208 if !slices.Equal(gotLM, tc.wantLM) { 209 t.Errorf("EncryptedHashes(): got LM hash: %v, want: %v", gotLM, tc.wantLM) 210 } 211 212 if !slices.Equal(gotNT, tc.wantNT) { 213 t.Errorf("EncryptedHashes(): got NT hash: %v, want: %v", gotNT, tc.wantNT) 214 } 215 }) 216 } 217 }