Si no ha leído la publicación anterior, puede encontrarla aquí: Parte 2: análisis de las funciones de inicio . En esta publicación, finalmente analizaremos la función Dispatcher. Recuerde que identificamos esta función en la primera publicación . Esta función tiene el objetivo principal de procesar cualquier paquete de solicitud de E/S (IRP) y, en este caso, manejar cualquier solicitud del código de función principal IRP_MJ_WRITE.
¿Por lo que pasaremos?
Al invertir las cosas relacionadas con Windows, la lectura de la documentación de MS debería ser obligatoria. Por lo general, proporcionan mucha información útil que le ahorrará mucho tiempo, como las estructuras que usan, los parámetros y los ejemplos de código. Algo que me gusta hacer es examinar los ejemplos disponibles en sus repositorios y buscar códigos similares. Los desarrolladores de controladores suelen reutilizar una gran cantidad de código de esos ejemplos. Buscar patrones similares en nuestro binario podría proporcionarnos mucha información sobre el contexto y las funciones a las que se llama dentro de un fragmento de código similar. Al final de esta publicación, encontrará una lista de enlaces útiles.
fn_DriverIOCTLDispatcher (0x140004604)
Primero, tome unos minutos para analizar el código descompilado inicial aquí y el ensamblado . Un controlador puede proporcionar múltiples rutinas de despacho, en este caso, implementaron solo un código de función principal, como vimos antes.
Podemos ver que recibe dos parámetros, pero ¿sabemos cuáles son esos parámetros? Sí, la documentación de MS explica que las funciones del despachador IRP_MJ_WRITE reciben como primer parámetro un PDEVICE_OBJECT y como segundo un puntero a una estructura IRP (IDA identificará este por sí mismo).

En la línea 16, el controlador controla que la longitud del búfer de entrada es igual a 0x270 , parece que es la única longitud posible.
Tenga cuidado al analizar la siguiente línea:
Como puede ver a continuación, MasterIrp comparte el mismo desplazamiento con IrpCount y SystemBuffer dentro de una unión ( struct _IRP ):
En este caso, el Despachador está intentando recuperar el SystemBuffer de la solicitud de IRP; no hace referencia al puntero MasterIrp. Necesitamos arreglar eso adecuadamente. En IDA PRO, esto se puede hacer fácilmente haciendo clic derecho-> Seleccionar campo de unión:

Ahora que hemos identificado dónde se almacena el búfer de entrada, veamos cómo se analiza:
Podemos ver que se espera que el primer DWORD sea igual a 0x270 (mismo valor de la longitud); Entonces, el siguiente DWORD debe tener algún tipo de valor mágico que coincida con 0x345821AB . Si se cumplen ambos requisitos, la función sub_140001E00 se llama enviando el búfer en el primer parámetro, y una referencia a una estructura aún desconocida en el segundo parámetro. Decidí llamar a esta función fn_DispatchIOCTLMethod y la analizaremos en la siguiente sección.
Después de cambiar el nombre de las variables, podemos concluir que la estructura del búfer de entrada sería algo como esto:
Como todavía no estamos analizando todos los métodos de envío, pero quiero proporcionarle un análisis completo del búfer de entrada, le mostraré la estructura completa que está utilizando el controlador. La estructura final sería así:
Esta función hace algunas cosas más, pero ignorémoslas por ahora.
fn_DispatchIOCTLMethod (0x140001E00)

Esta función es pequeña, y veremos que después de configurar los tipos correctamente y cambiar el nombre, todo se vuelve mucho más claro.
Lo primero que deberíamos hacer es agregar la estructura previamente definida DrvInputBuffer a IDA Pro. En la subvista "Tipos locales", es posible hacer "Clic derecho-> Insertar", allí puede copiar y pegar la definición de estructura:

