Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
PACHAKUTlQ authored Dec 19, 2023
1 parent b7a3440 commit 3b0350d
Show file tree
Hide file tree
Showing 9 changed files with 469 additions and 0 deletions.
61 changes: 61 additions & 0 deletions assets/banner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions config.go
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
}
11 changes: 11 additions & 0 deletions config.yaml
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"
8 changes: 8 additions & 0 deletions go.mod
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
)
95 changes: 95 additions & 0 deletions hex.go
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
}
53 changes: 53 additions & 0 deletions main.go
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()
}
19 changes: 19 additions & 0 deletions printings.go
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")
}
113 changes: 113 additions & 0 deletions scan.go
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
}
}
Loading

0 comments on commit 3b0350d

Please sign in to comment.