Necropolis v1 release
This commit is contained in:
@@ -0,0 +1,529 @@
|
||||
//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")
|
||||
}
|
||||
Reference in New Issue
Block a user