• Win7 and CreateRemoteThread

 #370025  por cLn
 27 May 2012, 20:12
Hola..

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 :
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 menos este comportamiento está documentado...

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) :
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.
De hecho, cada usuario tiene su propio proceso csrss.exe. Aquí está el prototipo de CsrClientCallServer y el código de llamada en CreateRemoteThreadEx :
Código: [ Debe registrarse para ver este enlace ]
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
Donde nos interesa el parámetro ApiNumber que está definido por la macro :
Código: [ Debe registrarse para ver este enlace ]
#define CSR_MAKE_API_NUMBER( DllIndex, ApiIndex ) \
    (CSR_API_NUMBER)(((DllIndex) << 16) | (ApiIndex))
Aquí ApiIndex que vale 1 y DllIndex que vale 1. Salimos de las tablas realizadas por j00ru y vemos que "CsrClientCallServer" llamará a "basesrv! BaseSrvCreateThread". Despues de depurar con IDA vemos que "csrsrv! CsrLockProcessByClientId" es la que da problemas. Analizamos con IDA "CsrLockProcessByClientId".
Código: [ Debe registrarse para ver este enlace ]
unsigned int __stdcall CsrLockProcessByClientId(int a1, int ret)
{
  int v2; // [email protected]
  int v3; // [email protected]
  int v4; // [email protected]
  int v6; // [email protected]
  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;
}
Viendo directamente "CsrRootProcess" me acordé de "CsrWalker". En pocas palabras el proceso "csrss" en XP mantiene una lista de todos los procesos y subprocesos iniciados en el sistema. Así que si se extrae de esta lista se puede utilizar como un rootkit (o mejor aún ocultar un rootkit:]).

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: [ Debe registrarse para ver este enlace ]
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
De hecho no tenemos ninguna obligación de notificar el subsistema al crear un nuevo hilo o thread. Por ejemplo, una función del proceso "csrss" es cerrar todos los procesos antes de un cierre o apagado, es por eso que mantiene una lista de cada usuario en CsrRootProcess 7. Así que no impide que un nuevo hilo o thread sea creado, no es realmente un problema para la estabilidad del sistema. Precisamente, no vamos a hacer:]

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:

[ Debe registrarse para ver este enlace ]

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.

[ Debe registrarse para ver este enlace ]

Bueno,eso es todo, espero que les sea de ayuda.

Fuente: [ Debe registrarse para ver este enlace ]
Saludos !
 #370142  por linkgl
 28 May 2012, 04:16
El tipo hace un muy buen análisis con olly en mano xD estos son de los posts que me gustan. No tenía idea de que createremotethread no funcionase en 7 juraría que hice el subclassing en asm inyectándome en calc creando un hilo remoto con createremotethread bajo 7 y no me dió problemas, voy a volver a intentar :S