227 lines
4.9 KiB
Go
227 lines
4.9 KiB
Go
//go:build antivm
|
|
|
|
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type vmCheck struct {
|
|
name string
|
|
detect func() bool
|
|
certainty int
|
|
}
|
|
|
|
// allChecks populated by init() with platform-appropriate techniques
|
|
var allChecks []vmCheck
|
|
|
|
func init() {
|
|
registerCheck("hypervisor_cpu", hypervisorCPU, 100)
|
|
registerCheck("bochs_cpu", bochsCPU, 100)
|
|
registerCheck("cpuid_signature", cpuidSignature, 95)
|
|
registerCheck("cpu_brand_vm", cpuBrandVM, 95)
|
|
registerCheck("thread_mismatch", threadMismatch, 50)
|
|
registerCheck("thread_count", threadCount, 35)
|
|
registerCheck("container_env", containerEnv, 30)
|
|
registerCheck("dockerenv", dockerEnv, 30)
|
|
registerCheck("azure_hostname", azureHostname, 30)
|
|
|
|
registerCheck("mac_vmware", macVMware, 20)
|
|
registerCheck("mac_virtualbox", macVirtualBox, 20)
|
|
registerCheck("mac_qemu", macQEMU, 20)
|
|
registerCheck("mac_xen", macXen, 20)
|
|
}
|
|
|
|
const detectionThreshold = 50
|
|
|
|
func registerCheck(name string, fn func() bool, certainty int) {
|
|
allChecks = append(allChecks, vmCheck{name: name, detect: fn, certainty: certainty})
|
|
}
|
|
|
|
func DetectVM() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Printf("[antivm] FATAL: %v", r)
|
|
}
|
|
}()
|
|
|
|
sort.Slice(allChecks, func(i, j int) bool {
|
|
return allChecks[i].certainty > allChecks[j].certainty
|
|
})
|
|
|
|
var score int
|
|
var hits []string
|
|
for _, c := range allChecks {
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Printf("[antivm] panic in %s: %v", c.name, r)
|
|
}
|
|
}()
|
|
if c.detect() {
|
|
score += c.certainty
|
|
hits = append(hits, fmt.Sprintf("%s(+%d)", c.name, c.certainty))
|
|
log.Printf("[antivm] +%d%% %s (total: %d)", c.certainty, c.name, score)
|
|
}
|
|
}()
|
|
}
|
|
|
|
if score > 100 {
|
|
score = 100
|
|
}
|
|
|
|
log.Printf("[antivm] score %d/%d (%d techniques)", score, detectionThreshold, len(hits))
|
|
|
|
if score >= detectionThreshold {
|
|
log.Printf("[antivm] threshold met, exiting")
|
|
os.Exit(0)
|
|
}
|
|
log.Printf("[antivm] below threshold, continuing")
|
|
}
|
|
|
|
// Cross-platform checks
|
|
|
|
var vmwarePrefixes = []string{
|
|
"00:50:56", "00:0c:29", "00:05:69", "00:1c:14",
|
|
"00:3c:7d", "00:0f:4b",
|
|
}
|
|
|
|
var vboxPrefixes = []string{"08:00:27"}
|
|
var qemuPrefixes = []string{"52:54:00"}
|
|
var xenPrefixes = []string{"00:16:3e"}
|
|
|
|
func macMatch(prefixes []string) bool {
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, iface := range ifaces {
|
|
mac := iface.HardwareAddr.String()
|
|
for _, p := range prefixes {
|
|
if strings.HasPrefix(strings.ToLower(mac), strings.ToLower(p)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func macVMware() bool { return macMatch(vmwarePrefixes) }
|
|
func macVirtualBox() bool { return macMatch(vboxPrefixes) }
|
|
func macQEMU() bool { return macMatch(qemuPrefixes) }
|
|
func macXen() bool { return macMatch(xenPrefixes) }
|
|
|
|
func threadCount() bool {
|
|
return runtime.NumCPU() < 2
|
|
}
|
|
|
|
func threadMismatch() bool {
|
|
n := runtime.NumCPU()
|
|
// Most modern CPUs have >=4 threads
|
|
return n < 4
|
|
}
|
|
|
|
func containerEnv() bool {
|
|
for _, e := range []string{"container", "DOCKER", "KUBERNETES", "LXC"} {
|
|
if os.Getenv(e) != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func dockerEnv() bool {
|
|
if _, err := os.Stat("/.dockerenv"); err == nil {
|
|
return true
|
|
}
|
|
if _, err := os.Stat("/run/.containerenv"); err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hypervisorCPU() bool {
|
|
if runtime.GOARCH != "amd64" {
|
|
return false
|
|
}
|
|
if runtime.GOOS == "linux" {
|
|
data, err := os.ReadFile("/proc/cpuinfo")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.Contains(line, "hypervisor") {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func cpuBrandVM() bool {
|
|
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
|
|
return false
|
|
}
|
|
data, err := os.ReadFile("/proc/cpuinfo")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
content := string(data)
|
|
for _, s := range []string{"QEMU", "KVM", "VirtualBox", "VMware", "Bochs",
|
|
"KVMKVMKVM", "Microsoft Hv", "VBoxVBoxVBox",
|
|
"VMwareVMware", "XenVMMXenVMM", "ACRNACRNACRN"} {
|
|
if strings.Contains(content, s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func bochsCPU() bool {
|
|
if runtime.GOOS != "linux" {
|
|
return false
|
|
}
|
|
content := readFile("/proc/cpuinfo")
|
|
return strings.Contains(content, "Bochs") || strings.Contains(content, "bochs")
|
|
}
|
|
|
|
func cpuidSignature() bool {
|
|
if runtime.GOOS != "linux" {
|
|
return false
|
|
}
|
|
content := readFile("/proc/cpuinfo")
|
|
for _, sig := range []string{
|
|
"KVMKVMKVM", "Microsoft Hv", "prl hyperv",
|
|
"VBoxVBoxVBox", "VMwareVMware", "XenVMMXenVMM",
|
|
"ACRNACRNACRN",
|
|
} {
|
|
if strings.Contains(content, sig) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func azureHostname() bool {
|
|
hostname, err := os.Hostname()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return strings.HasSuffix(strings.ToLower(hostname), "internal.cloudapp.net")
|
|
}
|
|
|
|
func readFile(path string) string {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(data))
|
|
}
|
|
|