-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b7a3440
commit 3b0350d
Showing
9 changed files
with
469 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"syscall" | ||
"unsafe" | ||
|
||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
type ScannerConfig struct { | ||
Name string `yaml:"name"` | ||
ScanCmd string `yaml:"scanCmd"` | ||
PosOutput string `yaml:"posOutput"` | ||
// NegOutput string `yaml:"negOutput"` | ||
} | ||
|
||
type Config struct { | ||
Scanners []ScannerConfig `yaml:"scanners"` | ||
} | ||
|
||
func parseConfig() (*Config, error) { | ||
path := "config.yaml" | ||
configInit(path) | ||
var config Config | ||
data, err := os.ReadFile(path) // Use os.ReadFile instead of ioutil.ReadFile | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = yaml.Unmarshal(data, &config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &config, nil | ||
} | ||
|
||
func configInit(path string) { | ||
configTemplate := ` | ||
scanners: | ||
# - name: "AV name" | ||
# scanCmd: "Command for scanning the target file. Use {{file}} as the file name to be scanned. The scanner executable is STRONGLY RECOMMENDED to be in PATH." | ||
# posOutput: "A string in output of positive detection but not in negative" | ||
- name: "ESET" | ||
scanCmd: "ecls /clean-mode=none /no-quarantine {{file}}" | ||
posOutput: ">" | ||
- name: "Windows Defender" | ||
scanCmd: "MpCmdRun.exe -Scan -ScanType 3 -File {{file}} -DisableRemediation -Trace -Level 0x10" | ||
posOutput: "Threat information" | ||
# - name: "Any others"` | ||
|
||
if _, err := os.Stat(path); os.IsNotExist(err) { | ||
// If file doesn't exist, create it | ||
err := os.WriteFile(path, []byte(configTemplate), 0644) | ||
if err != nil { | ||
fmt.Println("create config.yaml error", err) | ||
} | ||
} | ||
} | ||
|
||
// enableVirtualTerminalProcessing enables virtual terminal processing for the given file descriptor. | ||
func enableVirtualTerminalProcessing(fd uintptr) error { | ||
kernel32 := syscall.NewLazyDLL("kernel32.dll") | ||
procGetConsoleMode := kernel32.NewProc("GetConsoleMode") | ||
procSetConsoleMode := kernel32.NewProc("SetConsoleMode") | ||
|
||
var mode uint32 | ||
handle := syscall.Handle(fd) | ||
|
||
// Get the current console mode | ||
r1, _, e1 := syscall.SyscallN(procGetConsoleMode.Addr(), uintptr(handle), uintptr(unsafe.Pointer(&mode))) | ||
if r1 == 0 { | ||
return os.NewSyscallError("GetConsoleMode", e1) | ||
} | ||
|
||
// Enable virtual terminal processing | ||
const enableVirtualTerminalProcessing uint32 = 0x0004 | ||
mode |= enableVirtualTerminalProcessing | ||
|
||
r1, _, e1 = syscall.SyscallN(procSetConsoleMode.Addr(), uintptr(handle), uintptr(mode)) | ||
if r1 == 0 { | ||
return os.NewSyscallError("SetConsoleMode", e1) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
scanners: | ||
# - name: "AV name" | ||
# scanCmd: "Command for scanning the target file. Use {{file}} as the file name to be scanned. The scanner executable is STRONGLY RECOMMENDED to be in PATH." | ||
# posOutput: "A string in output of positive detection but not in negative" | ||
- name: "ESET" | ||
scanCmd: "ecls /clean-mode=none /no-quarantine {{file}}" | ||
posOutput: ">" | ||
- name: "Windows Defender" | ||
scanCmd: "MpCmdRun.exe -Scan -ScanType 3 -File {{file}} -DisableRemediation -Trace -Level 0x10" | ||
posOutput: "Threat information" | ||
# - name: "Any others" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module ThreatCheck | ||
|
||
go 1.21.0 | ||
|
||
require ( | ||
golang.org/x/text v0.14.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
) | ||
|
||
func hexDumpAround(filename string, offset, context int64) error { | ||
const bytesPerLine = 16 | ||
|
||
// Open binary file | ||
file, err := os.Open(filename) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
// Calculate start position, ensure it's not negative | ||
start := offset - context | ||
if start < 0 { | ||
start = 0 | ||
} | ||
end := offset + context | ||
|
||
// Seek to start position | ||
_, err = file.Seek(start, io.SeekStart) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
data := make([]byte, end-start) | ||
_, err = file.Read(data) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
mid := int64(len(data)) / 2 | ||
|
||
// Print hex dump from start to end | ||
for i := int64(0); i < int64(len(data)); i += bytesPerLine { | ||
chunk := data[i:min(i+bytesPerLine, int64(len(data)))] | ||
|
||
// Print the offset in the data stream | ||
fmt.Printf("%08x ", start+i) | ||
|
||
// Print the hex codes | ||
for j, b := range chunk { | ||
fmt.Printf("%02x ", b) | ||
if j%2 == 1 { | ||
fmt.Print(" ") | ||
} | ||
} | ||
|
||
// Pad the line with spaces if it's shorter than bytesPerLine | ||
if len(chunk) < bytesPerLine { | ||
for j := len(chunk); j < bytesPerLine; j++ { | ||
fmt.Print(" ") | ||
if j%2 == 1 { | ||
fmt.Print(" ") | ||
} | ||
} | ||
} | ||
|
||
colored := (i == mid-bytesPerLine || i == mid || i == mid+bytesPerLine) | ||
if colored { | ||
fmt.Print("\033[1;31m") | ||
} | ||
|
||
// Print the ASCII representation | ||
for _, b := range chunk { | ||
if b >= 32 && b <= 126 { | ||
fmt.Printf("%c", b) | ||
} else { | ||
fmt.Print("·") | ||
} | ||
} | ||
|
||
if colored { | ||
fmt.Print("\033[0m") | ||
} | ||
|
||
fmt.Println() | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// min returns the smaller of x or y. | ||
func min(x, y int64) int64 { | ||
if x < y { | ||
return x | ||
} | ||
return y | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
) | ||
|
||
func main() { | ||
// Enable virtual terminal processing on stdout to allow ANSI escape codes on Windows 10+. | ||
err := enableVirtualTerminalProcessing(os.Stdout.Fd()) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Failed to enable virtual terminal processing: %v\n", err) | ||
} | ||
|
||
printBanner() | ||
|
||
// Check for correct usage | ||
if len(os.Args) != 3 { | ||
fmt.Println("ThreatCheck is a tool written in Go to spot malicious content using external scanners.") | ||
fmt.Println("Usage: ./ThreatCheck -f <target_file>") | ||
os.Exit(1) | ||
} | ||
|
||
filePath := os.Args[2] | ||
|
||
// Parse the configuration file | ||
config, err := parseConfig() | ||
if err != nil { | ||
fmt.Printf("Error parsing config file: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
// Iterate over all scanners and scan the file | ||
for _, scanner := range config.Scanners { | ||
printSeparator() | ||
fmt.Printf("Scanning with \033[1;32m%s\033[0m\n\n", scanner.Name) | ||
totalResult, err := overAllScan(scanner, filePath) | ||
if err != nil || totalResult == false { | ||
fmt.Printf("Not found with %s\n", scanner.Name) | ||
continue | ||
} | ||
fmt.Printf("Details:\n\n") | ||
result, err := findMaliciousSection(scanner, filePath) | ||
if err != nil { | ||
fmt.Printf("Error scanning file with %s: %v\n", scanner.Name, err) | ||
continue | ||
} | ||
|
||
fmt.Println(result) | ||
// scanFile(scanner, filePath) | ||
} | ||
printSeparator() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package main | ||
|
||
import "fmt" | ||
|
||
func printBanner() { | ||
fmt.Println("\033[0;31m" + ` | ||
* ) ) ) ( ) ) | ||
) /( ( /( ( ( ) ( /( )\ ( /( ( ( /( | ||
( )(_)) )\()) )( ))\ ( /( )\()) (((_) )\()) ))\ ( )\())` + "\033[1;31m" + ` | ||
(_(_()) ((_)\ (()\ /((_) )(_)) (_))/ )\___ ((_)\ /((_) )\ ((_)\` + "\033[1;33m" + ` | ||
|_ _| | |(_) ((_) (_)) ((_)_ | |_ ((/ __| | |(_) (_)) ((_) | |(_)` + "\033[1;37m" + ` | ||
| | | '' \ | '_| / -_) / _` + "`" + ` | | _| | (__ | '' \ / -_) / _| | / / | ||
|_| |_||_| |_| \___| \__,_| \__| \___| |_||_| \___| \__| |_\_\ | ||
` + "\033[0m") | ||
} | ||
|
||
func printSeparator() { | ||
fmt.Println("\n\033[1;37m==================================================================================\033[0m") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
) | ||
|
||
func scanFile(scanner ScannerConfig, filePath string) (bool, error) { | ||
// Replace placeholder with actual file path | ||
scanCommand := strings.Replace(scanner.ScanCmd, "{{file}}", filePath, -1) | ||
// fmt.Println(scanCommand) | ||
cmdParts := strings.Fields(scanCommand) | ||
cmd := exec.Command(cmdParts[0], cmdParts[1:]...) | ||
|
||
output, _ := cmd.CombinedOutput() | ||
// fmt.Println(output) | ||
|
||
// Check if the output contains the positive or negative signature | ||
if strings.Contains(string(output), scanner.PosOutput) { | ||
return true, nil | ||
} else { | ||
return false, nil | ||
} | ||
} | ||
|
||
func findMaliciousSection(scanner ScannerConfig, filePath string) (string, error) { | ||
data, err := os.ReadFile(filePath) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// Define the initial bounds for the search | ||
left, right := 0, len(data) | ||
lastMaliciousEnd := right // Store the end index of the last detected malicious content | ||
|
||
// fmt.Println("last mid left right") | ||
|
||
for left < right { | ||
percentage := (len(data) - right + left) * 100 / len(data) | ||
fmt.Printf("\rChecking done: %d%%", percentage) | ||
|
||
// Define the current section to scan: always start from 0 to include the file head | ||
mid := (left + right) / 2 | ||
currentData := data[:mid] | ||
|
||
// Write the current data to a temporary file and scan it | ||
tempFile, err := os.CreateTemp("", "section_search_") | ||
if err != nil { | ||
return "", err | ||
} | ||
tempFileName := tempFile.Name() | ||
_, err = tempFile.Write(currentData) | ||
if err != nil { | ||
tempFile.Close() | ||
os.Remove(tempFileName) // Clean up the file | ||
return "", err | ||
} | ||
tempFile.Close() | ||
|
||
malicious, err := scanFile(scanner, tempFileName) | ||
if err != nil { | ||
os.Remove(tempFileName) // Clean up the file | ||
return "", err | ||
} | ||
os.Remove(tempFileName) // Clean up the file | ||
|
||
if malicious { | ||
// The malicious content is in the current range, narrow down the scope | ||
lastMaliciousEnd = mid | ||
right = mid | ||
// fmt.Println("malicious") | ||
} else { | ||
// The malicious content is not in the current range, expand the scope | ||
left = mid + 1 | ||
// fmt.Println("not malicious") | ||
} | ||
// fmt.Println(lastMaliciousEnd, " ", mid, " ", left, " ", right) | ||
} | ||
|
||
fmt.Printf("\r%s\r", strings.Repeat(" ", 50)) | ||
|
||
// The malicious content is between 'left' and 'lastMaliciousEnd' | ||
if lastMaliciousEnd == len(data) { | ||
return "Malicious content not found.", nil | ||
} else { | ||
err := hexDumpAround(filePath, int64(lastMaliciousEnd), int64(128)) | ||
if err != nil { | ||
fmt.Println("Error:", err) | ||
} | ||
|
||
return fmt.Sprintf("\nMalicious content detected at: %08x (%d bytes)", int64(lastMaliciousEnd), lastMaliciousEnd), nil | ||
} | ||
} | ||
|
||
func overAllScan(scanner ScannerConfig, filePath string) (bool, error) { | ||
// Replace placeholder with actual file path | ||
scanCommand := strings.Replace(scanner.ScanCmd, "{{file}}", filePath, -1) | ||
// fmt.Println(scanCommand) | ||
cmdParts := strings.Fields(scanCommand) | ||
cmd := exec.Command(cmdParts[0], cmdParts[1:]...) | ||
|
||
output, _ := cmd.CombinedOutput() | ||
fmt.Println("Report for the whole file:\n\n", string(output)) | ||
|
||
// Check if the output contains the positive or negative signature | ||
if strings.Contains(string(output), scanner.PosOutput) { | ||
return true, nil | ||
} else { | ||
return false, nil | ||
} | ||
} |
Oops, something went wrong.