Antes que nada decir que yo no soy el autor de este tuto,tan solo es una traducción....(franchute),el autor es Ivan Lefou.
Desde Vista y 7 el funcionamiento de la API "CreateRemoteThread" ha cambiado un poco. De hecho, ya no es posible inyectar hilos o threads en los procesos que no nos pertenecen o para ser más precisos los de otras sesiones. Esto es problemático cuando uno quiere jugar con el sistema operativo, por ejemplo, inyectar DLL en los procesos de otros usuarios. Después de mirar más de cerca lo que no funciona, yo sugiero una pequeña solución simple pero efectiva para eludir esta restricción.
Trabajamos en el contexto de un usuario logueado en win 7 que pertenece al grupo "Administradores" con el nivel de UAC por defecto.
Antes de poder hacer un "CreateRemoteThread" en el proceso de otro usuario debemos ser capaces de abrir un handle o identificador para el proceso que se describe con "OpenProcess" ya que al pertenecer al grupo de administradores pueden activar "SeDebugPrivilege" y lanzar nuestro proceso con el token de Administrador obtenido al registrarse con el comando "runas". Por defecto con el UAC, cuando se ha logueado como Administrador no trabaja directamente como Administrador, pero si con token de usuario. Cuando una acción administrativa es necesaria, el UAC (si está activado) pedirá una advertencia para lanzar la aplicación con el token de Administrador.
La documentación de CreateRemoteThread especifíca lo siguiente :
Al menos este comportamiento está documentado...Terminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process.
Al intentar llamar a CreateRemoteThread con un handle sobre el proceso de otro usuario devuelve NULL y GetLastError devuelve 8 (ERROR_NOT_ENOUGH_MEMORY). Ya que dices WTF?!. Traceamos la función para ver donde se originó el error. Antes de profundizar más no nos olvidemos de que, desde Win7 funciones de kernel32.dll se han reducido en varios archivos DLL virtuales. Aunque la tabla muestra que las importaciones de CreateRemoteThread kernel32.dll se encuentran en la API de MS-Windows-Core-ProcessThreads-L1-1-0.dll donde se llega al final en kernelbase.dll y lo mismo pasa con el resto de funciones de kernel32.dll.
Al final caemos en kernelbase! CreateRemoteThreadEx. Trazando esta función, el error no es de syscall NtCreateThreadEx tampoco de CsrClientCallServer, que devuelve 0xc0000001 (STATUS_UNSUCCESSFUL). CreateRemoteThreadEx transforma este error en 0xC00000017 (STATUS_NO_MEMORY) y actualiza el GetLastError BaseSetLastNTError a 8. La buena noticia es que esta no es en si misma la llamada al sistema que falla, sino una llamada al subsistema "csrss" que hace que todo salga mal o equivocado. De hecho, el hilo o thread se creó con el CREATE_SUSPENDED, que espera una llamada o ResumeThread (ZwResumeThread) para ser lanzado. Hay en realidad una llamada a CreateRemoteThreadEx, pero sólo se ejecuta en caso de éxito de CsrClientCallServer.
Pequeña descripción del proceso csrss.exe en Win 7 ( Windows Internals 5th) :
De hecho, cada usuario tiene su propio proceso csrss.exe. Aquí está el prototipo de CsrClientCallServer y el código de llamada en CreateRemoteThreadEx :Session space contains information global to each session.A session consists of the processes and other system objects (such as the window station, desktops, and windows) that represent a single user’s logon session. Each session has a session-specific paged pool area used by the kernel-mode portion of the Windows subsystem (Win32k.sys) to allocate session-private GUI data structures. In addition, each session has its own copy of the Windows subsystem process (Csrss.exe) and logon process (Winlogon.exe). The session manager process (Smss.exe) is responsible for creating new sessions, which includes loading a session-private copy of Win32k.sys, creating the sessionprivate object manager namespace, and creating the session-specific instances of the Csrss and Winlogon processes.
Código: Seleccionar todo
NTSTATUS
NTAPI
CsrClientCallServer(
struct _CSR_API_MESSAGE *Request,
struct _CSR_CAPTURE_BUFFER *CaptureBuffer OPTIONAL,
ULONG ApiNumber,
ULONG RequestLength
);
kernelbase.dll
7597BD24 6A 0C PUSH 0C
7597BD26 68 01000100 PUSH 10001
7597BD2B 53 PUSH EBX
7597BD2C 8D85 F0FDFFFF LEA EAX, DWORD PTR SS:[EBP-210]
7597BD32 50 PUSH EAX
7597BD33 FF15 00129775 CALL NEAR DWORD PTR DS:[<&ntdll.CsrClientCallServer>] ; ntdll.CsrClientCallServer
7597BD39 8B85 10FEFFFF MOV EAX, DWORD PTR SS:[EBP-1F0]
7597BD3F 8985 E8FDFFFF MOV DWORD PTR SS:[EBP-218], EAX
7597BD45 399D E8FDFFFF CMP DWORD PTR SS:[EBP-218], EBX
7597BD4B 0F8C 13D80100 JL KERNELBA.75999564
Código: Seleccionar todo
#define CSR_MAKE_API_NUMBER( DllIndex, ApiIndex ) \
(CSR_API_NUMBER)(((DllIndex) << 16) | (ApiIndex))
Código: Seleccionar todo
unsigned int __stdcall CsrLockProcessByClientId(int a1, int ret)
{
int v2; // ebx@1
int v3; // edi@1
int v4; // esi@1
int v6; // edx@6
unsigned int v7; // [sp+18h] [bp+Ch]@1
v3 = ret;
*(_DWORD *)ret = 0;
v2 = CsrRootProcess + 8;
v7 = 0xC0000001u;
v4 = CsrRootProcess + 8;
RtlEnterCriticalSection(&CsrProcessStructureLock);
while ( *(_DWORD *)(v4 - 8 ) != a1 )
{
v4 = *(_DWORD *)v4;
if ( v4 == v2 )
{
RtlLeaveCriticalSection(&CsrProcessStructureLock);
return v7;
}
}
v7 = 0;
CsrLockedReferenceProcess(v4 - 8);
*(_DWORD *)v3 = v6;
return v7;
}
En Win 7 esto cambia. Ya no es la lista de todos los procesos que tenemos en CSRSS, pero si los que están en el mismo período de sesiones (mismo usuario) que este proceso. Y "CsrLockProcessByClientId" es llamado por BaseSrvCreateThread para actualizar, precisamente, la lista de threads o hilos que pertenecen al proceso de destino. Pero dado que el proceso se encuentra en otra sesión "csrsrv! CsrLockProcessByClientId" falla con 0xc0000001 (STATUS_UNSUCCESSFUL) transmitido por "basesrv! BaseSrvCreateThread". Aquí está el origen del valor de retorno de CsrClientCallServer.
Si bien es posible volcar el "CLIENT_ID" de Procesos de "CsrRootProcess" utilizando un kernel debugger (WinDBG):
Código: Seleccionar todo
1: kd> p
eax=024df7a0 ebx=00000000 ecx=000003b0 edx=00000008 esi=024df7f0 edi=003f7ea0
eip=75884458 esp=024df770 ebp=024df7a4 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
basesrv!BaseSrvCreateThread+0x3f:
001b:75884458 ff1504108875 call dword ptr [basesrv!_imp__CsrLockProcessByClientId (75881004)] ds:0023:75881004={CSRSRV!CsrLockProcessByClientId (75895ad5)}
1: kd> !list -x "dt nt!_CLIENT_ID @$extret-8 " poi(CsrRootProcess)+8
+0x000 UniqueProcess : 0x000001a8
+0x004 UniqueThread : 0x000001ac
+0x000 UniqueProcess : 0x000001e4
+0x004 UniqueThread : 0x000001e8
+0x000 UniqueProcess : 0x000000f0
+0x004 UniqueThread : 0x0000006c
+0x000 UniqueProcess : 0x00000550
+0x004 UniqueThread : 0x0000078c
+0x000 UniqueProcess : 0x00000480
+0x004 UniqueThread : 0x0000043c
+0x000 UniqueProcess : 0x00000258
+0x004 UniqueThread : 0x000004dc
+0x000 UniqueProcess : 0x0000019c
+0x004 UniqueThread : 0x00000388
+0x000 UniqueProcess : 0x00000b20
+0x004 UniqueThread : 0x00000b24
+0x000 UniqueProcess : 0x00000b28
+0x004 UniqueThread : 0x00000b2c
+0x000 UniqueProcess : 0x00000d1c
+0x004 UniqueThread : 0x00000d20
+0x000 UniqueProcess : 0x000006d0
+0x004 UniqueThread : 0x00000288
+0x000 UniqueProcess : 0x00000120
+0x004 UniqueThread : 0x00000428
+0x000 UniqueProcess : 0x00000f5c
+0x004 UniqueThread : 0x000001d8
Existen varias soluciones para poder crear un thread en el proceso de otra sesion o usuario. La primera y más directa sería pasar directamente a través de la API nativa "ZwCreateThreadEx". Porque no? pero se necesita de una API para manipular las estructuras no documentadas.
Otra posibilidad sería pasar por "ntdll!RtlCreateUserThread" como mostré aquí. Parecido en este caso donde se manejan estas estructuras y API's no documentadas.
Por mi parte, opté por ser más simple. Como "CsrClientCallServer" es importado por kernelbase.dll. Sólo hay que hookear la IAT para reemplazar temporalmente por nuestra función "CsrClientCallServer" que siempre devuelve un valor de éxito. Por lo menos podemos confiar en los headers del SDK para hacerlo. Quiero decir que esto es una solución como cualquier otra y no ofrece nada realmente innovador, pero al menos funciona con las API documentadas y por lo tanto, fiable. La única pega es que no es suficiente para cambiar el valor de retorno de "CsrClientCallServer", sino también el campo ReturnValue de la estructura "CSR_API_MESSAGE" pasada como argumento. ¿Por qué se cambia directamente con el disass, es decir, se actualiza el valor al que apunta ebp-0x1f0.
El POC permite inyectar una DLL en cualquier proceso sobre Win 7. Mientras se ejecuta desde una shell o cuenta de administrador. La DLL inyectada llama a "OutputDebugString" donde el comportamiento a cambiado también en Win 7. A continuación actualizar el valor de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter.
Descarga del código y binarios:
[Enlace externo eliminado para invitados]
Notas:
Cuando el autor nombra CsrWalker se refiere a un programa creado por el que sirve para encontrar procesos ocultos en modo usuario.
Cuando el autor dice " Otra posibilidad sería pasar por "ntdll!RtlCreateUserThread" como mostré aquí." se refiere al siguiente enlace.
[Enlace externo eliminado para invitados]
Bueno,eso es todo, espero que les sea de ayuda.
Fuente: [Enlace externo eliminado para invitados]
Saludos !