530 lines
12 KiB
Go
530 lines
12 KiB
Go
//go:build antivm && linux
|
|
|
|
package core
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
func init() {
|
|
registerLinuxChecks()
|
|
}
|
|
|
|
func registerLinuxChecks() {
|
|
// 100% certainty
|
|
registerCheck("firmware_dmi", firmwareDMI, 100)
|
|
|
|
// 95% certainty
|
|
registerCheck("devices_pci", devicesPCI, 95)
|
|
|
|
// 80% certainty
|
|
registerCheck("uml_cpu", umlCPU, 80)
|
|
registerCheck("kgt_signature", kgtSignature, 80)
|
|
|
|
// 75% certainty
|
|
registerCheck("nsjail_pid", nsjailPID, 75)
|
|
|
|
// 70% certainty
|
|
registerCheck("vmware_ioports", vmwareIOPorts, 70)
|
|
registerCheck("cgroup", cgroupVM, 70)
|
|
registerCheck("qemu_fw_cfg", qemuFWCfg, 70)
|
|
|
|
// 65% certainty
|
|
registerCheck("dmi_product", dmiProduct, 65)
|
|
registerCheck("dmi_sys_vendor", dmiSysVendor, 65)
|
|
registerCheck("dmi_chassis_vendor", dmiChassisVendor, 65)
|
|
registerCheck("vmware_iomem", vmwareIOMEM, 65)
|
|
registerCheck("vmware_dmesg", vmwareDMESG, 65)
|
|
|
|
// 55% certainty
|
|
registerCheck("dmidecode", dmidecodeCheck, 55)
|
|
registerCheck("dmesg", dmesgCheck, 55)
|
|
|
|
// 50% certainty
|
|
registerCheck("dmi_scan", dmiScan, 50)
|
|
registerCheck("smbios_vm_bit", smbiosVMBit, 50)
|
|
registerCheck("thread_mismatch_linux", threadMismatchLinux, 50)
|
|
registerCheck("amd_sev_msr", amdSEVMSR, 50)
|
|
|
|
// 40% certainty
|
|
registerCheck("vmware_scsi", vmwareSCSI, 40)
|
|
registerCheck("qemu_virtual_dmi", qemuVirtualDMI, 40)
|
|
registerCheck("processes_linux", processesLinux, 40)
|
|
|
|
// 35% certainty
|
|
registerCheck("systemd_virt", systemdVirt, 35)
|
|
registerCheck("hwmon", hwmonMissing, 35)
|
|
|
|
// 30% certainty
|
|
registerCheck("wsl_proc", wslProc, 30)
|
|
|
|
// 20% certainty
|
|
registerCheck("hypervisor_dir", hypervisorDir, 20)
|
|
registerCheck("temperature", temperatureCheck, 20)
|
|
registerCheck("qemu_usb", qemuUSB, 20)
|
|
registerCheck("ctype", dmiChassisType, 20)
|
|
|
|
// 15% certainty
|
|
registerCheck("vbox_module", vboxModule, 15)
|
|
registerCheck("sysinfo_proc", sysinfoProc, 15)
|
|
registerCheck("file_access_history", fileAccessHistory, 15)
|
|
|
|
// 10% certainty
|
|
registerCheck("linux_user_host", linuxUserHost, 10)
|
|
|
|
// 5% certainty
|
|
registerCheck("podman_file", podmanFile, 5)
|
|
registerCheck("bluestacks", bluestacksFolders, 5)
|
|
registerCheck("kmsg", kmsgCheck, 5)
|
|
}
|
|
|
|
// --- Linux 100% ---
|
|
|
|
func firmwareDMI() bool {
|
|
// Check all DMI entries for VM signatures
|
|
dirs := []string{
|
|
"/sys/class/dmi/id",
|
|
"/sys/devices/virtual/dmi/id",
|
|
}
|
|
vmBrands := []string{"virtualbox", "vmware", "qemu", "kvm", "innotek",
|
|
"xen", "bochs", "hyper-v", "microsoft", "oracle", "oem"}
|
|
for _, d := range dirs {
|
|
entries, err := os.ReadDir(d)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(d, e.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
content := strings.ToLower(string(data))
|
|
for _, brand := range vmBrands {
|
|
if strings.Contains(content, brand) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 95% ---
|
|
|
|
func devicesPCI() bool {
|
|
entries, err := os.ReadDir("/sys/bus/pci/devices")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// VM vendors: 8086 (Intel), 15ad (VMware), 80ee (VirtualBox),
|
|
// 1af4 (Red Hat/QEMU), 10de (NVIDIA in passthrough)
|
|
vmVendors := []string{"15ad", "80ee", "1af4"}
|
|
for _, e := range entries {
|
|
vendorFile := filepath.Join("/sys/bus/pci/devices", e.Name(), "vendor")
|
|
data, err := os.ReadFile(vendorFile)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
vendor := strings.TrimSpace(string(data))
|
|
for _, v := range vmVendors {
|
|
if strings.Contains(strings.ToLower(vendor), v) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 80% ---
|
|
|
|
func umlCPU() bool {
|
|
content := readFile("/proc/cpuinfo")
|
|
return strings.Contains(content, "UML")
|
|
}
|
|
|
|
func kgtSignature() bool {
|
|
content := readFile("/proc/cpuinfo")
|
|
return strings.Contains(content, "Intel KGT") || strings.Contains(content, "TGKIntel")
|
|
}
|
|
|
|
// --- Linux 75% ---
|
|
|
|
func nsjailPID() bool {
|
|
content := readFile("/proc/self/status")
|
|
if content == "" {
|
|
return false
|
|
}
|
|
// nsjail makes processes see PID 1 in the namespace
|
|
for _, line := range strings.Split(content, "\n") {
|
|
if strings.HasPrefix(line, "Pid:") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 2 && parts[1] == "1" {
|
|
// Check if nsjail env vars are present
|
|
if os.Getenv("NSJAIL") != "" {
|
|
return true
|
|
}
|
|
// Check cgroup for nsjail
|
|
cg := readFile("/proc/self/cgroup")
|
|
return strings.Contains(cg, "nsjail")
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 70% ---
|
|
|
|
func vmwareIOPorts() bool {
|
|
return strings.Contains(readFile("/proc/ioports"), "VMware")
|
|
}
|
|
|
|
func cgroupVM() bool {
|
|
content := readFile("/proc/1/cgroup")
|
|
if content == "" {
|
|
return false
|
|
}
|
|
for _, s := range []string{"docker", "lxc", "kubepods"} {
|
|
if strings.Contains(content, s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func qemuFWCfg() bool {
|
|
if _, err := os.Stat("/sys/firmware/qemu_fw_cfg"); err == nil {
|
|
return true
|
|
}
|
|
if _, err := os.Stat("/proc/device-tree/fw-cfg"); err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 65% ---
|
|
|
|
func dmiProduct() bool {
|
|
name := readFile("/sys/class/dmi/id/product_name")
|
|
switch {
|
|
case strings.Contains(name, "VirtualBox"):
|
|
return true
|
|
case strings.Contains(name, "VMware"):
|
|
return true
|
|
case name == "KVM" || name == "QEMU":
|
|
return true
|
|
case strings.Contains(name, "Bochs"):
|
|
return true
|
|
case strings.Contains(name, "Hyper-V"):
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func dmiSysVendor() bool {
|
|
vendor := readFile("/sys/class/dmi/id/sys_vendor")
|
|
switch {
|
|
case strings.Contains(vendor, "innotek"):
|
|
return true
|
|
case strings.Contains(vendor, "Xen"):
|
|
return true
|
|
case strings.Contains(vendor, "Microsoft"):
|
|
return true
|
|
case strings.Contains(vendor, "QEMU"):
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func dmiChassisVendor() bool {
|
|
vendor := readFile("/sys/class/dmi/id/chassis_vendor")
|
|
return strings.Contains(vendor, "Oracle") || strings.Contains(vendor, "innotek")
|
|
}
|
|
|
|
func vmwareIOMEM() bool {
|
|
return strings.Contains(readFile("/proc/iomem"), "VMware")
|
|
}
|
|
|
|
func vmwareDMESG() bool {
|
|
content := readFile("/var/log/dmesg")
|
|
if content == "" {
|
|
content = readFile("/var/log/kern.log")
|
|
}
|
|
return strings.Contains(content, "VMware") || strings.Contains(content, "vmware")
|
|
}
|
|
|
|
// --- Linux 55% ---
|
|
|
|
func dmidecodeCheck() bool {
|
|
// Check all DMI sysfs files for VM brands
|
|
entries, err := os.ReadDir("/sys/class/dmi/id")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
patterns := []string{"virtualbox", "vmware", "qemu", "kvm", "innotek", "xen", "bochs"}
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(filepath.Join("/sys/class/dmi/id", e.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
content := strings.ToLower(string(data))
|
|
for _, p := range patterns {
|
|
if strings.Contains(content, p) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func dmesgCheck() bool {
|
|
content := readFile("/var/log/dmesg")
|
|
if content == "" {
|
|
return false
|
|
}
|
|
for _, s := range []string{"Hypervisor", "VMware", "VirtualBox", "QEMU", "KVM", "Xen"} {
|
|
if strings.Contains(content, s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 50% ---
|
|
|
|
func dmiScan() bool {
|
|
return firmwareDMI()
|
|
}
|
|
|
|
func smbiosVMBit() bool {
|
|
// Check /sys/firmware/dmi/tables for SMBIOS entry point
|
|
data, err := os.ReadFile("/sys/firmware/dmi/tables/smbios_entry_point")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// SMBIOS v3+ has a VM bit at offset 0x09
|
|
if len(data) >= 0x1F {
|
|
if data[0x1E]&0x01 != 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func threadMismatchLinux() bool {
|
|
cpuContent := readFile("/proc/cpuinfo")
|
|
if cpuContent == "" {
|
|
return false
|
|
}
|
|
n := 0
|
|
for _, line := range strings.Split(cpuContent, "\n") {
|
|
if strings.HasPrefix(line, "processor") {
|
|
n++
|
|
}
|
|
}
|
|
// If we see CPU brand but low core count, likely VM
|
|
if n < 2 && strings.Contains(cpuContent, "Intel") || strings.Contains(cpuContent, "AMD") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func amdSEVMSR() bool {
|
|
// Check /sys/kernel/security/sev for AMD SEV/SEV-ES/SEV-SNP
|
|
if data, err := os.ReadFile("/sys/kernel/security/sev/version"); err == nil {
|
|
return strings.TrimSpace(string(data)) != ""
|
|
}
|
|
// Also check MSR via /dev/cpu (requires root)
|
|
data, err := os.ReadFile("/dev/cpu/0/msr")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(data) > 0x800 {
|
|
_ = data[0x800]
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 40% ---
|
|
|
|
func vmwareSCSI() bool {
|
|
return strings.Contains(readFile("/proc/scsi/scsi"), "VMware")
|
|
}
|
|
|
|
func qemuVirtualDMI() bool {
|
|
content := readFile("/sys/devices/virtual/dmi/id/product_name")
|
|
if strings.Contains(content, "QEMU") || strings.Contains(content, "KVM") || strings.Contains(content, "VirtualBox") {
|
|
return true
|
|
}
|
|
vendor := readFile("/sys/devices/virtual/dmi/id/sys_vendor")
|
|
return strings.Contains(vendor, "QEMU")
|
|
}
|
|
|
|
func processesLinux() bool {
|
|
vmProcs := []string{"vbox", "vmware", "VBox", "VMware", "qemu", "QEMU", "xen", "Xen"}
|
|
entries, err := os.ReadDir("/proc")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
// Only check numeric directories (process PIDs)
|
|
if e.Name()[0] < '0' || e.Name()[0] > '9' {
|
|
continue
|
|
}
|
|
cmdline, err := os.ReadFile(filepath.Join("/proc", e.Name(), "comm"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
comm := strings.TrimSpace(string(cmdline))
|
|
for _, p := range vmProcs {
|
|
if strings.EqualFold(comm, p) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 35% ---
|
|
|
|
func systemdVirt() bool {
|
|
// Check /sys/devices/virtual/dmi/id for VM indicators
|
|
// In containers, /sys might not have DMI info
|
|
vendor := readFile("/sys/devices/virtual/dmi/id/sys_vendor")
|
|
if vendor == "" {
|
|
// No DMI info — likely container
|
|
return cgroupVM() || dockerEnv()
|
|
}
|
|
return strings.Contains(vendor, "QEMU") || strings.Contains(vendor, "Xen")
|
|
}
|
|
|
|
func hwmonMissing() bool {
|
|
_, err := os.ReadDir("/sys/class/hwmon")
|
|
return err != nil
|
|
}
|
|
|
|
// --- Linux 30% ---
|
|
|
|
func wslProc() bool {
|
|
version := readFile("/proc/version")
|
|
if strings.Contains(version, "Microsoft") || strings.Contains(version, "WSL") || strings.Contains(version, "microsoft") {
|
|
return true
|
|
}
|
|
osrelease := readFile("/proc/sys/kernel/osrelease")
|
|
return strings.Contains(osrelease, "Microsoft") || strings.Contains(osrelease, "WSL")
|
|
}
|
|
|
|
// --- Linux 20% ---
|
|
|
|
func hypervisorDir() bool {
|
|
entries, err := os.ReadDir("/sys/hypervisor")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return len(entries) > 0
|
|
}
|
|
|
|
func temperatureCheck() bool {
|
|
entries, err := os.ReadDir("/sys/class/thermal")
|
|
if err != nil {
|
|
return true // no thermal info = likely VM
|
|
}
|
|
return len(entries) < 2
|
|
}
|
|
|
|
func qemuUSB() bool {
|
|
data, err := os.ReadFile("/sys/kernel/debug/usb/devices")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return strings.Contains(string(data), "QEMU")
|
|
}
|
|
|
|
func dmiChassisType() bool {
|
|
typ := readFile("/sys/class/dmi/id/chassis_type")
|
|
return typ == "" || typ == "0"
|
|
}
|
|
|
|
// --- Linux 15% ---
|
|
|
|
func vboxModule() bool {
|
|
content := readFile("/proc/modules")
|
|
return strings.Contains(content, "vbox")
|
|
}
|
|
|
|
func sysinfoProc() bool {
|
|
content := readFile("/proc/sysinfo")
|
|
return strings.Contains(content, "QEMU") || strings.Contains(content, "KVM") || strings.Contains(content, "Xen")
|
|
}
|
|
|
|
func fileAccessHistory() bool {
|
|
// Check if there's very low file access history (common in fresh VMs)
|
|
_, err := os.Stat("/root/.bash_history")
|
|
hasHistory := err == nil
|
|
_, err2 := os.Stat("/home")
|
|
hasHome := err2 == nil
|
|
return hasHome && !hasHistory
|
|
}
|
|
|
|
// --- Linux 10% ---
|
|
|
|
func linuxUserHost() bool {
|
|
hostname := readFile("/proc/sys/kernel/hostname")
|
|
if hostname == "" {
|
|
return false
|
|
}
|
|
lower := strings.ToLower(hostname)
|
|
for _, s := range []string{"ubuntu", "debian", "localhost"} {
|
|
if lower == s || strings.HasPrefix(lower, s+"-") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// --- Linux 5% ---
|
|
|
|
func podmanFile() bool {
|
|
_, err := os.Stat("/run/.containerenv")
|
|
return err == nil
|
|
}
|
|
|
|
func bluestacksFolders() bool {
|
|
entries, err := os.ReadDir("/mnt")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, e := range entries {
|
|
if strings.Contains(strings.ToLower(e.Name()), "bluestacks") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func kmsgCheck() bool {
|
|
f, err := os.OpenFile("/dev/kmsg", syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer f.Close()
|
|
buf := make([]byte, 4096)
|
|
n, err := f.Read(buf)
|
|
if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
content := strings.ToLower(string(buf[:n]))
|
|
return strings.Contains(content, "hypervisor") || strings.Contains(content, "kvm")
|
|
}
|