● 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.
PUBLIC MIRRORGo v1.0.0

Offline Active Directory database (ntds.dit) parser and BloodHound graph reconstructor in pure Go. Extracts directory objects, group memberships, domain trusts, and binary DACLs from dead-disk ESE database pages and registry hives.

$ git clone https://shehzade.io/repos/ntds-to-bloodhound.git Copy
Source ZIP (246.9 KB) Prebuilt Binaries
Code 21 Commits 1 Releases 5
ntds-to-bloodhound / pkg / crypto / crypto.go
← Back to directory
269 lines · 6.4 KB
Raw
1package crypto
2
3import (
4 "crypto/aes"
5 "crypto/cipher"
6 "crypto/des"
7 "crypto/md5"
8 "crypto/rc4"
9 "encoding/binary"
10 "encoding/hex"
11 "errors"
12 "fmt"
13 "strings"
14)
15
16const (
17 EmptyLMHash = "aad3b435b51404eeaad3b435b51404ee"
18 EmptyNTHash = "31d6cfe0d16ae931b73c59d7e0c089c0"
19)
20
21type CryptoEngine struct {
22 BootKey []byte
23 PEKList [][]byte
24}
25
26func NewCryptoEngine(bootKey []byte) *CryptoEngine {
27 return &CryptoEngine{
28 BootKey: bootKey,
29 PEKList: make([][]byte, 0),
30 }
31}
32
33func (c *CryptoEngine) ParsePekList(pekListRaw []byte) error {
34 if len(pekListRaw) < 24 {
35 return errors.New("pekList blob too short")
36 }
37
38 header := pekListRaw[:8]
39 keyMaterial := pekListRaw[8:24]
40 encryptedPek := pekListRaw[24:]
41
42 var decryptedPekList []byte
43
44 if binary.LittleEndian.Uint32(header[:4]) == 0x02 {
45 h := md5.New()
46 h.Write(c.BootKey)
47 for i := 0; i < 1000; i++ {
48 h.Write(keyMaterial)
49 }
50 tmpKey := h.Sum(nil)
51 rc4Cipher, err := rc4.NewCipher(tmpKey)
52 if err != nil {
53 return fmt.Errorf("failed to create RC4 cipher for PEK: %w", err)
54 }
55 decryptedPekList = make([]byte, len(encryptedPek))
56 rc4Cipher.XORKeyStream(decryptedPekList, encryptedPek)
57 } else if binary.LittleEndian.Uint32(header[:4]) == 0x03 {
58 block, err := aes.NewCipher(c.BootKey)
59 if err != nil {
60 return fmt.Errorf("failed to create AES cipher for PEK: %w", err)
61 }
62 if len(encryptedPek)%aes.BlockSize != 0 {
63 return fmt.Errorf("encrypted PEK size %d is not a multiple of block size", len(encryptedPek))
64 }
65 mode := cipher.NewCBCDecrypter(block, keyMaterial)
66 decryptedPekList = make([]byte, len(encryptedPek))
67 mode.CryptBlocks(decryptedPekList, encryptedPek)
68 } else {
69 return fmt.Errorf("unsupported PEK header version: %x", header[:4])
70 }
71
72 if len(decryptedPekList) < 32 {
73 return errors.New("decrypted PEK list too short")
74 }
75
76 plainEntries := decryptedPekList[32:]
77 pos := 0
78 curIndex := uint32(0)
79
80 for pos+20 <= len(plainEntries) {
81 index := binary.LittleEndian.Uint32(plainEntries[pos : pos+4])
82 pekKey := make([]byte, 16)
83 copy(pekKey, plainEntries[pos+4:pos+20])
84
85 if index != curIndex {
86 break
87 }
88 c.PEKList = append(c.PEKList, pekKey)
89 curIndex++
90 pos += 20
91 }
92
93 if len(c.PEKList) == 0 {
94 return errors.New("no valid PEK keys found in pekList")
95 }
96
97 return nil
98}
99
100func transformKey(in []byte) []byte {
101 out := make([]byte, 8)
102 out[0] = in[0] >> 1
103 out[1] = ((in[0] & 0x01) << 6) | (in[1] >> 2)
104 out[2] = ((in[1] & 0x03) << 5) | (in[2] >> 3)
105 out[3] = ((in[2] & 0x07) << 4) | (in[3] >> 4)
106 out[4] = ((in[3] & 0x0F) << 3) | (in[4] >> 5)
107 out[5] = ((in[4] & 0x1F) << 2) | (in[5] >> 6)
108 out[6] = ((in[5] & 0x3F) << 1) | (in[6] >> 7)
109 out[7] = in[6] & 0x7F
110
111 for i := 0; i < 8; i++ {
112 out[i] = (out[i] << 1) & 0xFE
113 }
114 return out
115}
116
117func deriveDesKeys(rid uint32) ([]byte, []byte) {
118 ridBytes := make([]byte, 4)
119 binary.LittleEndian.PutUint32(ridBytes, rid)
120
121 k1 := []byte{ridBytes[0], ridBytes[1], ridBytes[2], ridBytes[3], ridBytes[0], ridBytes[1], ridBytes[2]}
122 k2 := []byte{ridBytes[3], ridBytes[0], ridBytes[1], ridBytes[2], ridBytes[3], ridBytes[0], ridBytes[1]}
123
124 return transformKey(k1), transformKey(k2)
125}
126
127func removeDESLayer(crypted []byte, rid uint32) ([]byte, error) {
128 if len(crypted) < 16 {
129 return nil, errors.New("crypted hash must be at least 16 bytes")
130 }
131
132 k1, k2 := deriveDesKeys(rid)
133 c1, err := des.NewCipher(k1)
134 if err != nil {
135 return nil, err
136 }
137 c2, err := des.NewCipher(k2)
138 if err != nil {
139 return nil, err
140 }
141
142 out := make([]byte, 16)
143 c1.Decrypt(out[:8], crypted[:8])
144 c2.Decrypt(out[8:16], crypted[8:16])
145 return out, nil
146}
147
148func (c *CryptoEngine) DecryptHash(encBlob []byte, rid uint32) ([]byte, error) {
149 if len(c.PEKList) == 0 {
150 return nil, errors.New("no PEK keys loaded")
151 }
152 if len(encBlob) < 24 {
153 return nil, errors.New("encrypted hash blob too short")
154 }
155
156 header := encBlob[:8]
157 keyMaterial := encBlob[8:24]
158
159 pekIndexHex := hex.EncodeToString(header)
160 if len(pekIndexHex) < 10 {
161 return nil, errors.New("invalid header format")
162 }
163 pekIndex := int(header[4])
164 if pekIndex >= len(c.PEKList) {
165 pekIndex = 0
166 }
167 pek := c.PEKList[pekIndex]
168
169 var tmpHash []byte
170
171 if binary.LittleEndian.Uint32(header[:4]) == 0x13 {
172 if len(encBlob) < 28+16 {
173 return nil, errors.New("AES hash blob too short")
174 }
175 ciphertext := encBlob[28 : 28+16]
176
177 block, err := aes.NewCipher(pek)
178 if err != nil {
179 return nil, fmt.Errorf("failed to create AES cipher: %w", err)
180 }
181 mode := cipher.NewCBCDecrypter(block, keyMaterial)
182 tmpHash = make([]byte, 16)
183 mode.CryptBlocks(tmpHash, ciphertext)
184 } else {
185 if len(encBlob) < 24+16 {
186 return nil, errors.New("RC4 hash blob too short")
187 }
188 ciphertext := encBlob[24 : 24+16]
189
190 h := md5.New()
191 h.Write(pek)
192 h.Write(keyMaterial)
193 tmpKey := h.Sum(nil)
194
195 rc4Cipher, err := rc4.NewCipher(tmpKey)
196 if err != nil {
197 return nil, fmt.Errorf("failed to create RC4 cipher: %w", err)
198 }
199 tmpHash = make([]byte, 16)
200 rc4Cipher.XORKeyStream(tmpHash, ciphertext)
201 }
202
203 return removeDESLayer(tmpHash, rid)
204}
205
206func ParseSID(b []byte) (string, uint32, error) {
207 if len(b) < 8 {
208 return "", 0, errors.New("SID buffer too short")
209 }
210
211 revision := b[0]
212 subAuthCount := int(b[1])
213 if len(b) < 8+subAuthCount*4 {
214 return "", 0, errors.New("SID buffer truncated for subauthorities")
215 }
216
217 var authority uint64
218 for i := 2; i < 8; i++ {
219 authority = (authority << 8) | uint64(b[i])
220 }
221
222 parts := []string{fmt.Sprintf("S-%d-%d", revision, authority)}
223 var rid uint32
224
225 for i := 0; i < subAuthCount; i++ {
226 offset := 8 + i*4
227 var subAuth uint32
228 if i == subAuthCount-1 && subAuthCount > 1 {
229 subAuth = binary.BigEndian.Uint32(b[offset : offset+4])
230 rid = subAuth
231 } else {
232 subAuth = binary.LittleEndian.Uint32(b[offset : offset+4])
233 }
234 parts = append(parts, fmt.Sprintf("%d", subAuth))
235 }
236
237 return strings.Join(parts, "-"), rid, nil
238}
239
240func ParseBinarySID(b []byte) (string, uint32, error) {
241 if len(b) < 8 {
242 return "", 0, errors.New("SID buffer too short")
243 }
244
245 revision := b[0]
246 subAuthCount := int(b[1])
247 if revision != 1 || subAuthCount > 15 || len(b) < 8+subAuthCount*4 {
248 return "", 0, errors.New("SID buffer invalid or truncated")
249 }
250
251 var authority uint64
252 for i := 2; i < 8; i++ {
253 authority = (authority << 8) | uint64(b[i])
254 }
255
256 parts := []string{fmt.Sprintf("S-%d-%d", revision, authority)}
257 var rid uint32
258
259 for i := 0; i < subAuthCount; i++ {
260 offset := 8 + i*4
261 subAuth := binary.LittleEndian.Uint32(b[offset : offset+4])
262 if i == subAuthCount-1 {
263 rid = subAuth
264 }
265 parts = append(parts, fmt.Sprintf("%d", subAuth))
266 }
267
268 return strings.Join(parts, "-"), rid, nil
269}