github.com/google/osv-scalibr@v0.4.1/detector/weakcredentials/winlocal/samreg/crypto.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 "crypto/aes" 19 "crypto/cipher" 20 "crypto/des" 21 "crypto/md5" 22 "crypto/rc4" 23 "errors" 24 "slices" 25 ) 26 27 /* 28 ** Note: This file contains the code to perform the final step of decrypting the user's hashes. 29 ** 30 ** You can read more about hash encryption in the following article: 31 ** https://web.archive.org/web/20190717124313/http://www.beginningtoseethelight.org/ntsecurity/index.htm#D3BC3F5643A17823 32 ** 33 ** The hashes are encrypted several times with different encryption algorithms and keys. 34 ** First, the hashes can be encrypted with RC4 or AES-256. 35 ** - For RC4, the key is the MD5 hash of the syskey, the RID and statically defined constants 36 ** (different for NTLM or LM). 37 ** - For AES-256, the key is the syskey and the IV is found in the UserV structure directly. 38 ** 39 ** Whether RC4 or AES-256 is to be used depends on a combination of the current OS version, the time 40 ** the hash were generated and the current OS configuration. But the hash data stored in the UserV 41 ** structure contains bits indicating which encryption algorithm was used. 42 ** 43 ** Once the first decryption has been performed, the hash is always encrypted with a final layer of 44 ** DES. The encrypted is split in two halves and each half is decrypted with a different key. 45 ** The two keys are derived from the user's RID. They are derived from a set of statically 46 ** defined permutations and bitwise operations. 47 */ 48 49 const ( 50 // LMHashConstant is used to decrypt LM hashes when RC4 is used. 51 LMHashConstant = "LMPASSWORD\x00" 52 // NTLMHashConstant is used to decrypt NTLM hashes when RC4 is used. 53 NTLMHashConstant = "NTPASSWORD\x00" 54 ) 55 56 var ( 57 errInvalidRIDSize = errors.New("RID cannot be derived: is not 4 bytes") 58 ) 59 60 // transformRID performs a set of bitwise operations on the provided key to derive one of the two 61 // 8-byte keys that will be used in the final step of hash decryption (DES). 62 // These bitwise operations are hardcoded on the Windows side, so we do the same here. 63 func transformRID(key []byte) []byte { 64 var outputKey []byte 65 outputKey = append(outputKey, key[0]>>0x1) 66 outputKey = append(outputKey, ((key[0]&0x01)<<6)|(key[1]>>2)) 67 outputKey = append(outputKey, ((key[1]&0x03)<<5)|(key[2]>>3)) 68 outputKey = append(outputKey, ((key[2]&0x07)<<4)|(key[3]>>4)) 69 outputKey = append(outputKey, ((key[3]&0x0F)<<3)|(key[4]>>5)) 70 outputKey = append(outputKey, ((key[4]&0x1F)<<2)|(key[5]>>6)) 71 outputKey = append(outputKey, ((key[5]&0x3F)<<1)|(key[6]>>7)) 72 outputKey = append(outputKey, key[6]&0x7F) 73 74 for i := range 8 { 75 outputKey[i] = (outputKey[i] << 1) & 0xfe 76 } 77 78 return outputKey 79 } 80 81 // deriveRID derives two 8-byte keys from the provided RID. It performs a set of predefined 82 // circular permutations on the RID before using transformRID to perform bitwise operations that 83 // will result in the final keys. 84 func deriveRID(rid []byte) ([]byte, []byte, error) { 85 if len(rid) != 4 { 86 return nil, nil, errInvalidRIDSize 87 } 88 89 rid1 := []byte{rid[0], rid[1], rid[2], rid[3], rid[0], rid[1], rid[2]} 90 rid2 := []byte{rid[3], rid[0], rid[1], rid[2], rid[3], rid[0], rid[1]} 91 return transformRID(rid1), transformRID(rid2), nil 92 } 93 94 // Whichever encryption algorithm is used, the last round of decryption is always DES. It involves: 95 // - Taking the RID of the user and deriving it into two 8-byte DES keys 96 // - Each of these two keys is then used to decrypt a half of the encrypted hash 97 func decryptDES(rid []byte, encryptedHash []byte) ([]byte, error) { 98 key1, key2, err := deriveRID(rid) 99 if err != nil { 100 return nil, err 101 } 102 103 block1, err := des.NewCipher(key1) 104 if err != nil { 105 return nil, err 106 } 107 108 block2, err := des.NewCipher(key2) 109 if err != nil { 110 return nil, err 111 } 112 113 decryptedHash := make([]byte, 16) 114 block1.Decrypt(decryptedHash[:8], encryptedHash[:8]) 115 block2.Decrypt(decryptedHash[8:], encryptedHash[8:]) 116 return decryptedHash, nil 117 } 118 119 // decryptRC4Hash decrypts the RC4 encrypted user's hash back to LM/NTLM format using the derived 120 // syskey. Note that the syskey is expected to be already derived. 121 func decryptRC4Hash(rid []byte, syskey, hash []byte, hashConstant []byte) ([]byte, error) { 122 rc4Key := md5.Sum(slices.Concat(syskey, rid, hashConstant)) 123 c, err := rc4.NewCipher(rc4Key[:]) 124 if err != nil { 125 return nil, err 126 } 127 128 rc4Decrypted := make([]byte, 16) 129 c.XORKeyStream(rc4Decrypted, hash) 130 return decryptDES(rid, rc4Decrypted[:16]) 131 } 132 133 // decryptAESHash decrypts the AES encrypted user's hash back to LM/NTLM format using the derived 134 // syskey. Note that the syskey is expected to be already derived. 135 func decryptAESHash(rid []byte, syskey, hash []byte, iv []byte) ([]byte, error) { 136 if len(hash) == 0 { 137 return nil, nil 138 } 139 140 if len(hash)%aes.BlockSize != 0 { 141 return nil, errors.New("hash length not aligned with AES block size") 142 } 143 144 block, err := aes.NewCipher(syskey) 145 if err != nil { 146 return nil, err 147 } 148 149 mode := cipher.NewCBCDecrypter(block, iv) 150 aesDecrypted := make([]byte, 32) 151 mode.CryptBlocks(aesDecrypted, hash) 152 return decryptDES(rid, aesDecrypted) 153 }