En esta práctica vamos a estudiar una variante diferente: el programa no necesita cargar una DLL desarrollada por nosotros. En su lugar, utiliza una función disponible en Windows para ejecutar una cadena de comandos desde el proceso objetivo.
El ejemplo permite estudiar varios conceptos importantes de Windows Internals, ejecución de procesos e instrumentación de procesos:
CreateToolhelp32Snapshot;- identificación de procesos mediante PID;
OpenProcess;VirtualAllocEx;WriteProcessMemory;GetProcAddress;CreateRemoteThread;WinExec;rundll32.exe;zipfldr.dll;- creación de procesos secundarios;
- análisis de cadenas de ejecución.
Aviso: debe utilizarse únicamente en sistemas propios o en un laboratorio autorizado. La combinación de manipulación de procesos y ejecución mediante binarios del sistema también aparece en determinadas técnicas de malware, por lo que resulta especialmente interesante estudiarla desde el punto de vista defensivo.
1. ¿De qué va?
En este ejemplo no se usa DLL.
La cadena pasa a ser conceptualmente:
Inyector
│
▼
Notepad
│
▼
WinExec
│
▼
cmd.exe
│
▼
rundll32.exe
│
▼
zipfldr.dll
│
▼
Chrome
Esto permite introducir un concepto muy interesante: la ejecución indirecta utilizando componentes que ya forman parte de Windows.
2. Código completo del laboratorio
El programa completo que sirve como base para la práctica es el siguiente:
#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
// Función para soportar proyectos en modo Unicode y localizar el PID de Notepad
DWORD ObtenerPIDNotepad() {
DWORD pid = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, L"notepad.exe") == 0) {
pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
return pid;
}
int main() {
// Comando que se utilizará como cadena de prueba del laboratorio
const char* comandoSistema =
"cmd.exe /c rundll32.exe "
"zipfldr.dll,RouteTheCall "
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
std::cout
<< "[*] Buscando 'notepad.exe' en el sistema..."
<< std::endl;
DWORD targetPID = ObtenerPIDNotepad();
if (targetPID == 0) {
std::cerr
<< "[-] ERROR: No se encontro Notepad abierto. "
<< "Abre el Bloc de notas antes de ejecutar este programa."
<< std::endl;
system("pause");
return 1;
}
std::cout
<< "[+] Notepad encontrado. PID: "
<< targetPID
<< std::endl;
// 1. Abrir Notepad
HANDLE hProcess =
OpenProcess(
PROCESS_ALL_ACCESS,
FALSE,
targetPID
);
if (!hProcess) {
std::cerr
<< "[-] ERROR: No se pudo abrir el proceso. Codigo: "
<< GetLastError()
<< std::endl;
std::cerr
<< "Nota: Si falla, comprueba los permisos del laboratorio."
<< std::endl;
system("pause");
return 1;
}
// 2. Reservar memoria dentro de Notepad
SIZE_T tamanoComando =
strlen(comandoSistema) + 1;
LPVOID pRemoteMem =
VirtualAllocEx(
hProcess,
NULL,
tamanoComando,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (!pRemoteMem) {
std::cerr
<< "[-] ERROR: No se pudo asignar memoria "
<< "en el proceso remoto. Codigo: "
<< GetLastError()
<< std::endl;
CloseHandle(hProcess);
system("pause");
return 1;
}
// 3. Escribir la cadena en la memoria del proceso
if (!WriteProcessMemory(
hProcess,
pRemoteMem,
(LPVOID)comandoSistema,
tamanoComando,
NULL)) {
std::cerr
<< "[-] ERROR: No se pudo escribir "
<< "en la memoria de Notepad. Codigo: "
<< GetLastError()
<< std::endl;
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hProcess);
system("pause");
return 1;
}
// 4. Obtener kernel32.dll
HMODULE hKernel32 =
GetModuleHandleA("kernel32.dll");
if (!hKernel32) {
std::cerr
<< "[-] ERROR: No se pudo obtener "
<< "kernel32.dll"
<< std::endl;
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hProcess);
system("pause");
return 1;
}
// Obtener la dirección de WinExec
LPVOID pWinExec =
(LPVOID)GetProcAddress(
hKernel32,
"WinExec"
);
if (!pWinExec) {
std::cerr
<< "[-] ERROR: No se pudo localizar "
<< "WinExec."
<< std::endl;
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hProcess);
system("pause");
return 1;
}
// 5. Crear el hilo remoto
HANDLE hThread =
CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pWinExec,
pRemoteMem,
0,
NULL
);
if (!hThread) {
std::cerr
<< "[-] ERROR: No se pudo crear "
<< "el hilo remoto. Codigo: "
<< GetLastError()
<< std::endl;
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hProcess);
system("pause");
return 1;
}
std::cout
<< "[+] Comando enviado con exito."
<< std::endl;
WaitForSingleObject(
hThread,
INFINITE
);
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hThread);
CloseHandle(hProcess);
std::cout
<< "[+] Proceso finalizado correctamente."
<< std::endl;
system("pause");
return 0;
}
3. Primera fase: localizar Notepad
El programa comienza buscando:
notepad.exe
Para ello utiliza:
CreateToolhelp32Snapshot()
y posteriormente:
Process32First()
Process32Next()
La función:
DWORD ObtenerPIDNotepad()
recorre los procesos disponibles hasta encontrar:
_wcsicmp(pe.szExeFile, L"notepad.exe")
Cuando lo encuentra devuelve:
pe.th32ProcessID
El resultado puede ser, por ejemplo:
[+] Notepad encontrado. PID: 15324
El PID será utilizado posteriormente para abrir el proceso.
4. OpenProcess()
El programa obtiene un HANDLE asociado al proceso mediante:
OpenProcess()
Ese handle se almacena en:
HANDLE hProcess
A partir de ese momento el programa dispone de una referencia con la que puede solicitar determinadas operaciones sobre el proceso objetivo, siempre sujetas a los permisos del sistema.
Este concepto es fundamental en Windows:
PID
↓
OpenProcess()
↓
HANDLE
El PID identifica el proceso, mientras que el handle representa el acceso obtenido a ese objeto del sistema.
5. Reservar memoria en el proceso
El siguiente paso utiliza:
VirtualAllocEx()
La función reserva una región de memoria asociada al proceso objetivo.
En nuestro ejemplo se necesita espacio para almacenar una cadena:
comandoSistema
La dirección resultante se almacena en:
LPVOID pRemoteMem
Conceptualmente:
Inyector
│
│ VirtualAllocEx
▼
Notepad
│
└── memoria reservada
6. WriteProcessMemory()
Una vez reservada la memoria, el programa utiliza:
WriteProcessMemory()
para escribir la cadena del comando en esa región.
Por tanto, el proceso objetivo contiene ahora una copia de la cadena que posteriormente recibirá la función de ejecución.
Este patrón:
VirtualAllocEx
↓
WriteProcessMemory
es una combinación especialmente importante desde el punto de vista del análisis de seguridad.
7. Localizando WinExec
El programa obtiene primero:
GetModuleHandleA("kernel32.dll")
y después utiliza:
GetProcAddress()
para localizar:
WinExec
La idea es obtener la dirección de una función de Windows que pueda utilizarse para iniciar un comando.
Conceptualmente:
kernel32.dll
│
▼
GetProcAddress()
│
▼
WinExec
La dirección obtenida se almacena en:
LPVOID pWinExec
8. CreateRemoteThread()
La siguiente operación vuelve a ser:
CreateRemoteThread()
Pero aquí existe una diferencia interesante respecto al laboratorio anterior.
Anteriormente el hilo remoto apuntaba a una función relacionada con la carga de una DLL.
Ahora apunta a:
WinExec
y recibe como argumento la cadena almacenada previamente en la memoria del proceso.
El flujo conceptual es:
Notepad
│
▼
hilo remoto
│
▼
WinExec
│
▼
cadena de comando
Esto permite estudiar cómo una función de la API de Windows puede ser utilizada como punto de ejecución desde un hilo asociado al proceso objetivo.
9. La cadena de ejecución
La parte más interesante de este laboratorio es la cadena de procesos.
Conceptualmente tenemos:
┌────────────────┐
│ Inyector.exe │
└───────┬────────┘
│
│ manipula
▼
┌────────────────┐
│ Notepad.exe │
└───────┬────────┘
│
│ WinExec
▼
┌────────────────┐
│ cmd.exe │
└───────┬────────┘
│
│ /c
▼
┌────────────────┐
│ rundll32.exe │
└───────┬────────┘
│
▼
┌────────────────┐
│ zipfldr.dll │
└───────┬────────┘
│
▼
┌────────────────┐
│ Chrome.exe │
└────────────────┘
Esta representación es muy útil para comprender que la ejecución no termina en Notepad.
Notepad actúa como proceso en cuyo contexto se inicia la primera operación. A partir de ahí aparece una nueva cadena de ejecución.
10. ¿Qué papel tiene rundll32.exe?
rundll32.exe es un ejecutable legítimo de Windows utilizado para invocar determinadas funciones exportadas por DLL.
En este laboratorio aparece en la cadena de ejecución como un intermediario:
cmd.exe
↓
rundll32.exe
↓
DLL
Esto resulta interesante desde el punto de vista defensivo porque un analista no debería fijarse únicamente en el proceso final.
También debe estudiar cómo se ha llegado hasta él.
Por ejemplo:
Chrome.exe
por sí solo puede parecer una ejecución completamente normal.
Sin embargo:
Inyector.exe
↓
Notepad.exe
↓
cmd.exe
↓
rundll32.exe
↓
Chrome.exe
constituye una cadena de procesos mucho más interesante para investigar.
11. zipfldr.dll
El ejemplo utiliza:
zipfldr.dll
una biblioteca nativa relacionada con la funcionalidad de carpetas comprimidas de Windows.
Aquí es importante distinguir entre:
DLL propia
y:
DLL del sistema operativo.
En los primeros laboratorios creábamos:
Dll1.dll
y controlábamos completamente su código.
En este caso el programa utiliza un componente que ya forma parte del sistema.
Esto permite introducir el concepto de living-off-the-land, donde una actividad utiliza herramientas o componentes legítimos disponibles en el propio sistema.
No obstante, que un binario o una DLL sea legítimo no significa que cualquier utilización de los mismos sea legítima. Para determinar el comportamiento hay que analizar el contexto y la cadena completa de ejecución.
12. Una observación importante sobre WinExec
WinExec es una API antigua de Windows.
Para desarrollo moderno existen alternativas más completas, especialmente:
CreateProcess
CreateProcessW
CreateProcessA
Por eso, desde el punto de vista de programación de aplicaciones, WinExec no suele ser la primera opción.
En este laboratorio tiene interés porque permite estudiar una función histórica de Windows y observar cómo puede aparecer en cadenas de ejecución.
13. ¿Por qué es interesante desde el punto de vista de ciberseguridad?
Este laboratorio permite estudiar simultáneamente dos comportamientos diferentes.
Manipulación de procesos
OpenProcess
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
Ejecución de procesos
WinExec
cmd.exe
rundll32.exe
Por separado, cada elemento puede tener usos legítimos.
Pero una cadena completa puede proporcionar mucha más información:
Proceso A
↓
acceso al proceso B
↓
escritura de memoria
↓
hilo remoto
↓
ejecución
↓
cmd.exe
↓
rundll32.exe
↓
nuevo proceso
Esta es precisamente la perspectiva que interesa adoptar durante un análisis de seguridad.
14. ¿Qué podría observar un sistema de detección?
Una herramienta de monitorización podría registrar diferentes elementos de esta actividad:
| Elemento | Información interesante |
|---|---|
OpenProcess | Qué proceso intenta acceder a otro |
VirtualAllocEx | Reserva de memoria en otro proceso |
WriteProcessMemory | Escritura en memoria de otro proceso |
CreateRemoteThread | Creación de un hilo remoto |
WinExec | Ejecución mediante una API antigua |
cmd.exe | Proceso intermedio |
rundll32.exe | Ejecución de una DLL |
zipfldr.dll | Módulo implicado |
Chrome.exe | Proceso final |
| PID/PPID | Relaciones entre procesos |
Una práctica defensiva interesante sería reconstruir el árbol de procesos y comprobar cómo se relacionan todos ellos.
15. Árbol de procesos
El resultado puede representarse conceptualmente como:
Inyector.exe
│
└── Notepad.exe
│
└── cmd.exe
│
└── rundll32.exe
│
└── Chrome.exe
Este árbol resulta especialmente útil en análisis forense y detección.
En lugar de analizar cada proceso de forma aislada, se estudia la relación temporal entre ellos.
16. De la ejecución al análisis defensivo
Una ampliación muy interesante de este laboratorio consiste en utilizar herramientas de monitorización para responder a preguntas como:
- ¿Qué proceso abrió Notepad?
- ¿Quién obtuvo acceso al proceso?
- ¿Quién escribió en su memoria?
- ¿Se creó un hilo remoto?
- ¿Qué proceso apareció posteriormente?
- ¿Cuál es el proceso padre?
- ¿Qué argumentos recibió
cmd.exe? - ¿Qué DLL se cargó mediante
rundll32.exe? - ¿Qué proceso terminó iniciando Chrome?
El objetivo no sería únicamente conseguir que Chrome se ejecute.