[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
96 lines · 2.3 KB
| 1 | package registry |
| 2 | |
| 3 | import ( |
| 4 | "encoding/binary" |
| 5 | "encoding/hex" |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "os" |
| 9 | |
| 10 | "www.velocidex.com/golang/regparser" |
| 11 | ) |
| 12 | |
| 13 | var transforms = [16]int{8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7} |
| 14 | |
| 15 | func ExtractBootKey(systemHivePath string) ([]byte, error) { |
| 16 | f, err := os.Open(systemHivePath) |
| 17 | if err != nil { |
| 18 | return nil, fmt.Errorf("failed to open SYSTEM hive: %w", err) |
| 19 | } |
| 20 | defer f.Close() |
| 21 | |
| 22 | return ExtractBootKeyFromReader(f) |
| 23 | } |
| 24 | |
| 25 | func ExtractBootKeyFromReader(reader io.ReaderAt) ([]byte, error) { |
| 26 | reg, err := regparser.NewRegistry(reader) |
| 27 | if err != nil { |
| 28 | return nil, fmt.Errorf("failed to parse registry hive: %w", err) |
| 29 | } |
| 30 | |
| 31 | controlSet := "ControlSet001" |
| 32 | selectNode := reg.OpenKey("Select") |
| 33 | if selectNode != nil { |
| 34 | var currentCS, defaultCS uint32 |
| 35 | for _, v := range selectNode.Values() { |
| 36 | vd := v.ValueData() |
| 37 | if len(vd.Data) >= 4 { |
| 38 | switch v.ValueName() { |
| 39 | case "Current": |
| 40 | currentCS = binary.LittleEndian.Uint32(vd.Data) |
| 41 | case "Default": |
| 42 | defaultCS = binary.LittleEndian.Uint32(vd.Data) |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | if currentCS > 0 { |
| 47 | controlSet = fmt.Sprintf("ControlSet%03d", currentCS) |
| 48 | } else if defaultCS > 0 { |
| 49 | controlSet = fmt.Sprintf("ControlSet%03d", defaultCS) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | var rawHex string |
| 54 | subkeys := []string{"JD", "Skew1", "GBG", "Data"} |
| 55 | for _, key := range subkeys { |
| 56 | path := fmt.Sprintf("%s\\Control\\Lsa\\%s", controlSet, key) |
| 57 | node := reg.OpenKey(path) |
| 58 | if node == nil { |
| 59 | return nil, fmt.Errorf("required LSA subkey not found: %s", path) |
| 60 | } |
| 61 | |
| 62 | classOffset := node.Class() |
| 63 | classLen := node.ClassLength() |
| 64 | if classLen < 16 { |
| 65 | return nil, fmt.Errorf("invalid class length for subkey %s: %d", key, classLen) |
| 66 | } |
| 67 | |
| 68 | buf := make([]byte, 16) |
| 69 | fileOffset := 0x1000 + int64(classOffset) + 4 |
| 70 | if _, err := reader.ReadAt(buf, fileOffset); err != nil { |
| 71 | return nil, fmt.Errorf("failed to read class data for %s: %w", key, err) |
| 72 | } |
| 73 | |
| 74 | var chars []rune |
| 75 | for i := 0; i < 16; i += 2 { |
| 76 | chars = append(chars, rune(binary.LittleEndian.Uint16(buf[i:i+2]))) |
| 77 | } |
| 78 | rawHex += string(chars) |
| 79 | } |
| 80 | |
| 81 | rawBytes, err := hex.DecodeString(rawHex) |
| 82 | if err != nil { |
| 83 | return nil, fmt.Errorf("failed to decode hex class data: %w", err) |
| 84 | } |
| 85 | |
| 86 | if len(rawBytes) != 16 { |
| 87 | return nil, fmt.Errorf("expected 16 raw key bytes, got %d", len(rawBytes)) |
| 88 | } |
| 89 | |
| 90 | bootKey := make([]byte, 16) |
| 91 | for i := 0; i < 16; i++ { |
| 92 | bootKey[i] = rawBytes[transforms[i]] |
| 93 | } |
| 94 | |
| 95 | return bootKey, nil |
| 96 | } |