added amsi support

This commit is contained in:
gatari
2024-02-09 23:52:30 +08:00
parent f5bd9a211e
commit f9deda9ea4
15 changed files with 24452 additions and 182 deletions
+14 -3
View File
@@ -12,13 +12,24 @@ Usage:
gocheck check [flags]
Flags:
-a, --amsi Use AMSI to scan the binary
-d, --defender Use Windows Defender to scan the binary
-a, --amsi Use AMSI to scan a file
-d, --defender Use Windows Defender to scan a binary
-f, --file string Binary to check
-h, --help help for check
```
## Quick Use
The `check` cobra flag is only used for ease of extensibility in the event that I finally decide to integrate [Ghidra's Headless Analyzer](https://static.grumpycoder.net/pixel/support/analyzeHeadlessREADME.html) with `gocheck`. For ease of use, you can actually completely omit the `check` flag and directly pass the file to `gocheck` as an argument.
```cmd
$ gocheck <file> /optional:args
```
> This may be changed in the future.
## Windows Defender
## Installation
You can install `gocheck` from `go install`
```bash
+4 -2
View File
@@ -30,6 +30,8 @@ var checkCmd = &cobra.Command{
defender, _ := cmd.Flags().GetBool("defender")
if !amsi && !defender {
/* Assume that the user wants to use defender */
utils.PrintInfo("No flags provided, defaulting to Windows Defender")
defender = true
}
@@ -44,12 +46,12 @@ var checkCmd = &cobra.Command{
utils.PrintErr(err.Error())
return
}
utils.PrintInfo(fmt.Sprintf("Found Windows Defender at %s", defender_path))
} else {
/* If we're not using defender, we can assume an empty string */
defender_path = ""
}
utils.PrintInfo(fmt.Sprintf("Found Windows Defender at %s", defender_path))
token := scanner.Scanner{
File: file,
Amsi: amsi,
+1
View File
@@ -5,6 +5,7 @@ go 1.21.1
require github.com/spf13/cobra v1.8.0
require (
github.com/garethjensen/amsi v0.0.0-20180303015433-eb8d5f740669
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
+2
View File
@@ -1,4 +1,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/garethjensen/amsi v0.0.0-20180303015433-eb8d5f740669 h1:AiQEkP6s81dg3b/+wwY4u2OJIPHyV28cSP9R1lV7imA=
github.com/garethjensen/amsi v0.0.0-20180303015433-eb8d5f740669/go.mod h1:n0f50c5UcAiPwx6RJ9Ky0m376hHJqmKXjDyqmjnGajk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+11 -6
View File
@@ -7,14 +7,19 @@ import (
"github.com/gatariee/gocheck/cmd"
)
var suffixes = []string{".exe", ".dll", ".sys", ".drv", ".ps1"}
func main() {
/* If the second arg ends with a .exe, we'll just assume that the user meant to run the check command */
if len(os.Args) > 1 && strings.HasSuffix(os.Args[1], ".exe") {
/*
What are you gonna do about it? (ง'̀-'́)ง
*/
os.Args = append([]string{os.Args[0], "check"}, os.Args[1:]...)
if len(os.Args) > 1 {
for _, suffix := range suffixes {
if strings.HasSuffix(os.Args[1], suffix) {
os.Args = append([]string{os.Args[0], "check"}, os.Args[1:]...)
}
}
}
/*
What are you gonna do about it? (ง'̀-'́)ง
*/
cmd.Execute()
}
+154
View File
@@ -0,0 +1,154 @@
package scanner
import (
"fmt"
"os"
"path/filepath"
"github.com/garethjensen/amsi"
utils "github.com/gatariee/gocheck/utils"
)
type AMSIScanner struct{}
func newAMSIScanner() *AMSIScanner {
return &AMSIScanner{}
}
func (as *AMSIScanner) Scan(filePath string) (amsi.ScanResult, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return 0, err
}
fileData, err := os.ReadFile(filePath)
if err != nil {
return 0, err
}
err = amsi.Initialize()
if err != nil {
return 0, err
}
defer amsi.Uninitialize()
session := amsi.OpenSession()
defer amsi.CloseSession(session)
// sample := []byte("AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386")
/* Thanks, Rasta :P */
/* https://github.com/rasta-mouse/ThreatCheck/blob/master/ThreatCheck/ThreatCheck/Amsi/AmsiInstance.cs */
// test := session.ScanBuffer(sample)
// fmt.Println(test)
result := session.ScanBuffer(fileData)
return result, nil
}
func (as *AMSIScanner) Go(amsi_instance *AMSIScanner, file_path string) (int, error) {
start, err := os.ReadFile(file_path)
if err != nil {
return 0, err
}
size := len(start)
utils.PrintInfo(fmt.Sprintf("Scanning %s, analyzing %d bytes...", file_path, size))
tempDir := filepath.Join(".", "temp")
err = os.MkdirAll(tempDir, 0o755)
if err != nil {
return 0, err
}
testFilePath := filepath.Join(tempDir, "test")
defer os.RemoveAll(tempDir)
lastGood := 0
upperBound := len(start)
mid := upperBound / 2
ok := false
for upperBound-lastGood > 1 {
err := os.WriteFile(testFilePath, start[0:mid], 0o644)
if err != nil {
return 0, err
}
result, err := amsi_instance.Scan(testFilePath)
if err != nil {
return 0, err
}
if result == amsi.ResultDetected {
upperBound = mid
ok = true
} else {
lastGood = mid
}
mid = (upperBound + lastGood) / 2
}
if !ok {
return 0, fmt.Errorf("unable to isolate bad bytes, uh oh. :(")
}
return mid, nil
}
func ScanAMSI(filePath string) error {
scanner := newAMSIScanner()
original_file, err := os.ReadFile(filePath)
if err != nil {
return err
}
result, err := scanner.Scan(filePath)
if err != nil {
return err
}
switch result {
/*
https://learn.microsoft.com/en-us/windows/win32/api/amsi/ne-amsi-amsi_result
*/
case amsi.ResultClean:
utils.PrintInfo("No threats found, got 'AMSI_RESULT_CLEAN'")
return nil
case amsi.ResultNotDetected:
utils.PrintInfo("No threats found, got 'AMSI_RESULT_NOT_DETECTED'")
return nil
case amsi.ResultBlockedByAdminStart:
utils.PrintErr("Scan was blocked for some reason, got 'AMSI_RESULT_BLOCKED_BY_ADMIN_START'")
case amsi.ResultBlockedByAdminEnd:
utils.PrintErr("Scan was blocked for some reason, got 'AMSI_RESULT_BLOCKED_BY_ADMIN_END'")
case amsi.ResultDetected:
/* Continue, let's do our binary search now! */
utils.PrintInfo("Threat detected in original file, beginning AMSI binary search...")
offset, err := scanner.Go(scanner, filePath)
if err != nil {
return err
}
utils.PrintInfo(fmt.Sprintf("Isolated bad bytes at offset 0x%X in the original file [approximately %d / %d bytes]", offset, offset, len(original_file)))
start := offset - 64
if start < 0 {
start = 0
}
end := offset + 64
if end > len(original_file) {
end = len(original_file)
}
/* Start printing the hex dump */
threatData := original_file[start:end]
fmt.Println(HexDump(threatData))
default:
utils.PrintErr(fmt.Sprintf("Unknown result: %d", result))
}
return nil
}
+31
View File
@@ -0,0 +1,31 @@
package scanner
import (
"encoding/hex"
"strings"
)
func HexDump(data []byte) string {
dump := hex.Dump(data)
return dump
}
func extractThreat(scanOutput string) string {
lines := strings.Split(scanOutput, "\n")
threatInfo := ""
for _, line := range lines {
if strings.HasPrefix(line, "Threat ") {
threatInfo = line
break
}
}
if threatInfo != "" {
threatInfo = strings.Split(threatInfo, ": ")[1]
return threatInfo
}
return "No specific threat information found"
}
+14 -157
View File
@@ -1,21 +1,18 @@
package scanner
import (
"bytes"
"encoding/hex"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
utils "github.com/gatariee/gocheck/utils"
)
func newDefenderScanner(path string) *DefenderScanner {
return &DefenderScanner{Path: path}
type Scanner struct {
File string
Amsi bool
Defender bool
EnginePath string
}
type ScanResult string
const (
NoThreatFound ScanResult = "NoThreatFound"
ThreatFound ScanResult = "ThreatFound"
@@ -25,158 +22,18 @@ const (
Error ScanResult = "Error"
)
func (ds *DefenderScanner) Scan(filePath string, threat_names chan string) ScanResult {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return FileNotFound
}
// TODO: convert to abs before passing into the function, if this errors out- it's not actually handled properly in the caller.
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return Error
}
cmd := exec.Command(ds.Path, "-Scan", "-ScanType", "3", "-File", absFilePath, "-DisableRemediation", "-Trace", "-Level", "0x10")
var out, stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Run()
stdOut := out.String()
if strings.Contains(stdOut, "Threat ") {
threat := extractThreat(stdOut)
/* yeah, there has to be a better way to do this. */
threat_names <- threat
return ThreatFound
}
return NoThreatFound
}
func HexDump(data []byte) {
dump := hex.Dump(data)
fmt.Println(dump)
}
func extractThreat(scanOutput string) string {
lines := strings.Split(scanOutput, "\n")
threatInfo := ""
for _, line := range lines {
if strings.HasPrefix(line, "Threat ") {
threatInfo = line
break
}
}
if threatInfo != "" {
threatInfo = strings.Split(threatInfo, ": ")[1]
return threatInfo
}
return "No specific threat information found"
}
func HalfSplitter(originalArray []byte, lastGood int) []byte {
newSize := (len(originalArray)-lastGood)/2 + lastGood
return originalArray[:newSize]
}
func Overshot(originalArray []byte, splitArraySize int) []byte {
newSize := (len(originalArray)-splitArraySize)/2 + splitArraySize
return originalArray[:newSize]
}
func Run(token Scanner) {
scanner := newDefenderScanner(token.EnginePath)
original_file, err := os.ReadFile(token.File)
if err != nil {
fmt.Println(err)
return
}
threat_names := make(chan string)
threat_list := make([]string, 0)
go func() {
for threat := range threat_names {
threat_list = append(threat_list, threat)
}
}()
size := len(original_file)
utils.PrintInfo(fmt.Sprintf("Scanning %s, analyzing %d bytes...", token.File, size))
if scanner.Scan(token.File, threat_names) == NoThreatFound {
utils.PrintInfo("File looks clean, no threat detected")
return
} else {
utils.PrintErr("Threat detected in the original file, beginning binary search...")
}
tempDir := filepath.Join(os.TempDir(), "gocheck")
os.MkdirAll(tempDir, 0o755)
testFilePath := filepath.Join(tempDir, "testfile.exe")
lastGood := 0
upperBound := len(original_file)
mid := upperBound / 2
threatFound := false
for upperBound-lastGood > 1 {
// utils.PrintInfo(fmt.Sprintf("scanning from %d to %d bytes", lastGood, mid))
err := os.WriteFile(testFilePath, original_file[0:mid], 0o644)
if token.Defender {
err := ScanWindef(token)
if err != nil {
utils.PrintErr(fmt.Sprintf("failed to write to test file: %s", err))
return
utils.PrintErr(err.Error())
}
if scanner.Scan(testFilePath, threat_names) == ThreatFound {
threatFound = true
upperBound = mid
} else {
lastGood = mid
}
mid = lastGood + (upperBound-lastGood)/2
}
if threatFound {
utils.PrintNewLine()
utils.PrintErr(fmt.Sprintf("Isolated bad bytes at offset 0x%X in the original file [approximately %d / %d bytes]", lastGood, lastGood, size))
start := lastGood - 64
if start < 0 {
start = 0
if token.Amsi {
err := ScanAMSI(token.File)
if err != nil {
utils.PrintErr(err.Error())
}
end := mid + 64
if end > len(original_file) {
end = len(original_file)
}
threatData := original_file[start:end]
HexDump(threatData)
uniqueThreats := make(map[string]bool)
for _, threat := range threat_list {
uniqueThreats[threat] = true
}
for threat := range uniqueThreats {
utils.PrintErr(threat)
}
utils.PrintNewLine()
} else {
utils.PrintInfo("No threat detected")
}
os.Remove(testFilePath)
}
-14
View File
@@ -1,14 +0,0 @@
package scanner
type Scanner struct {
File string
Amsi bool
Defender bool
EnginePath string
}
type ScanResult string
type DefenderScanner struct {
Path string
}
+165
View File
@@ -0,0 +1,165 @@
package scanner
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
utils "github.com/gatariee/gocheck/utils"
)
type DefenderScanner struct {
Path string
}
func newDefenderScanner(path string) *DefenderScanner {
return &DefenderScanner{Path: path}
}
func (ds *DefenderScanner) Scan(filePath string, threat_names chan string) ScanResult {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return FileNotFound
}
// TODO: convert to abs before passing into the function, if this errors out- it's not actually handled properly in the caller.
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return Error
}
cmd := exec.Command(ds.Path, "-Scan", "-ScanType", "3", "-File", absFilePath, "-DisableRemediation", "-Trace", "-Level", "0x10")
var out, stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Run()
stdOut := out.String()
if strings.Contains(stdOut, "Threat ") {
threat := extractThreat(stdOut)
/* yeah, there has to be a better way to do this. */
threat_names <- threat
return ThreatFound
}
return NoThreatFound
}
func ScanWindef(token Scanner) error {
/* Setup */
scanner := newDefenderScanner(token.EnginePath)
original_file, err := os.ReadFile(token.File)
if err != nil {
return err
}
/* Setup a channel to keep track of threat names */
threat_names := make(chan string)
threat_list := make([]string, 0)
go func() {
for threat := range threat_names {
threat_list = append(threat_list, threat)
}
}()
size := len(original_file)
utils.PrintInfo(fmt.Sprintf("Scanning %s, analyzing %d bytes...", token.File, size))
/* Scan the original file, if the original file isn't flagged as malicious, don't begin the binary search */
if scanner.Scan(token.File, threat_names) == NoThreatFound {
utils.PrintInfo("File looks clean, no threat detected")
return nil
} else {
utils.PrintErr("Threat detected in the original file, beginning binary search...")
}
/* Create a temporary directory to store the scanning files */
tempDir := filepath.Join(os.TempDir(), "gocheck")
/* TODO: Check whether the parent directory has an exclusion, or to perform a sanity check to ensure that MpCmdRun.exe is actually working */
os.MkdirAll(tempDir, 0o755)
testFilePath := filepath.Join(tempDir, "testfile.exe")
lastGood := 0 // lower range
upperBound := len(original_file) // upper range
mid := upperBound / 2 // pivot point
threatFound := false
for upperBound-lastGood > 1 {
// utils.PrintInfo(fmt.Sprintf("scanning from %d to %d bytes", lastGood, mid))
err := os.WriteFile(testFilePath, original_file[0:mid], 0o644)
if err != nil {
utils.PrintErr(fmt.Sprintf("failed to write to test file: %s", err))
return err
}
if scanner.Scan(testFilePath, threat_names) == ThreatFound {
/*
Since we found a threat in the slice, we'll set the upper range to whatever the pivot point is.
*/
threatFound = true
upperBound = mid
} else {
/*
We didn't find a threat in this slice, so we flip to the other half of the slice.
*/
lastGood = mid // lower range becomes the pivot (middle)
}
mid = lastGood + (upperBound-lastGood)/2
}
if threatFound {
/*
This only hits once the binary search has been exhausted, i.e: the range between the upperBound and lastGood is 0
TODO: Check if an off-by-one error appears here for binaries with an odd number of bytes lol.
*/
utils.PrintNewLine()
utils.PrintErr(fmt.Sprintf("Isolated bad bytes at offset 0x%X in the original file [approximately %d / %d bytes]", lastGood, lastGood, size))
/* Add 64 bytes before the offset */
start := lastGood - 64
if start < 0 {
start = 0
}
/* Add 64 bytes after the offset */
end := mid + 64
if end > len(original_file) {
end = len(original_file)
}
/* Start printing the hex dump */
threatData := original_file[start:end]
fmt.Println(HexDump(threatData))
uniqueThreats := make(map[string]bool)
for _, threat := range threat_list {
uniqueThreats[threat] = true
}
for threat := range uniqueThreats {
utils.PrintErr(threat)
}
utils.PrintNewLine()
} else {
utils.PrintInfo("No threat detected, but the original file was flagged as malicious. The bad bytes are likely at the very end of the binary.")
}
/* End */
os.Remove(testFilePath)
close(threat_names)
return nil
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,393 @@
function Get-DomainComputer {
<#
.SYNOPSIS
Return all computers or specific computer objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all computer objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Unconstrained
Switch. Return computer objects that have unconstrained delegation.
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER Printers
Switch. Return only printers.
.PARAMETER SPN
Return computers with a specific service principal name, wildcards accepted.
.PARAMETER OperatingSystem
Return computers with a specific operating system, wildcards accepted.
.PARAMETER ServicePack
Return computers with a specific service pack, wildcards accepted.
.PARAMETER SiteName
Return computers in the specific AD Site name, wildcards accepted.
.PARAMETER Ping
Switch. Ping each host to ensure it's up before enumerating.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainComputer
Returns the current computers in current domain.
.EXAMPLE
Get-DomainComputer -SPN mssql* -Domain testlab.local
Returns all MS SQL servers in the testlab.local domain.
.EXAMPLE
Get-DomainComputer -UACFilter TRUSTED_FOR_DELEGATION,SERVER_TRUST_ACCOUNT -Properties dnshostname
Return the dns hostnames of servers trusted for delegation.
.EXAMPLE
Get-DomainComputer -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -Unconstrained
Search the specified OU for computeres that allow unconstrained delegation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainComputer -Credential $Cred
.OUTPUTS
PowerView.Computer
Custom PSObject with translated computer property fields.
PowerView.Computer.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[OutputType('PowerView.Computer')]
[OutputType('PowerView.Computer.Raw')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity,
[Switch]
$Unconstrained,
[Switch]
$TrustedToAuth,
[Switch]
$Printers,
[ValidateNotNullOrEmpty()]
[Alias('ServicePrincipalName')]
[String]
$SPN,
[ValidateNotNullOrEmpty()]
[String]
$OperatingSystem,
[ValidateNotNullOrEmpty()]
[String]
$ServicePack,
[ValidateNotNullOrEmpty()]
[String]
$SiteName,
[Switch]
$Ping,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$CompSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($CompSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$CompSearcher = Get-DomainSearcher @SearcherArguments
if (-not $CompSearcher) {
Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
else {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['Printers']) {
Write-Verbose '[Get-DomainComputer] Searching for printers'
$Filter += '(objectCategory=printQueue)'
}
if ($PSBoundParameters['SPN']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
$Filter += "(servicePrincipalName=$SPN)"
}
if ($PSBoundParameters['OperatingSystem']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
$Filter += "(operatingsystem=$OperatingSystem)"
}
if ($PSBoundParameters['ServicePack']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
if ($PSBoundParameters['SiteName']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
$Filter += "(serverreferencebl=$SiteName)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
else { $Results = $CompSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $_.Properties
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
$CompSearcher.dispose()
}
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
Invoke-Expression 'AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386'
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd