mirror of
https://github.com/gatariee/gocheck
synced 2026-06-08 14:22:03 +00:00
added amsi support
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
@@ -0,0 +1,5 @@
|
||||
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
|
||||
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
|
||||
Invoke-Expression 'AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386'
|
||||
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
|
||||
aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd
|
||||
Reference in New Issue
Block a user