Siguiente paso, cambie el nombre del primer parámetro para que sea un puntero a esa estructura haciendo "clic derecho en la variable -> Convertir a estructura *".
Si ocultamos los moldes, obtendremos algo como esto:
Si ha leído las publicaciones anteriores, puede reconocer dword_14000A240. Cambiamos el nombre de esta variable en la segunda publicación, mientras analizábamos fn_InitDispatchMethodArray : esta era la variable FunctionsCount .
Lo mismo sucede con dword_140009E40, que se renombró a IOCTLFunctionArray en la misma publicación, y es una matriz que contiene múltiples estructuras DispatcherStruct :
En base a eso, podemos identificar el siguiente comportamiento: Primero, la aplicación valida que IOCTLFunctionArray se haya inicializado comparando FunctionsCount con NULL. Luego, itera en un ciclo while, comparando el valor del índice de cada elemento con el DWORD en el desplazamiento 0xC del búfer de entrada (definido en la estructura como FnIndex ). Aumentan el contador hasta que alcanza el valor máximo almacenado en FunctionsCount . Si hay una coincidencia entre los índices, la función almacenada en DispatcherStruct-> FnPtr es call; enviando los mismos dos parámetros de fn_DispatchIOCTLMethod: SystemBuffer y la referencia a una estructura aún desconocida.
Esta sería la función final:
Próximos pasos
Niemand
Enlace de origen
niemand.com.ar
Twitter
twitter.com
¿Por lo que pasaremos?
- Aprenda cómo se implementan las rutinas de Dispatcher.
- Para revertir el método de análisis que maneja las solicitudes IRP_MJ_WRITE.
- Identifique estructuras personalizadas utilizadas por el controlador y cree nuevos tipos locales en IDA.
- Comprenda cómo el controlador distribuye los diferentes identificadores codificados atravesando una matriz con estructuras personalizadas.
Al invertir las cosas relacionadas con Windows, la lectura de la documentación de MS debería ser obligatoria. Por lo general, proporcionan mucha información útil que le ahorrará mucho tiempo, como las estructuras que usan, los parámetros y los ejemplos de código. Algo que me gusta hacer es examinar los ejemplos disponibles en sus repositorios y buscar códigos similares. Los desarrolladores de controladores suelen reutilizar una gran cantidad de código de esos ejemplos. Buscar patrones similares en nuestro binario podría proporcionarnos mucha información sobre el contexto y las funciones a las que se llama dentro de un fragmento de código similar. Al final de esta publicación, encontrará una lista de enlaces útiles.
fn_DriverIOCTLDispatcher (0x140004604)
Primero, tome unos minutos para analizar el código descompilado inicial aquí y el ensamblado . Un controlador puede proporcionar múltiples rutinas de despacho, en este caso, implementaron solo un código de función principal, como vimos antes.
Si quieres hacer trampa un poco, aquí está el código final .
Podemos ver que recibe dos parámetros, pero ¿sabemos cuáles son esos parámetros? Sí, la documentación de MS explica que las funciones del despachador IRP_MJ_WRITE reciben como primer parámetro un PDEVICE_OBJECT y como segundo un puntero a una estructura IRP (IDA identificará este por sí mismo).

En la línea 16, el controlador controla que la longitud del búfer de entrada es igual a 0x270 , parece que es la única longitud posible.
Tenga cuidado al analizar la siguiente línea:
C++:
v5 = a2->AssociatedIrp.MasterIrp;
Como puede ver a continuación, MasterIrp comparte el mismo desplazamiento con IrpCount y SystemBuffer dentro de una unión ( struct _IRP ):
C++:
union {
struct _IRP *MasterIrp;
__volatile LONG IrpCount;
PVOID SystemBuffer;
} AssociatedIrp;
En este caso, el Despachador está intentando recuperar el SystemBuffer de la solicitud de IRP; no hace referencia al puntero MasterIrp. Necesitamos arreglar eso adecuadamente. En IDA PRO, esto se puede hacer fácilmente haciendo clic derecho-> Seleccionar campo de unión:

