● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.

Offline extractor and decryptor for SCCM/MECM Network Access Account (NAA) credentials and policy secrets from dead-disk WMI repositories and DPAPI master keys.

$ git clone https://shehzade.io/repos/naa-dump-offline.git Copy
Source ZIP (290.0 KB) Prebuilt Binaries
Code 15 Commits 3 Releases 5
naa-dump-offline / pkg / dpapi / blob.go
← Back to directory
239 lines · 5.9 KB
Raw
1package dpapi
2
3import (
4 "crypto/aes"
5 "crypto/cipher"
6 "crypto/des"
7 "crypto/hmac"
8 "crypto/sha1"
9 "crypto/sha512"
10 "encoding/binary"
11 "fmt"
12 "hash"
13)
14
15type DPAPIBlob struct {
16 Version uint32
17 GuidCredential string
18 MasterKeyVersion uint32
19 GuidMasterKey string
20 Flags uint32
21 Description string
22 CryptAlgo uint32
23 CryptAlgoLen uint32
24 Salt []byte
25 HMacKey []byte
26 HashAlgo uint32
27 HashAlgoLen uint32
28 HMac []byte
29 Data []byte
30 Sign []byte
31 RawData []byte
32}
33
34func ParseDPAPIBlob(raw []byte) (*DPAPIBlob, error) {
35 if len(raw) < 44 {
36 return nil, fmt.Errorf("DPAPI blob too short (%d bytes)", len(raw))
37 }
38
39 blob := &DPAPIBlob{
40 RawData: raw,
41 Version: binary.LittleEndian.Uint32(raw[0:4]),
42 GuidCredential: FormatGUID(raw[4:20]),
43 MasterKeyVersion: binary.LittleEndian.Uint32(raw[20:24]),
44 GuidMasterKey: FormatGUID(raw[24:40]),
45 Flags: binary.LittleEndian.Uint32(raw[40:44]),
46 }
47
48 offset := 44
49 if offset+4 > len(raw) {
50 return nil, fmt.Errorf("invalid blob at description len")
51 }
52 descLen := binary.LittleEndian.Uint32(raw[offset : offset+4])
53 offset += 4
54 if offset+int(descLen) > len(raw) {
55 return nil, fmt.Errorf("invalid blob description length")
56 }
57 blob.Description = DecodeUTF16LE(raw[offset : offset+int(descLen)])
58 offset += int(descLen)
59
60 if offset+8 > len(raw) {
61 return nil, fmt.Errorf("invalid blob at CryptAlgo")
62 }
63 blob.CryptAlgo = binary.LittleEndian.Uint32(raw[offset : offset+4])
64 blob.CryptAlgoLen = binary.LittleEndian.Uint32(raw[offset+4 : offset+8])
65 offset += 8
66
67 if offset+4 > len(raw) {
68 return nil, fmt.Errorf("invalid blob at SaltLen")
69 }
70 saltLen := binary.LittleEndian.Uint32(raw[offset : offset+4])
71 offset += 4
72 if offset+int(saltLen) > len(raw) {
73 return nil, fmt.Errorf("invalid blob salt length")
74 }
75 blob.Salt = raw[offset : offset+int(saltLen)]
76 offset += int(saltLen)
77
78 if offset+4 > len(raw) {
79 return nil, fmt.Errorf("invalid blob at HMacKeyLen")
80 }
81 hmacKeyLen := binary.LittleEndian.Uint32(raw[offset : offset+4])
82 offset += 4
83 if offset+int(hmacKeyLen) > len(raw) {
84 return nil, fmt.Errorf("invalid blob hmac key length")
85 }
86 blob.HMacKey = raw[offset : offset+int(hmacKeyLen)]
87 offset += int(hmacKeyLen)
88
89 if offset+8 > len(raw) {
90 return nil, fmt.Errorf("invalid blob at HashAlgo")
91 }
92 blob.HashAlgo = binary.LittleEndian.Uint32(raw[offset : offset+4])
93 blob.HashAlgoLen = binary.LittleEndian.Uint32(raw[offset+4 : offset+8])
94 offset += 8
95
96 if offset+4 > len(raw) {
97 return nil, fmt.Errorf("invalid blob at HMacLen")
98 }
99 hmacLen := binary.LittleEndian.Uint32(raw[offset : offset+4])
100 offset += 4
101 if offset+int(hmacLen) > len(raw) {
102 return nil, fmt.Errorf("invalid blob hmac length")
103 }
104 blob.HMac = raw[offset : offset+int(hmacLen)]
105 offset += int(hmacLen)
106
107 if offset+4 > len(raw) {
108 return nil, fmt.Errorf("invalid blob at DataLen")
109 }
110 dataLen := binary.LittleEndian.Uint32(raw[offset : offset+4])
111 offset += 4
112 if offset+int(dataLen) > len(raw) {
113 return nil, fmt.Errorf("invalid blob data length")
114 }
115 blob.Data = raw[offset : offset+int(dataLen)]
116 offset += int(dataLen)
117
118 if offset+4 <= len(raw) {
119 signLen := binary.LittleEndian.Uint32(raw[offset : offset+4])
120 offset += 4
121 if offset+int(signLen) <= len(raw) {
122 blob.Sign = raw[offset : offset+int(signLen)]
123 }
124 }
125
126 return blob, nil
127}
128
129func (blob *DPAPIBlob) Decrypt(masterKey []byte) ([]byte, error) {
130 h := sha1.New()
131 h.Write(masterKey)
132 keyHash := h.Sum(nil)
133
134 var hashFunc func() hash.Hash
135 var hashBlockSize int
136 switch blob.HashAlgo {
137 case CALG_SHA1, CALG_HMAC:
138 hashFunc = sha1.New
139 hashBlockSize = 64
140 case CALG_SHA_512:
141 hashFunc = sha512.New
142 hashBlockSize = 128
143 default:
144 return nil, fmt.Errorf("unsupported blob hash algo: 0x%x", blob.HashAlgo)
145 }
146
147 var cryptKeyLen, ivLen int
148 switch blob.CryptAlgo {
149 case CALG_AES_256:
150 cryptKeyLen = 32
151 ivLen = 16
152 case CALG_AES_128:
153 cryptKeyLen = 16
154 ivLen = 16
155 case CALG_3DES:
156 cryptKeyLen = 24
157 ivLen = 8
158 default:
159 return nil, fmt.Errorf("unsupported blob crypt algo: 0x%x", blob.CryptAlgo)
160 }
161
162 h2 := hmac.New(hashFunc, keyHash)
163 h2.Write(blob.Salt)
164 sessionKey := h2.Sum(nil)
165
166 derivedKey := sessionKey
167 if len(derivedKey) > hashBlockSize {
168 h3 := hmac.New(hashFunc, sessionKey)
169 derivedKey = h3.Sum(nil)
170 }
171
172 if len(derivedKey) < cryptKeyLen {
173 padLen := hashBlockSize
174 padded := make([]byte, len(derivedKey)+padLen)
175 copy(padded, derivedKey)
176 ipad := make([]byte, padLen)
177 opad := make([]byte, padLen)
178 for i := 0; i < padLen; i++ {
179 ipad[i] = padded[i] ^ 0x36
180 opad[i] = padded[i] ^ 0x5c
181 }
182
183 hi := hashFunc()
184 hi.Write(ipad)
185 ho := hashFunc()
186 ho.Write(opad)
187
188 derivedKey = append(hi.Sum(nil), ho.Sum(nil)...)
189 }
190
191 cryptKey := derivedKey[:cryptKeyLen]
192 iv := make([]byte, ivLen)
193
194 var cleartext []byte
195 switch blob.CryptAlgo {
196 case CALG_AES_256, CALG_AES_128:
197 block, err := aes.NewCipher(cryptKey)
198 if err != nil {
199 return nil, err
200 }
201 if len(blob.Data)%aes.BlockSize != 0 {
202 return nil, fmt.Errorf("ciphertext length not multiple of AES block size")
203 }
204 mode := cipher.NewCBCDecrypter(block, iv)
205 cleartext = make([]byte, len(blob.Data))
206 mode.CryptBlocks(cleartext, blob.Data)
207 case CALG_3DES:
208 block, err := des.NewTripleDESCipher(cryptKey)
209 if err != nil {
210 return nil, err
211 }
212 if len(blob.Data)%des.BlockSize != 0 {
213 return nil, fmt.Errorf("ciphertext length not multiple of 3DES block size")
214 }
215 mode := cipher.NewCBCDecrypter(block, iv)
216 cleartext = make([]byte, len(blob.Data))
217 mode.CryptBlocks(cleartext, blob.Data)
218 }
219
220 if len(cleartext) == 0 {
221 return nil, fmt.Errorf("decrypted cleartext empty")
222 }
223
224 padding := int(cleartext[len(cleartext)-1])
225 if padding > 0 && padding <= len(cleartext) {
226 valid := true
227 for i := len(cleartext) - padding; i < len(cleartext); i++ {
228 if cleartext[i] != byte(padding) {
229 valid = false
230 break
231 }
232 }
233 if valid {
234 return cleartext[:len(cleartext)-padding], nil
235 }
236 }
237
238 return cleartext, nil
239}