[TUTORIAL] Reversing XignCode3 Driver – Part 1 – Identificando el Driver Entry Point

  • Hola Invitado, si deseas saber que es lo que paso con nuestro servidor de discord, puedes ingresar al siguiente enlace Discord


195
Me Gusta
68
Temas

c0de

MOV EAX, EDX
Registrado
19 Abr 2020
Mensajes
125
Ubicación
Localhost
Mejores respuestas
0
LV
0
 
1588814421413.png
Esta será una serie de publicaciones relacionadas con el controlador XignCode3 (XC3). Comencé esto a principios del 2019 mientras investigaba para desvelar el mundo subterráneo de Anti-Cheats . Debido a eso, encontrará que la versión de XC3 no es la última disponible.

Revertir un controlador de este tipo no solo es divertido, sino que también le permite aprender mucho sobre Windows Internals. Dicho esto, seré lo más breve y preciso posible, e intentaré escribir algunos aspectos destacados de lo que creo que son algunos fragmentos de código o características interesantes de este controlador.

1588814777633.png

¿Por lo que pasaremos?
  1. Aprenda acerca de DriverEntry
  2. Aprende a identificar esta función en el binario
  3. Identificar cómo se crea el objeto del controlador
  4. Analizar las funciones principales implementados, especialmente el despachador uno
  5. Detecta algún código adicional, que no sabemos qué hace. Todavía.
También puede encontrar aquí el resultado final de invertir la función DriverEntry.

¿Dónde empiezo?

Obteniendo el archivo .sys, por supuesto. Si desea revertir esto por su cuenta, estos son los hash del archivo que utilicé:
  • md5 : C82DE0BC9A3A08E351B9BFA960F8757A
  • sha1 : F3AE1406B4CD7D1394DAC529C62BB01F3EB46D4A
  • sha256 : E7994B56152955835B5025049ABEE651E9C52A6CCAD7E04F2C458C8568967245
Cuando quiero revertir un controlador, generalmente me gusta comenzar con DriverEntry. En esta función, podemos encontrar algunos detalles interesantes que nos ayudarán a identificar la mayoría de las características que ha implementado un controlador.

IDA Pro y Ghidra generalmente pueden detectar esta función automáticamente. Pero a veces puede que tenga que hacer esto manualmente. Como dice el sitio de Microsoft :
“DriverEntry es la primera rutina proporcionada por el controlador que se llama después de cargar un controlador. Es responsable de inicializar el controlador ".

Básicamente, debemos buscar una función que llame a IoCreateDevice , esto generalmente se realiza dentro de DriverEntry y se usa para crear un nuevo objeto de dispositivo. Esta es solo una forma de hacerlo, pero como IDA lo hizo automáticamente por nosotros, pasemos directamente a la fase de inversión.

DriverEntry (0x1400047B8)

Para mantener esta publicación limpia, puede ver aquí cómo DriverEntry fue descompilado por IDA Pro al principio, y aquí el ensamblaje. Te animo a que te tomes unos minutos para leer el código y trates de identificar el objetivo principal de la función antes de continuar con tu lectura. Veamos cómo podemos limpiar esto. A veces, cambiar el nombre y cambiar los tipos de variables puede ser el 50% del trabajo y, por supuesto, nos ayudará a comprender mejor la función.

Sabemos que DriverEntry recibe dos parámetros : un puntero _DRIVER_OBJECT y un PUNICODE_STRING con la ruta del registro. Entonces, cambiaremos el nombre y el tipo de esas dos variables.

Algo a tener en cuenta es que muchos de los tipos de estructura que necesitamos al invertir no están disponibles de forma predeterminada en IDA Pro. Por lo tanto, podemos importarlos haciendo: "Archivo-> Cargar archivo-> Analizar archivo de encabezado C ...".

Volviendo al código, tenemos algunas validaciones y concatenación de SymbolicLinkName y DeviceName, nada extraño. Solo están configurando las cadenas requeridas:
C++:
_DriverObject = DriverObject;
DeviceObject = 0i64;
if (!RegistryPath->Length || fn_strcat(Dest, RegistryPath) < 0)
    return 0xC0000001i64;
