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  }