Skip to content

Commit ecaa968

Browse files
committed
Wrapped map in object Edge methods to be able to do locking
1 parent a14a7d3 commit ecaa968

File tree

8 files changed

+135
-74
lines changed

8 files changed

+135
-74
lines changed

modules/analyze/webservicefuncs.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ func analysisfuncs(ws *webservice) {
865865

866866
var pwnlinks int
867867
for _, object := range ws.Objs.Slice() {
868-
pwnlinks += len(object.CanPwn)
868+
pwnlinks += object.EdgeCount(engine.Out)
869869
}
870870
result.Statistics["Total"] = len(ws.Objs.Slice())
871871
result.Statistics["PwnConnections"] = pwnlinks

modules/engine/analyzeobjects.go

+13-14
Original file line numberDiff line numberDiff line change
@@ -135,31 +135,28 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg Graph) {
135135

136136
newconnectionsmap := make(map[ObjectPair]EdgeBitmap) // Pwn Connection between objects
137137

138-
var ec EdgeConnections
138+
var ec EdgeDirection
139139
if forward {
140-
ec = object.PwnableBy
140+
ec = In
141141
} else {
142-
ec = object.CanPwn
142+
ec = Out
143143
}
144144

145145
// Iterate over ever outgoing pwn
146146
// This is not efficient, but we sort the pwnlist first
147-
for _, target := range ec.Objects() {
148-
eb := ec[target]
149-
147+
object.EdgeIterator(ec, func(target *Object, eb EdgeBitmap) bool {
150148
// If this is not a chosen method, skip it
151149
detectededges := eb.Intersect(detectedges)
152150

153-
edgecount := detectededges.Count()
154-
if edgecount == 0 {
151+
if detectededges.IsBlank() {
155152
// Nothing useful or just a deny ACL, skip it
156-
continue
153+
return true // continue
157154
}
158155

159156
if detectobjecttypes != nil {
160157
if _, found := detectobjecttypes[target.Type()]; !found {
161158
// We're filtering on types, and it's not wanted
162-
continue
159+
return true //continue
163160
}
164161
}
165162

@@ -171,7 +168,7 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg Graph) {
171168
}
172169
if maxprobability < Probability(opts.MinProbability) {
173170
// Too unlikeliy, so we skip it
174-
continue
171+
return true // continue
175172
}
176173

177174
// If we allow backlinks, all pwns are mapped, no matter who is the victim
@@ -193,19 +190,21 @@ func AnalyzeObjects(opts AnalyzeObjectsOptions) (pg Graph) {
193190
// If SIDs match between objects, it's a cross forest link and we want to see it
194191
(object.SID().IsNull() || target.SID().IsNull() || object.SID().Component(2) != 21 || object.SID() != target.SID()) {
195192
// skip it
196-
continue
193+
return true // continue
197194
}
198195

199196
if opts.ExcludeObjects != nil {
200197
if _, found := opts.ExcludeObjects.FindByID(target.ID()); found {
201198
// skip excluded objects
202199
// ui.Debug().Msgf("Excluding target %v", pwntarget.DN())
203-
continue
200+
return true // continue
204201
}
205202
}
206203

207204
newconnectionsmap[ObjectPair{Source: object, Target: target}] = detectededges
208-
}
205+
206+
return true
207+
})
209208

210209
if opts.MaxOutgoingConnections == -1 || len(newconnectionsmap) < opts.MaxOutgoingConnections {
211210
for pwnpair, detectedmethods := range newconnectionsmap {

modules/engine/analyzepaths.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -166,22 +166,20 @@ func AnalyzePaths(start, end *Object, obs *Objects, lookforedges EdgeBitmap, min
166166

167167
visited[source] = struct{}{}
168168

169-
for target, edges := range source.CanPwn {
169+
source.EdgeIterator(Out, func(target *Object, edges EdgeBitmap) bool {
170170
if _, found := visited[target]; !found {
171-
172171
// If this is not a chosen method, skip it
173172
detectededges := edges.Intersect(lookforedges)
174173

175-
edgecount := detectededges.Count()
176-
if edgecount == 0 {
174+
if detectededges.IsBlank() {
177175
// Nothing useful or just a deny ACL, skip it
178-
continue
176+
return true //continue
179177
}
180178

181179
prob := detectededges.MaxProbability(v.Object, target)
182180
if prob < minprobability {
183181
// Skip entirely if too
184-
continue
182+
return true //continue
185183
}
186184

187185
weight := uint32(101 - prob)
@@ -201,7 +199,8 @@ func AnalyzePaths(start, end *Object, obs *Objects, lookforedges EdgeBitmap, min
201199
q.Push(target, sdist+weight)
202200
}
203201
}
204-
}
202+
return true
203+
})
205204
}
206205

207206
if prev[end] == nil {
@@ -228,7 +227,7 @@ func AnalyzePaths(start, end *Object, obs *Objects, lookforedges EdgeBitmap, min
228227
GraphEdge{
229228
Source: prenode,
230229
Target: curnode,
231-
EdgeBitmap: prenode.CanPwn[curnode],
230+
EdgeBitmap: prenode.Edge(Out, curnode),
232231
})
233232
if prenode == start {
234233
break

modules/engine/edge.go

+16
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ func (eb EdgeBitmap) Count() int {
7474
return ones
7575
}
7676

77+
func (eb EdgeBitmap) IsBlank() bool {
78+
for i := 0; i < PMBSIZE; i++ {
79+
if eb[i] != 0 {
80+
return false
81+
}
82+
}
83+
return true
84+
}
85+
7786
func (eb EdgeBitmap) Edges() []Edge {
7887
result := make([]Edge, eb.Count())
7988
var n int
@@ -239,6 +248,13 @@ type PwnMethodsAndProbabilities struct {
239248
}
240249
*/
241250

251+
type EdgeDirection int
252+
253+
const (
254+
Out EdgeDirection = 0
255+
In EdgeDirection = 1
256+
)
257+
242258
type EdgeConnections map[*Object]EdgeBitmap //sAndProbabilities
243259

244260
var globalEdgeConnectionsLock sync.Mutex // Ugly but it will do

modules/engine/object.go

+66-24
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ func setThreadsafe(enable bool) {
4848
var UnknownGUID = uuid.UUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
4949

5050
type Object struct {
51-
values AttributeValueMap
52-
PwnableBy EdgeConnections
53-
CanPwn EdgeConnections
54-
parent *Object
55-
sdcache *SecurityDescriptor
56-
sid windowssecurity.SID
57-
children []*Object
51+
values AttributeValueMap
52+
edges [2]EdgeConnections
53+
parent *Object
54+
sdcache *SecurityDescriptor
55+
sid windowssecurity.SID
56+
children []*Object
5857

5958
members map[*Object]struct{}
6059
membersrecursive map[*Object]struct{}
@@ -190,20 +189,20 @@ func (o *Object) AbsorbEx(source *Object, fast bool) {
190189
}
191190
}
192191

193-
for pwntarget, methods := range source.CanPwn {
194-
target.CanPwn[pwntarget] = target.CanPwn[pwntarget].Merge(methods)
195-
delete(source.CanPwn, pwntarget)
192+
for pwntarget, methods := range source.edges[Out] {
193+
target.edges[Out][pwntarget] = target.edges[Out][pwntarget].Merge(methods)
194+
delete(source.edges[Out], pwntarget)
196195

197-
pwntarget.PwnableBy[target] = pwntarget.PwnableBy[target].Merge(methods)
198-
delete(pwntarget.PwnableBy, source)
196+
pwntarget.edges[In][target] = pwntarget.edges[In][target].Merge(methods)
197+
delete(pwntarget.edges[In], source)
199198
}
200199

201-
for pwner, methods := range source.PwnableBy {
202-
target.PwnableBy[pwner] = target.PwnableBy[pwner].Merge(methods)
203-
delete(source.PwnableBy, pwner)
200+
for pwner, methods := range source.edges[In] {
201+
target.edges[In][pwner] = target.edges[In][pwner].Merge(methods)
202+
delete(source.edges[In], pwner)
204203

205-
pwner.CanPwn[target] = pwner.CanPwn[target].Merge(methods)
206-
delete(pwner.CanPwn, source)
204+
pwner.edges[Out][target] = pwner.edges[Out][target].Merge(methods)
205+
delete(pwner.edges[Out], source)
207206
}
208207

209208
// For everyone that is a member of source, relink them to the target
@@ -981,9 +980,9 @@ func (o *Object) init() {
981980
if o.values == nil {
982981
o.values = NewAttributeValueMap()
983982
}
984-
if o.CanPwn == nil || o.PwnableBy == nil {
985-
o.CanPwn = make(EdgeConnections)
986-
o.PwnableBy = make(EdgeConnections)
983+
if o.edges[Out] == nil || o.edges[In] == nil {
984+
o.edges[Out] = make(EdgeConnections)
985+
o.edges[In] = make(EdgeConnections)
987986
}
988987
}
989988

@@ -1107,13 +1106,21 @@ func (o *Object) GUID() uuid.UUID {
11071106
return guid
11081107
}
11091108

1109+
// Look up edge
1110+
func (o *Object) Edge(direction EdgeDirection, target *Object) EdgeBitmap {
1111+
o.lock()
1112+
bm := o.edges[direction][target]
1113+
o.unlock()
1114+
return bm
1115+
}
1116+
11101117
// Register that this object can pwn another object using the given method
11111118
func (o *Object) EdgeTo(target *Object, method Edge) {
1112-
o.PwnsEx(target, method, false)
1119+
o.EdgeToEx(target, method, false)
11131120
}
11141121

11151122
// Enhanched Pwns function that allows us to force the pwn (normally self-pwns are filtered out)
1116-
func (o *Object) PwnsEx(target *Object, method Edge, force bool) {
1123+
func (o *Object) EdgeToEx(target *Object, method Edge, force bool) {
11171124
if !force {
11181125
if o == target { // SID check solves (some) dual-AD analysis problems
11191126
// We don't care about self owns
@@ -1134,14 +1141,49 @@ func (o *Object) PwnsEx(target *Object, method Edge, force bool) {
11341141
}
11351142

11361143
o.lock()
1137-
o.CanPwn.Set(target, method) // Add the connection
1144+
o.edges[Out].Set(target, method) // Add the connection
1145+
o.unlock()
1146+
1147+
target.lock()
1148+
target.edges[In].Set(o, method) // Add the reverse connection too
1149+
target.unlock()
1150+
}
1151+
1152+
func (o *Object) EdgeClear(target *Object, method Edge) {
1153+
o.lock()
1154+
currentedge := o.edges[Out][target]
1155+
if currentedge.IsSet(method) {
1156+
o.edges[Out][target] = currentedge.Clear(method)
1157+
}
11381158
o.unlock()
11391159

11401160
target.lock()
1141-
target.PwnableBy.Set(o, method) // Add the reverse connection too
1161+
currentedge = target.edges[In][o]
1162+
if currentedge.IsSet(method) {
1163+
target.edges[In][o] = currentedge.Clear(method)
1164+
}
11421165
target.unlock()
11431166
}
11441167

1168+
func (o *Object) EdgeCount(direction EdgeDirection) int {
1169+
o.lock()
1170+
result := len(o.edges[direction])
1171+
o.unlock()
1172+
return result
1173+
}
1174+
1175+
func (o *Object) EdgeIterator(direction EdgeDirection, ei func(target *Object, edge EdgeBitmap) bool) {
1176+
o.lock()
1177+
for target, edge := range o.edges[direction] {
1178+
o.unlock()
1179+
if !ei(target, edge) {
1180+
return
1181+
}
1182+
o.lock()
1183+
}
1184+
o.unlock()
1185+
}
1186+
11451187
func (o *Object) ChildOf(parent *Object) {
11461188
o.lock()
11471189
if o.parent != nil {

modules/integrations/activedirectory/analyze/analyze-ad.go

+19-17
Original file line numberDiff line numberDiff line change
@@ -678,12 +678,13 @@ func init() {
678678
}
679679

680680
// Add the DCsync combination flag
681-
for p, methods := range o.PwnableBy {
682-
if methods.IsSet(activedirectory.EdgeDSReplicationGetChanges) && methods.IsSet(activedirectory.EdgeDSReplicationGetChangesAll) {
681+
o.EdgeIterator(engine.In, func(target *engine.Object, edge engine.EdgeBitmap) bool {
682+
if edge.IsSet(activedirectory.EdgeDSReplicationGetChanges) && edge.IsSet(activedirectory.EdgeDSReplicationGetChangesAll) {
683683
// DCsync attack WOT WOT
684-
p.EdgeTo(o, activedirectory.EdgeDCsync)
684+
target.EdgeTo(o, activedirectory.EdgeDCsync)
685685
}
686-
}
686+
return true
687+
})
687688
},
688689
},
689690
)
@@ -1218,12 +1219,13 @@ func init() {
12181219

12191220
// Find the computer AD object if any
12201221
var computer *engine.Object
1221-
for target, edges := range machine.CanPwn {
1222-
if edges.IsSet(EdgeAuthenticatesAs) && target.Type() == engine.ObjectTypeComputer {
1222+
machine.EdgeIterator(engine.Out, func(target *engine.Object, edge engine.EdgeBitmap) bool {
1223+
if edge.IsSet(EdgeAuthenticatesAs) && target.Type() == engine.ObjectTypeComputer {
12231224
computer = target
1224-
break
1225+
return false //break
12251226
}
1226-
}
1227+
return true
1228+
})
12271229

12281230
if computer == nil {
12291231
continue
@@ -1452,7 +1454,7 @@ func init() {
14521454
if sources, found := ao.FindMulti(engine.ObjectSid, engine.AttributeValueSID(sid)); found {
14531455
for _, source := range sources {
14541456
if source.Type() != engine.ObjectTypeForeignSecurityPrincipal {
1455-
source.PwnsEx(foreign, activedirectory.EdgeForeignIdentity, true)
1457+
source.EdgeToEx(foreign, activedirectory.EdgeForeignIdentity, true)
14561458
}
14571459
}
14581460
}
@@ -1469,20 +1471,19 @@ func init() {
14691471
for _, gpo := range ao.Filter(func(o *engine.Object) bool {
14701472
return o.Type() == engine.ObjectTypeGroupPolicyContainer
14711473
}).Slice() {
1472-
for group, methods := range gpo.PwnableBy {
1473-
1474+
gpo.EdgeIterator(engine.In, func(group *engine.Object, methods engine.EdgeBitmap) bool {
14741475
groupname := group.OneAttrString(engine.SAMAccountName)
14751476
if strings.Contains(groupname, "%") {
14761477
// Lowercase for ease
14771478
groupname := strings.ToLower(groupname)
14781479

14791480
// It has some sort of % variable in it, let's go
1480-
for affected, amethods := range gpo.CanPwn {
1481+
gpo.EdgeIterator(engine.Out, func(affected *engine.Object, amethods engine.EdgeBitmap) bool {
14811482
if amethods.IsSet(activedirectory.EdgeAffectedByGPO) && affected.Type() == engine.ObjectTypeComputer {
14821483
netbiosdomain, computername, found := strings.Cut(affected.OneAttrString(engine.DownLevelLogonName), "\\")
14831484
if !found {
14841485
ui.Error().Msgf("Could not parse downlevel logon name %v", affected.OneAttrString(engine.DownLevelLogonName))
1485-
continue
1486+
return true //continue
14861487
}
14871488
computername = strings.TrimRight(computername, "$")
14881489

@@ -1507,7 +1508,7 @@ func init() {
15071508
warnlines++
15081509
} else if len(targetgroups) == 1 {
15091510
for _, edge := range methods.Edges() {
1510-
targetgroups[0].PwnsEx(affected, edge, true)
1511+
targetgroups[0].EdgeToEx(affected, edge, true)
15111512
}
15121513
} else {
15131514
ui.Warn().Msgf("Found multiple groups for %v: %v", realgroup, targetgroups)
@@ -1516,10 +1517,11 @@ func init() {
15161517
}
15171518
}
15181519
}
1519-
}
1520+
return true
1521+
})
15201522
}
1521-
1522-
}
1523+
return true
1524+
})
15231525
}
15241526
if warnlines > 0 {
15251527
ui.Warn().Msgf("%v groups could not be resolved, this could affect analysis results", warnlines)

modules/integrations/localmachine/analyze/postprocessing.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func init() {
1313
for _, o := range ao.Slice() {
1414
if o.HasAttrValue(engine.MetaDataSource, ln) {
1515
if o.HasAttr(activedirectory.ObjectSid) {
16-
if len(o.CanPwn) == 0 && len(o.PwnableBy) == 0 {
16+
if o.EdgeCount(engine.Out)+o.EdgeCount(engine.In) == 0 {
1717
ui.Debug().Msgf("Object has no graph connections: %v", o.Label())
1818
}
1919
warns++

0 commit comments

Comments
 (0)