[SHEHZADE//ABDULLAH ANSARI]
Red Team Consultant @ Google | MS in Cybersecurity @ NYU
● 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
134 lines · 2.5 KB
| 1 | package acl |
| 2 | |
| 3 | import ( |
| 4 | "encoding/binary" |
| 5 | "encoding/hex" |
| 6 | |
| 7 | "github.com/Velocidex/ordereddict" |
| 8 | "github.com/shehzade/ntds-to-bloodhound/pkg/ese" |
| 9 | ) |
| 10 | |
| 11 | type cacheKey struct { |
| 12 | sdID uint64 |
| 13 | objType string |
| 14 | hasLAPS bool |
| 15 | } |
| 16 | |
| 17 | type cachedSD struct { |
| 18 | aces []Ace |
| 19 | isProtected bool |
| 20 | } |
| 21 | |
| 22 | type SecurityEngine struct { |
| 23 | RawSDs map[uint64][]byte |
| 24 | AclCache map[cacheKey]cachedSD |
| 25 | LapsGUIDs map[string]bool |
| 26 | } |
| 27 | |
| 28 | func NewSecurityEngine(db *ese.DB) (*SecurityEngine, error) { |
| 29 | engine := &SecurityEngine{ |
| 30 | RawSDs: make(map[uint64][]byte), |
| 31 | AclCache: make(map[cacheKey]cachedSD), |
| 32 | LapsGUIDs: make(map[string]bool), |
| 33 | } |
| 34 | |
| 35 | aliases := make(map[uint64]uint64) |
| 36 | |
| 37 | err := db.StreamTable("sd_table", func(row *ordereddict.Dict) error { |
| 38 | idVal, ok := row.Get("sd_id") |
| 39 | if !ok || idVal == nil { |
| 40 | return nil |
| 41 | } |
| 42 | |
| 43 | var id uint64 |
| 44 | switch v := idVal.(type) { |
| 45 | case uint64: |
| 46 | id = v |
| 47 | case int64: |
| 48 | id = uint64(v) |
| 49 | case uint32: |
| 50 | id = uint64(v) |
| 51 | case int32: |
| 52 | id = uint64(v) |
| 53 | case int: |
| 54 | id = uint64(v) |
| 55 | } |
| 56 | |
| 57 | val, _ := row.Get("sd_value") |
| 58 | hexStr, ok := val.(string) |
| 59 | if !ok || len(hexStr) == 0 { |
| 60 | return nil |
| 61 | } |
| 62 | |
| 63 | b, err := hex.DecodeString(hexStr) |
| 64 | if err != nil { |
| 65 | return nil |
| 66 | } |
| 67 | |
| 68 | if len(b) >= 20 { |
| 69 | engine.RawSDs[id] = b |
| 70 | } else if len(b) == 4 { |
| 71 | targetID := uint64(binary.LittleEndian.Uint32(b)) |
| 72 | aliases[id] = targetID |
| 73 | } |
| 74 | |
| 75 | return nil |
| 76 | }) |
| 77 | if err != nil { |
| 78 | return nil, err |
| 79 | } |
| 80 | |
| 81 | for srcID := range aliases { |
| 82 | curr := srcID |
| 83 | visited := make(map[uint64]bool) |
| 84 | for { |
| 85 | if visited[curr] { |
| 86 | break |
| 87 | } |
| 88 | visited[curr] = true |
| 89 | if target, ok := aliases[curr]; ok { |
| 90 | curr = target |
| 91 | } else { |
| 92 | break |
| 93 | } |
| 94 | } |
| 95 | if rawSD, exists := engine.RawSDs[curr]; exists { |
| 96 | engine.RawSDs[srcID] = rawSD |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | return engine, nil |
| 101 | } |
| 102 | |
| 103 | func (e *SecurityEngine) Count() int { |
| 104 | return len(e.RawSDs) |
| 105 | } |
| 106 | |
| 107 | func (e *SecurityEngine) GetAces(sdID uint64) []Ace { |
| 108 | aces, _ := e.GetAcesForObject(sdID, "", false) |
| 109 | return aces |
| 110 | } |
| 111 | |
| 112 | func (e *SecurityEngine) GetAcesForObject(sdID uint64, objectType string, hasLAPS bool) ([]Ace, bool) { |
| 113 | key := cacheKey{sdID: sdID, objType: objectType, hasLAPS: hasLAPS} |
| 114 | if cached, exists := e.AclCache[key]; exists { |
| 115 | out := make([]Ace, len(cached.aces)) |
| 116 | copy(out, cached.aces) |
| 117 | return out, cached.isProtected |
| 118 | } |
| 119 | |
| 120 | rawSD, exists := e.RawSDs[sdID] |
| 121 | if !exists { |
| 122 | return nil, false |
| 123 | } |
| 124 | |
| 125 | aces, isProtected, err := ParseSecurityDescriptorForType(rawSD, objectType, hasLAPS, e.LapsGUIDs) |
| 126 | if err != nil { |
| 127 | return nil, false |
| 128 | } |
| 129 | |
| 130 | e.AclCache[key] = cachedSD{aces: aces, isProtected: isProtected} |
| 131 | out := make([]Ace, len(aces)) |
| 132 | copy(out, aces) |
| 133 | return out, isProtected |
| 134 | } |