github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/userv.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 "bytes" 19 "encoding/binary" 20 "errors" 21 22 "golang.org/x/text/encoding/unicode" 23 ) 24 25 var ( 26 errReadOutOfBounds = errors.New("failed to read: out of bounds") 27 errNoHashInfoFound = errors.New("no hash information found") 28 ) 29 30 // userV is the V structure containing the user's information in the SAM hive. 31 // See https://web.archive.org/web/20190717124313/http://www.beginningtoseethelight.org/ntsecurity/index.htm#D3BC3F5643A17823 32 // for more information about the V structure. 33 type userV struct { 34 rid string 35 header *userVHeader 36 data []byte 37 } 38 39 // newUserV parses the V structure from the provided buffer. 40 func newUserV(data []byte, rid string) (*userV, error) { 41 entry := userVHeader{} 42 if err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &entry); err != nil { 43 return nil, err 44 } 45 46 return &userV{ 47 rid: rid, 48 data: data, 49 header: &entry, 50 }, nil 51 } 52 53 // read finds and reads data in the V structure from the provided offset and size. 54 func (s *userV) read(offset, size uint32) ([]byte, error) { 55 offset += 0xCC 56 limit := int(offset) + int(size) 57 if len(s.data) < limit { 58 return nil, errReadOutOfBounds 59 } 60 61 return s.data[offset:limit], nil 62 } 63 64 // Username returns the username of the user. 65 func (s *userV) Username() (string, error) { 66 data, err := s.read(s.header.NameOffset, s.header.NameLength) 67 if err != nil { 68 return "", err 69 } 70 71 decoded, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder().Bytes(data) 72 if err != nil { 73 return "", err 74 } 75 76 return string(decoded), nil 77 } 78 79 // EncryptedHashes returns the encrypted LM and NT hashes of the user. 80 // Note that at this point, the hashes are still encrypted with RC4 or AES. 81 func (s *userV) EncryptedHashes() ([]byte, []byte, error) { 82 if s.header.NtHashLength == 0 { 83 return nil, nil, errNoHashInfoFound 84 } 85 86 ntHash, err := s.read(s.header.NtHashOffset, s.header.NtHashLength) 87 if err != nil { 88 return nil, nil, err 89 } 90 91 lmHash, err := s.read(s.header.LmHashOffset, s.header.LmHashLength) 92 if err != nil { 93 return nil, nil, err 94 } 95 96 return lmHash, ntHash, nil 97 } 98 99 type userVHeader struct { 100 Reserved0 [12]byte 101 NameOffset uint32 102 NameLength uint32 103 Reserved1 uint32 104 FullNameOffset uint32 105 FullNameLength uint32 106 Reserved2 uint32 107 CommentOffset uint32 108 CommentLength uint32 109 Reserved3 uint32 110 UserCommentOffset uint32 111 UserCommentLength uint32 112 Reserved4 uint32 113 Reserved5 [12]byte 114 HomeDirOffset uint32 115 HomeDirLength uint32 116 Reserved6 uint32 117 HomeDirConnectOffset uint32 118 HomeDirConnectLength uint32 119 Reserved7 uint32 120 ScriptPathOffset uint32 121 ScriptPathLength uint32 122 Reserved8 uint32 123 ProfilePathOffset uint32 124 ProfilePathLength uint32 125 Reserved9 uint32 126 WorkstationsOffset uint32 127 WorkstationsLength uint32 128 Reserved10 uint32 129 HoursAllowedOffset uint32 130 HoursAllowedLength uint32 131 Reserved11 uint32 132 Reserved12 [12]byte 133 LmHashOffset uint32 134 LmHashLength uint32 135 Reserved13 uint32 136 NtHashOffset uint32 137 NtHashLength uint32 138 Reserved14 uint32 139 Reserved15 [24]byte 140 }