Ahora que hemos identificado dónde se almacena el búfer de entrada, veamos cómo se analiza:
C++:
v5 = a2->AssociatedIrp.SystemBuffer;
if ( *(_DWORD *)v5 == 0x270 && *((_DWORD *)v5 + 1) == 0x345821AB )
{
sub_140001E00(v5, &v13);
Podemos ver que se espera que el primer DWORD sea igual a 0x270 (mismo valor de la longitud); Entonces, el siguiente DWORD debe tener algún tipo de valor mágico que coincida con 0x345821AB . Si se cumplen ambos requisitos, la función sub_140001E00 se llama enviando el búfer en el primer parámetro, y una referencia a una estructura aún desconocida en el segundo parámetro. Decidí llamar a esta función fn_DispatchIOCTLMethod y la analizaremos en la siguiente sección.
Después de cambiar el nombre de las variables, podemos concluir que la estructura del búfer de entrada sería algo como esto:
Código:
struct DrvInputBuffer
{
_DWORD Size; // Offset 0x0
_DWORD MagicNumber; // Offset 0x4
_BYTE gap8[8]; // Offset 0x8
void *pvoid10; // Offset 0x10
};
Como todavía no estamos analizando todos los métodos de envío, pero quiero proporcionarle un análisis completo del búfer de entrada, le mostraré la estructura completa que está utilizando el controlador. La estructura final sería así:
C++:
struct DrvInputBuffer
{
_DWORD Size; // Offset 0x0
_DWORD MagicNumber; // Offset 0x4
_DWORD RequestId; // Offset 0x8
_DWORD FnIndex; // Offset 0xc
void *pvoid10; // Offset 0x10
unsigned __int8 buffer[600]; // Offset 0x18
};
Esta función hace algunas cosas más, pero ignorémoslas por ahora.
fn_DispatchIOCTLMethod (0x140001E00)

Esta función es pequeña, y veremos que después de configurar los tipos correctamente y cambiar el nombre, todo se vuelve mucho más claro.
Lo primero que deberíamos hacer es agregar la estructura previamente definida DrvInputBuffer a IDA Pro. En la subvista "Tipos locales", es posible hacer "Clic derecho-> Insertar", allí puede copiar y pegar la definición de estructura:

Siguiente paso, cambie el nombre del primer parámetro para que sea un puntero a esa estructura haciendo "clic derecho en la variable -> Convertir a estructura *".
Si ocultamos los moldes, obtendremos algo como esto:
C++:
__int64 __fastcall fn_DispatchIOCTLMethod(DrvInputBuffer *a1, __int64 a2)
{
int v2; // er8
v2 = 0;
if ( !dword_14000A240 )
return 3221225473i64;
while ( dword_140009E40[4 * v2] != a1->FnIndex )
{
if ( ++v2 >= dword_14000A240 )
return 3221225473i64;
}
return (*&dword_140009E40[4 * v2 + 2])(a1, a2);
}
Si ha leído las publicaciones anteriores, puede reconocer dword_14000A240. Cambiamos el nombre de esta variable en la segunda publicación, mientras analizábamos fn_InitDispatchMethodArray : esta era la variable FunctionsCount .
Lo mismo sucede con dword_140009E40, que se renombró a IOCTLFunctionArray en la misma publicación, y es una matriz que contiene múltiples estructuras DispatcherStruct :
C++:
00000000 DispatcherStruct struc ; (sizeof=0x10, mappedto_424)
00000000 ; XREF: .data:_IOCTLFunctionArray/r
00000000 Index dd ? ; XREF: fn_InitDispatchMethodArray+1F/t
00000004 padding db 4 dup(?)
00000008 FnPtr dq ?
00000010 DispatcherStruct ends
En base a eso, podemos identificar el siguiente comportamiento: Primero, la aplicación valida que IOCTLFunctionArray se haya inicializado comparando FunctionsCount con NULL. Luego, itera en un ciclo while, comparando el valor del índice de cada elemento con el DWORD en el desplazamiento 0xC del búfer de entrada (definido en la estructura como FnIndex ). Aumentan el contador hasta que alcanza el valor máximo almacenado en FunctionsCount . Si hay una coincidencia entre los índices, la función almacenada en DispatcherStruct-> FnPtr es call; enviando los mismos dos parámetros de fn_DispatchIOCTLMethod: SystemBuffer y la referencia a una estructura aún desconocida.
Esta sería la función final:
C++:
__int64 __fastcall fn_DispatchIOCTLMethod(DrvInputBuffer *SystemBuffer, DrvOutputBuffer *a2)
{
int counter; // er8
counter = 0;
if ( !FunctionsCount )
return 0xC0000001i64;
while ( IOCTLFunctionArray[counter].Index != SystemBuffer->FnIndex )
{
if ( ++counter >= FunctionsCount )
return 0xC0000001i64;
}
return (IOCTLFunctionArray[counter].FnPtr)(SystemBuffer, a2);
}
Próximos pasos
- Análisis de NotifyRoutines (fn_InitRegistrationNotifyAndCallbackRoutines y fn_RegisterCreateProcessNotifyRoutine)
- Cargue estructuras de kernel con archivos .h y tipos locales
- https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/writing-dispatch-routines
- https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-irps
- https://github.com/microsoft/Windows-driver-samples
- https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp
Niemand
Enlace de origen

Reversing XignCode3 Driver - Part 3 - Analyzing dispatch functions - Niemand - Cyber Security
If you haven’t read the previous post you can find it here: Part 2 – Analyzing init functions. In this post, we will finally analyze the Dispatcher function. Remember that we identified this function in …

Niemand (@niemand_sec) | Twitter
The latest Tweets from Niemand (@niemand_sec). Security Consultant at @immunityinc. I'm a system engineer, who loves security. Opinions are my own. Argentina