Para confeccionar un sistema que proteja a nuestras aplicaciones de las inyecciones dll no deseadas de código hay que entender cómo se realiza el proceso de inyección. Todo proceso de inyección precisa de varias acciones comunes como son abrir un proceso externo, escribir en su espacio de direcciones de memoria, crear un hilo remoto en dicho proceso y obligarle con ello a cargar una dll no deseada. Existe otro tipo de inyección, más sofisticada, que no necesita crear un hilo remoto sino que suspende un hilo ya existente para inyectarle de forma directa un pequeño shellcode que se encargará de cargar la dll. Ambas inyecciones han sido tratadas en el foro en diversas ocasiones. Las API implicadas en estas técnicas son varias pero las más importantes son:
OpenProcess
WriteProcessMemory
CreateRemoteThread
OpenThread
LoadLibrary
SetWindowsHookEx
Conocidas estas bases, la forma de evitar la inyección, requeriría del bloqueo del uso de esas APIs, pero nos encontramos con un problema, son legales. De todas ellas, quizás CreateRemoteThread es la más sospechosa, ¿Qué hace un proceso externo tratando de ejecutar un hilo en mi aplicación? Es por eso que muchos antivirus la consideran heurísticamente perniciosa.
La inyección dll siempre termina provocando una llamada a LoadLibrary en el proceso atacado, si la bloqueamos, bloqueamos la inyección, pero también impediremos la carga dinámica de APIs, costumbre que muchas aplicaciones tienen.
Quizás la mejor forma de protegerse contra inyecciones de código sea en el mismo momento de escribir nuestra nueva aplicación, sabiendo de antemano cuantas y cuales dlls han de cargarse y evitar el resto.
En definitiva, el asunto no es fácil de manejar, sobre todo cuando tratamos de proteger aplicaciones de terceros.
La siguiente pregunta que nos hacemos es como conseguimos bloquear las APIs, la respuesta es mediante un Hook a las mismas y la inyección a todos los procesos del sistema y los nacientes. Además deberemos intentar discernir cuando su uso es legal y cuando es un ataque.
Otra cuestión es si la protección la queremos a nivel usuario o Kernel, En el segundo caso precisamos de un driver para cada versión de S.O. que realice el Hook a las Apis que queremos controlar. Sobre este tema ya publique alguna cosa en el foro platino y la necesidad de ser muy cuidadosos, no solo para no provocar la caída del S.O. sino para evitar los propios A.V. También os mostré una técnica para ello.
Lo que ahora os voy a mostrar es una prueba de concepto de un sistema de protección general que inyecta una dll a todos los procesos del sistema y a los nacientes para realizar un hook a tres APIs: OpenProcess, CreateRemoteThread y LdrLoadDll. La última API es donde termina cualquier llamada a LoadLibrary y últimamente puesta de moda por ser indocumentada, se está usando para saltarse cualquier control de LoadLibrary. También incluiré un sistema de control que informará del proceso sobra el que esas APIs no tienen permiso de actuación, dejándolas libres para el resto. Esto es así para evitar el bloqueo en acciones legales.
Ya publiqué como realizar un Hook a la API y las diferentes técnicas, en este caso hay que hacerlo mediante un trampolín.
El sistema de comunicación entre la dll que inyectaremos y el proceso de control, será la memoria compartida, enviando el Pid del proceso que queremos proteger.
//---------------------------------------------------------------------------- // La APIs Hookeadas function NewOpenProcess(dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwProcessId: DWORD): THANDLE; stdcall; begin Result:= THANDLE(0); if (dwProcessId = PDWORD(Memory.Buffer)^) and (dwDesiredAccess and PROCESS_VM_WRITE = PROCESS_VM_WRITE) then LockMessage('OpenProcess') else Result:= OldOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); end; function NewCreateRemoteThread(hProcess: THandle; lpThreadAttributes: Pointer; dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine; lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall; begin Result:= THANDLE(0); LockMessage('CreateRemoteThread'); end; function NewLdrLoadDll(PathToFile: PWCHAR; Flags: ULONG; ModuleFileName: PUNICODE_STRING; ModuleHandle: PHANDLE): DWORD; stdcall; begin Result:= ERROR_ACCESS_DENIED; if GetCurrentProcessId = PDWORD(Memory.Buffer)^ then LockMessage('LdrLoadDll') else Result:= OldLdrLoadDll(PathToFile, Flags, ModuleFileName, ModuleHandle); end;
Este código forma parte de la dll que inyectaremos en todos los procesos. Como podéis observar, el bloqueo de CreateRemoteThread es total para todos los procesos, el bloqueo de OpenProcess se circunscribe al permiso de escritura sobre el proceso a proteger y LdrLoadDll se bloquea totalmente para el proceso a proteger, esto le va a impedir la carga dinámica de funciones en dlls aunque sea legal. Solo sabiendo previamente que dll necesita, o conociendo la dll maligna podríamos hacer un control selectivo.
LockMessage se encarga de avisar del bloqueo informando del proceso inyector, en este punto se pueden añadir acciones como matar dicho proceso.
La técnica del Hook a las APIs es la misma que usé en el tutorial HOOK a la API en delphi y en C (trampolín). Se tata se un sistema automatizado para realizar un hook a cualquier API en modo usuario, no sirve para Ring0.
La forma de conseguir una inyección en todos los procesos se la vamos a dejar al mismo Windows realizando un hook global. La dll también incorpora un sistema de liberación. Puede cambiarse por inyección individualizada con un código como este, pero deberemos conocer que proceso nos ataca:
function InjectCRT(Pid: integer; dll: PCHAR): BOOL; var hThread: THANDLE; ExitCode: DWORD; hLib: Pointer;//LPTHREAD_START_ROUTINE; hProc: THANDLE; Buffer: Pointer; begin Result:= false; if(dll^ = #0) then exit; hThread:= 0; ExitCode:= 0; hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid); if hProc<>0 then begin Buffer:= VirtualAllocEx(hProc, nil, lstrlen(dll)+1, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); if Buffer<>nil then begin if WriteProcessMemory(hProc, Buffer, dll, lstrlen(dll)+1, PDWORD(0)^) then begin hLib:= GetProcAddress(GetModuleHandle('Kernel32.dll'), 'LoadLibraryA'); if hLib <> nil then begin hThread:= CreateRemoteThread(hProc, nil, 0, hLib, Buffer, 0, PDWORD(0)^); if hThread <> 0 then begin // libero la memoria localizada... WaitForSingleObject(hThread, INFINITE); // Espero a que se cree y termine el Thread GetExitCodeThread(hThread, ExitCode); // Extraigo el código de terminación del Thread CloseHandle(hThread); // Cierro el Handle hThread end; end; end; VirtualFreeEx(hProc, Buffer, 0, MEM_RELEASE); // Libero memoria end; CloseHandle(hProc); // Cierro el Handle hProc end; Result:= (ExitCode <> 0); end;
He de decir que la inyección solo se va a producir en procesos de 32 bits si nuestro código se escribe en esa plataforma. También he de decir que a la hora de desinstalar el sistema puede haber problemas con alguna aplicación, en concreto Acrobat Reader se cae porque tiene un sistema de protección anti hook, también Firefox puede caer en la batalla por su costumbre de cargar APIs de forma dinámica, son daños colaterales que se evitan seleccionando un poco mejor nuestro ataque defensivo y según la circunstancia particular, puede que no importe mucho.
He probado la protección con una aplicación inyectora de mi autoría que nunca he publicado y que me permite seleccionar la inyección o liberarla. No estaría de más usar otro tipo de inyectores que pueden encontrarse en la web y cuya seguridad en cuanto a portar malware es dudosa.
Subo el código completo y funcional, con un programita control que se encarga de realizar la inyección global, cuya funcionalidad está en la propia dll y de la comunicación con la misma. En principio solo protege a un proceso, pero nada nos impide enviar un array de procesos.
El tema queda abierto a sugerencias, ya que alguien me pidió que lo tratara en el foro.
Espero que sea de utilidad.
Saludos.