if (sub_140003A50(&SymbolicLinkName, Dest) < 0)
{
    Real_Driver_Entry(Dest);
    return 0xC0000001i64;
}

DriverEntry necesita implementar algunas "responsabilidades o rutinas requeridas" en particular, como se explica aquí . Veamos cómo lo hicieron antes de llamar a IoCreateDevice :
C++:
_DriverObject->DriverUnload = fn_DriverUnloadDispatcher;
_DriverObject->MajorFunction[0] = fn_DispatchCreate;// IRP_MJ_CREATE
_DriverObject->MajorFunction[2] = fn_DispatchClose;// IRP_MJ_CLOSE                     
_DriverObject->MajorFunction[4] = fn_DriverIOCTLDispatcher;// IRP_MJ_WRITE                     
ntStatus = IoCreateDevice(_DriverObject, 0, &DeviceName, 0x22u, 0, 0, &DeviceObject);

Cambié el nombre de algunas variables y funciones para describir su propósito principal. Sin embargo, déjame explicarte cómo puedes identificarlos fácilmente:

Aquí podemos ver todos los códigos de funciones principales de IRP disponibles en Windows. En el fragmento de código anterior, solo se implementaron los números 0, 2 y 4:
  • IRP_MJ_CREATE 0x00
  • IRP_MJ_CLOSE 0x02
  • IRP_MJ_WRITE 0x04
Se puede encontrar una lista completa en mi github .

IRP_MJ_CREATE e IRP_MJ_CLOSE son solo funciones genéricas. El interesante es IRP_MJ_WRITE , que manejará las solicitudes provenientes del modo de usuario y se implementa en fn_DriverIOCTLDispatcher. Explicaré en otra publicación cómo funciona esta función y cómo despacha cada solicitud de IRP. Por ahora, nos centraremos en funciones interesantes que DriverEntry llama.

1588814998967.png

Después de un análisis del comportamiento interno de las funciones que se están llamando, podemos llegar al siguiente código:
C++:
fn_InitDispatchMethodArray();
ntStatus = fn_InitRegistrationNotifyAndCallbackRoutines();
if (ntStatus >= 0)
{
    ntStatus = fn_ObtainKernelFunctions();
    if (ntStatus >= 0)
    {
        ntStatus = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
        if (ntStatus >= 0)
        {
            ntStatus = 0;
            goto LABEL_18;
        }
        nullsub_1();
    }
    fn_RegisterCreateProcessNotifyRoutine();
    nullsub_1();
}

Han aparecido algunos nombres interesantes:
  • fn_InitDispatchMethodArray
  • fn_InitRegistrationNotifyAndCallbackRoutines
  • fn_ObtainKernelFunctions
  • y fn_RegisterCreateProcessNotifyRoutine
1588815046030.png

Como habrás notado, tiendo a escribir nombres muy descriptivos jaja. Esos nombres pueden estar equivocados o no, dependiendo de lo que descubramos durante el proceso de reversión posterior. Como mencioné, la mayor parte de este trabajo se realizó hace meses. A veces, la inversión tiene muchas conjeturas, por lo que debemos comenzar a imaginar lo que cada función podría hacer potencialmente y darles nombres para poder imaginar el objetivo de cada una de ellas.

El nombre se basa en lo que podría inferir sobre esas funciones. Los veremos en profundidad en las siguientes publicaciones.

Básicamente, podemos separarlos en dos grupos en función de sus objetivos. Las funciones que registran algún tipo de devolución de llamada; fn_InitRegistrationNotifyAndCallbackRoutines y fn_RegisterCreateProcessNotifyRoutine . Y la función que contiene la información básica que requiere el controlador para funcionar; fn_InitDispatchMethodArray y fn_ObtainKernelFunctions .

Próximos pasos
  • Análisis de funciones init (fn_InitDispatchMethodArray y fn_ObtainKernelFunctions)
  • Analizar la función de envío (fn_DriverIOCTLDispatcher)
  • Análisis del registro de notificaciones y rutinas de devolución de llamada (fn_InitRegistrationNotifyAndCallbackRoutines y fn_RegisterCreateProcessNotifyRoutine)
Creditos
Niemand

Enlace de origen

Twiiter