Reversing
Ahora deberiamos ser capaces de ver el modulo ene
desde la máquina debugger
0: kd> lm m ene
Browse full module list
start end module name
fffff803`5c7f0000 fffff803`5c7f7000 ene (no symbols)
0: kd> !drvobj \driver\ene 2
Driver object (ffffdf0bc5cde310) is for:
\Driver\Ene
DriverEntry: fffff8035c7f6064 ene
DriverStartIo: 00000000
DriverUnload: fffff8035c7f14c0 ene
AddDevice: 00000000
Dispatch routines:
[00] IRP_MJ_CREATE fffff8035c7f1100 ene+0x1100
[01] IRP_MJ_CREATE_NAMED_PIPE fffff803401645d0 nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE fffff8035c7f1100 ene+0x1100
[03] IRP_MJ_READ fffff803401645d0 nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE fffff803401645d0 nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION fffff803401645d0 nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION fffff803401645d0 nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA fffff803401645d0 nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA fffff803401645d0 nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS fffff803401645d0 nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff803401645d0 nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff803401645d0 nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL fffff803401645d0 nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff803401645d0 nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL fffff8035c7f1100 ene+0x1100
Lo primero es entender como podemos comunicarnos con el driver, hay que entender que no podemos acceder a la memoria del espacio kernel sin embargo existe una interfaz del sistema operativo que permite esta comunicación, estas son las llamadas ioctl
, cuando se instala un driver establece un nombre de dispositivo llamando a la función IoCreateDevice
, utilizando IDA podemos desensamblar el driver y ver esta llamada en el bloque DriverEntry
, el nombre asignado al driver es \\.\EneIo
Cada función se identifica con un código ioctl
, el driver acepta este tipo de llamadas usando estructuras de tipo IRP
o I/O Request Packets
, en este bloque podemos ver que la función establecida para encargarse de esta tarea es sub_13f0
La función sub_13f0
es encargada de ejecutar ciertas instrucciones dependiendo de su código ioctl
, inicia accediendo a los miembros de la estructura IRP
y realiza un par de comparaciones, compara el registro al
con 0xe
que fue el offset con el que se establecio la función en el bloque pasado, si el resultado no es 0
salta al ret
final
Si la comparación resulta en 0
inicia una estructura de tipo switch
para buscar la comparación con el código ioctl
correspondiente, en este caso nos quedaremos con el primer código que es 0x80102040
y analizaremos lo que ejecuta al llamarlo
El siguiente bloque nuevamente muestra un mensaje, y hace un salto condicional, si ebx
es igual a 0
salta a loc_1494
de lo contrario sigue la siguiente instrucción
Siguiendo la linea roja vemos que se utiliza la función memmove
sin sanitización aparente, esto parece interesante ya que controlamos los datos y si escribimos mas de lo que soporta el buffer
podriamos desbordarlo y sobrescribir otros datos
Después de copiar los datos vuelve al final de la función concluyendo en la instrucción ret
del offset 0x16b9
, si sobrepasamos el buffer y sobrescribimos la dirección de retorno en este ultimo ret
deberiamos tomar el control del flujo del programa