[TUTORIAL] Reversing del controlador XignCode3 - Parte 2 - Análisis de las funciones de inicio

  • 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
127
Ubicación
Localhost
Mejores respuestas
0
LV
0
 
1588817741453.png
Si no ha leído la publicación anterior, puede encontrarla aquí . Hoy continuaremos analizando algunas funciones que DriverEntry está utilizando.

En la parte 1 identificamos la función Dispatcher del controlador, así como dos funciones que inicializaban algunas variables para el controlador ( fn_InitDispatchMethodArray y fn_ObtainKernelFunctions ). Vamos a revertir cada uno de ellos y analizar rápidamente qué están haciendo. Esto nos ayudará a comprender las funciones de Dispatcher implementadas en este controlador.

¿Por lo que pasaremos?
  1. Algunos mecanismos básicos de inicialización de este controlador
  2. Identifique lo que parece ser una estructura personalizada utilizada para indexar y almacenar todos los métodos disponibles.
  3. Identifique cómo se ubican las direcciones de función en la memoria. Por ejemplo, ObRegisterCallbacks . Vamos a hablar de esto en las siguientes publicaciones.
fn_InitDispatchMethodArray (0x1400015F8)
El código original para esta función se puede encontrar aquí: descompilado y asm . Y el resultado final aquí (trata de no consentirte aún).

Vamos a ver ahora en esta función que se está inicializando una especie de estructura personalizada:

C++:
.text:0000000140001617                 mov     cs:dword_140009E40, 306h
.text:0000000140001621                 mov     cs:qword_140009E48, rax
.text:0000000140001628                 lea     rax, sub_14000101C
.text:000000014000162F                 mov     cs:qword_140009E58, rax
.text:0000000140001636                 lea     rax, sub_140001CC8
.text:000000014000163D                 mov     cs:qword_140009E68, rax

Podemos resolver esto, porque si prestamos atención al ensamblaje, veremos que primero asignan un valor int a una dirección de memoria en particular, y luego mueven 8 bytes más para escribir un puntero a una función dentro del binario. . Llamaremos a esta estructura IOCTLFunctionArray. ¿Una matriz? solo sigue leyendo =) Esta matriz desempeñará un papel importante al enviar una solicitud.

La estructura sería algo como esto:

C++:
typedef struct DispatcherStruct  {
    int Index;
    char padding[4];
    PVOID FnPtr;
};

Y en IDA Pro:

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

Este proceso se repite varias veces en esta función, 25 veces exactamente. Es por eso que lo llamamos matriz , se almacenan 25 veces la misma estructura (con diferentes valores, por supuesto) dentro de una matriz.

El valor 25 también se está almacenando en una variable que decidí renombrar de dword_14000A240 a FunctionsCount :

C++:
.text:000000014000186A                 mov     cs:FunctionsCount , 19h

Más adelante veremos cómo se usa esta variable en el Dispatcher, pero podemos intentar adivinarla. En función de esta función, podemos deducir que el controlador tiene una lista de todos los métodos disponibles que se pueden invocar, y si se proporciona algún tipo de valor de índice, podría ser posible invocarlos.

1588818072753.png

El resultado final sería algo como esto . Tenga en cuenta que algunas funciones han cambiado de nombre desde que comencé a invertirlas antes. Veremos algunos interesantes en las siguientes publicaciones.

fn_ObtainKernelFunctions (0x140002A18)
La siguiente función es simple. Para continuar con la inicialización, el controlador necesita la dirección de algunas rutinas particulares:

1588818089426.png

Al hacer esto, pueden asegurarse de que esas funciones estén disponibles en la versión en ejecución de Windows y obtener un puntero a ellas. Solo necesitan almacenarlos en una variable y luego usarlos para llamar a cualquiera de esas rutinas convirtiéndolas en la definición de función adecuada. Puede detectar esto fácilmente también en la función de ensamblaje:

C++:
.text:0000000140002A1C                 lea     rdx, SourceString ; "ObGetFilterVersion"
.text:0000000140002A23                 lea     rcx, [rsp+38h+DestinationString] ; DestinationString
.text:0000000140002A28                 call    cs:RtlInitUnicodeString
.text:0000000140002A2E                 lea     rcx, [rsp+38h+DestinationString] ; SystemRoutineName
.text:0000000140002A33                 call    cs:MmGetSystemRoutineAddress
.text:0000000140002A39                 lea     rdx, aObregistercall ; "ObRegisterCallbacks"
.text:0000000140002A40                 mov     cs:qword_14000A288, rax
.text:0000000140002A47                 lea     rcx, [rsp+38h+DestinationString] ; DestinationString
.text:0000000140002A4C                 call    cs:RtlInitUnicodeString
.text:0000000140002A52                 lea     rcx, [rsp+38h+DestinationString] ; SystemRoutineName
.text:0000000140002A57                 call    cs:MmGetSystemRoutineAddress

No hay mucha diferencia entre el código original y el resultado final. El código era algo obvio para esta función.

Próximos pasos
  • 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 original

Twitter