edr采集能力要求

近日来做了一些edr的调研,因此整理了下面几条edr能力的需求记录,权当记事,如果有用拿走就好 1、系统兼容性(是否能够覆盖全场景)

2、edr对抗能力(是否会被绕过,被其他的杀掉这种情况)

3、可用性影响(cpu、内存占用大小,确定是否会对系统正常使用造成影响)

4、edr行为分析能力(主要指对于本地文件、木马、恶意进程是否能够识别)

5、edr采集数据维度(看其会采集设备的哪些参数如进程、文件等这种)

6、阻断能力(指的是发现的威胁进程能否停止阻断(例如有的进程level比较高))

7、规则自定义能力(在厂商能力不足的情况下对于一些其他场景能力的补充)

8、自动化响应报送(例如自动发送钉钉这种)

go学习-001

沙箱是一个比较吊毛和比较讨人厌的东西(吐槽完毕)。是用于隔离正在运行的程序的安全机制。这个程序代码可能来自未经验证的或不受信任的第三方、供应商、用户或网站,而不会危害主机或操作系统。
这边搜集一些反沙箱调试的思路,配合写出一些代码

基于go语言

1、操作系统语言检测

沙箱大部分都是国外的英文,所以根据首选项操作系统来判断操作系统语言是不是中文来判断(但是)。使用golang.org/x/sys/windows包。

代码如下:

package src

import (
	"fmt"
	"golang.org/x/sys/windows"
	"os"
)

func CheckLanguage() { // 首字母大写,成为导出的函数
	a, err := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}
	if a[0] != "zh-CN" {
		fmt.Println("英文环境")
		os.Exit(1)
	} else {
		fmt.Println("中文环境")
	}

}

2、操作系统信息

执行wmic命令,返回操作系统信息,根据关键字来判断,也是略简单粗暴。但是这里会被沙箱直接检测到所以我们这边要做一下他的一些免杀,需要更改一些他的命令

package src

import (
	"os/exec"
	"strings"
)

func Check_Virtual() (bool, error) {
	// 检查计算机模型信息
	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))

	// 检查BIOS信息
	cmd = exec.Command("cmd", "/C", "wmic bios get serialnumber")
	biosOutput, err := cmd.Output()
	if err != nil {
		return false, err
	}
	bios := strings.ToLower(string(biosOutput))

	// 检查硬盘驱动器信息
	cmd = exec.Command("cmd", "/C", "wmic diskdrive get model")
	diskOutput, err := cmd.Output()
	if err != nil {
		return false, err
	}
	disk := strings.ToLower(string(diskOutput))

	// 增加更多判断条件,如检测常见虚拟机厂商的标识
	if strings.Contains(model, "virtual") || strings.Contains(model, "vmware") || strings.Contains(model, "kvm") || strings.Contains(model, "virtualbox") ||
		strings.Contains(bios, "virtual") || strings.Contains(bios, "vmware") || strings.Contains(disk, "vbox") || strings.Contains(disk, "virtual") {
		return true, nil
	}
	return false, nil
}

3、检测系统文件

根据虚拟机或沙箱可能存在的一些文件,来进行判断。

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")
}

4、延迟运行

我们这里就当一次老六,因为大部分的检测运行资源有限,可以延迟等一会再进行真实操作。(杀敌一千自损八百)
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
 }
}

5、开机时间检测

因为沙箱检测完都会还原或者充值系统,因此可以通过检测开机事件来判断是否为真实的运行情况。
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
 }
}

6、检测物理内存大小

就跟前面赌沙箱耗费资源多少一样,这里笃定大部分物理机内存都在4gb以上,通过判断大于4来认定为是物理机。
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
}

7、检测cpu数目

跟前面一样,认定cpu数目大于4
func numberOfCPU() (int, error) {
 a := runtime.NumCPU()
 if a < 4 {
  return 0, nil
 } else {
  return 1, nil
 }
}

8、通过检测临时文件数目

正常的物理机中,用户日常的使用导致其肯定会再临时文件夹中存在一定数量的临时文件,因此临时文件夹内的文件数量可以被用来判定运行环境是否为沙箱。
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
}

9、查询硬盘大小

一些恶意程序使用系统硬盘大小作为判断是否运行在虚拟机中的依据,因为虚拟机的硬盘大小通常比较小。你可以通过 Windows API 获取系统硬盘的大小。
package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

func getDiskSize(path string) (uint64, error) {
	var freeBytesAvailable, totalBytes, totalFreeBytes uint64
	pathPtr, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return 0, err
	}

	kernel32 := syscall.NewLazyDLL("kernel32.dll")
	getDiskFreeSpaceExW := kernel32.NewProc("GetDiskFreeSpaceExW")
	_, _, err = getDiskFreeSpaceExW.Call(
		uintptr(unsafe.Pointer(pathPtr)),
		uintptr(unsafe.Pointer(&freeBytesAvailable)),
		uintptr(unsafe.Pointer(&totalBytes)),
		uintptr(unsafe.Pointer(&totalFreeBytes)),
	)

	if err != nil && err.Error() != "The operation completed successfully." {
		return 0, err
	}

	return totalBytes, nil
}

func main() {
	diskSize, err := getDiskSize("C:\\")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Printf("Disk size: %d bytes\n", diskSize)
}

10、反逆向工程

检测内核调试器可以通过检查系统的调试标志来实现。以下是一个简单的示例,使用 NtQuerySystemInformation API 来检查系统是否启用了调试。
package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

const (
	SystemBasicInformation = 0
)

type SYSTEM_BASIC_INFORMATION struct {
	Reserved uint32
	TimerResolution uint32
	PageSize uint32
	NumberOfPhysicalPages uint32
	MinimumUserModeAddress uintptr
	MaximumUserModeAddress uintptr
	ActiveProcessorsAffinityMask uint32
	NumberOfProcessors uint32
}

var (
	modntdll = syscall.NewLazyDLL("ntdll.dll")
	procNtQuerySystemInformation = modntdll.NewProc("NtQuerySystemInformation")
)

func isDebuggerPresent() bool {
	var sysInfo SYSTEM_BASIC_INFORMATION
	ret, _, _ := procNtQuerySystemInformation.Call(
		uintptr(SystemBasicInformation),
		uintptr(unsafe.Pointer(&sysInfo)),
		uintptr(unsafe.Sizeof(sysInfo)),
		0,
	)
	return ret == 0
}

func main() {
	if isDebuggerPresent() {
		fmt.Println("Debugger detected")
	} else {
		fmt.Println("No debugger detected")
	}
}

11、静态文件特征

PE文件节的数量异常,可以使用 Go 的 pe 包来解析 PE 文件,并检查节的数量。这可以帮助检测 PE 文件是否被修改或加壳。
package main

import (
	"debug/pe"
	"fmt"
	"os"
)

func checkPESections(filePath string) error {
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	f, err := pe.NewFile(file)
	if err != nil {
		return err
	}

	sectionCount := len(f.Sections)
	fmt.Printf("Number of sections: %d\n", sectionCount)

	if sectionCount > 10 {
		fmt.Println("Warning: Abnormally high number of sections")
	}

	return nil
}

func main() {
	err := checkPESections("path_to_your_pe_file.exe")
	if err != nil {
		fmt.Println("Error:", err)
	}
}