Como todos los programadores, amas el código. Tú y él son mejores amigos. Pero tarde o temprano en la vida llegará un momento en el que no habrá un código con usted. Sí, es difícil de creer, pero habrá una gran brecha entre ustedes: usted está afuera y él está muy adentro. Desde la desesperanza, usted, como todos, tendrá que ir al otro lado. Al lado de la ingeniería inversa.
Usando el ejemplo de la tarea No. 2 de la fase en línea de
NeoQUEST-2019, analizaremos el principio general del controlador inverso de Windows. Por supuesto, el ejemplo está bastante simplificado, pero la esencia del proceso no cambia a partir de esto: la única pregunta es la cantidad de código que debe verse. Armados con experiencia y suerte, ¡comencemos!
Dado
Según la leyenda, nos dieron dos archivos: un volcado de tráfico y un archivo binario que generó el mismo tráfico. Primero, eche un vistazo al vertedero con Wireshark:
El volcado contiene una secuencia de paquetes UDP, cada uno de los cuales contiene 6 bytes de datos. Estos datos, a primera vista, son algún tipo de conjunto aleatorio de bytes: no es posible sacar nada del tráfico. Por lo tanto, dirigimos nuestra atención al binario, que debería decirle cómo descifrar todo.
Ábrelo en la IDA:
Parece que nos enfrentamos a algún tipo de conductor. Las funciones con el prefijo WSK se refieren a Winsock Kernel, la interfaz de programación de red en modo kernel de Windows. En MSDN, puede
ver una descripción de las estructuras y funciones utilizadas en WSK.
Para mayor comodidad, puede cargar el Kit de controladores de Windows 8 (modo kernel) - wdk8_km (o cualquier otra versión más reciente) en la IDA para usar los tipos definidos allí:
¡Atención, reversa!
Como siempre, comience desde el punto de entrada:
Vamos en orden. Primero, se inicializa Wsk, se crea un socket y se une: no describiremos estas funciones en detalle, no contienen ninguna información que nos sea útil.
La función sub_140001608 establece 4 variables globales. Llamémoslo InitVars. En uno de ellos, se escribe un valor en la dirección 0xFFFFF78000000320. Googleando un poco esta dirección, podemos suponer que registra el número de tics del temporizador del sistema desde el momento en que se inicia el sistema. Por ahora, nombremos la variable TickCount.
Luego, EntryPoint configura funciones para procesar paquetes IRP (paquete de solicitud de E / S). Puede
leer más sobre ellos en MSDN. Para todos los tipos de solicitudes, se define una función que simplemente pasa el paquete al siguiente controlador en la pila.
Pero para el tipo IRP_MJ_READ (3) se define una función separada; Llamémoslo IrpRead.
En él, a su vez, se instala CompletionRoutine.
CompletionRoutine llena la estructura desconocida con los datos recibidos del IRP y los coloca en la lista. Hasta ahora, no sabemos qué hay dentro del paquete; volveremos a esta función más adelante.
Buscamos más en EntryPoint. Después de definir los manejadores IRP, se llama a la función sub_1400012F8. Echemos un vistazo al interior e inmediatamente notemos que se crea un dispositivo (IoCreateDevice) en él.
Llame a la función AddDevice. Si los tipos son correctos, veremos que el nombre del dispositivo es "\\ Device \\ KeyboardClass0". Entonces nuestro controlador interactúa con el teclado. Buscando en Google IRP_MJ_READ en el contexto del teclado, puede
encontrar que la estructura KEYBOARD_INPUT_DATA se transmite en paquetes. Regresemos a CompletionRoutine y veamos qué tipo de datos pasa.
El IDA aquí no analiza bien la estructura, pero por desplazamientos y llamadas adicionales puede comprender que consiste en ListEntry, KeyData (el código de escaneo de la clave se almacena aquí) y KeyFlags.
Después de AddDevice, se llama a la función sub_140001274 en EntryPoint. Ella crea una nueva secuencia.
Veamos qué pasa en ThreadFunc.
Ella obtiene el valor de la lista y los procesa. Preste atención de inmediato a la función sub_140001A18.
Pasa los datos procesados a la entrada de la función sub_140001A68, junto con un puntero a WskSocket y el número 0x89E0FEA928230002. Después de analizar el número de parámetro por bytes (0x89 = 137, 0xE0 = 224, 0xFE = 243, 0xA9 = 169, 0x2328 = 9000), obtenemos exactamente la misma dirección y puerto del volcado de tráfico: 169.243.224.137:9000. Es lógico suponer que esta función envía un paquete de red a la dirección y puerto especificados; no lo consideraremos en detalle.
Veamos cómo se procesan los datos antes de enviarlos.
Para los dos primeros elementos, se realiza un equivalente con el valor generado. Dado que el número de ticks se usa para calcular, se puede suponer que nos enfrentamos a la generación de un número pseudoaleatorio.
Después de generar el número, sobrescribe el valor de la variable que anteriormente llamamos TickCount. Las variables para la fórmula se establecen en InitVars. Si volvemos a la llamada a esta función, descubriremos los valores para estas variables y, como resultado, obtendremos la siguiente fórmula:
(54773 + 7141 * valor_previo)% 259200Este es un
generador de números pseudoaleatorio congruente lineal. Se inicializa en InitVars usando TickCount. Para cada número posterior, el anterior actúa como el valor inicial (el generador devuelve un valor de doble byte, y el mismo se utiliza para la generación posterior).
Después de un equivalente con un número aleatorio de dos valores transmitidos desde el teclado, se llama a una función que forma los dos bytes restantes del mensaje. Simplemente produce
xor de dos parámetros ya encriptados y algún valor constante. Es poco probable que de alguna manera descifre los datos, por lo que los últimos dos bytes del mensaje para nosotros no contienen ninguna información útil, y no pueden considerarse. ¿Pero qué hacer con los datos cifrados?
Echemos un vistazo más de cerca a lo que está cifrado exactamente. KeyData es un código de escaneo que puede tomar un rango bastante amplio de valores; adivinarlo no es fácil. Pero
KeyFlags es un campo de bits:
Si observa la
tabla de códigos
de escaneo, notará que la mayoría de las veces el indicador será 0 (la tecla está abajo) o 1 (la tecla está levantada). KEY_E0 estará expuesto muy raramente, pero puede aparecer, pero para cumplir con KEY_E1 las posibilidades son muy pequeñas. Por lo tanto, puede intentar hacer lo siguiente: revisamos los datos del volcado, seleccionamos un valor que es KeyFlags cifrado, hacemos un equivalente con 0, generamos dos PSC sucesivos. En primer lugar, KeyData es un byte único, y podemos verificar la exactitud del MSS generado por byte alto. Y en segundo lugar, los siguientes KeyFlags encriptados, cuando realicen un equivalente con el PSC correcto, tomarán los mismos valores de bit. Si esto resultó ser incorrecto, entonces asumimos que los KeyFlags que vimos originalmente eran 1, etc.
Intentemos implementar nuestro algoritmo. Usaremos python para esto:
Implementación de algoritmo Ejecute nuestro script en los datos recibidos del volcado:
¡Y en el tráfico descifrado encontramos nuestra línea más deseable!
NQ2019DABE17518674F97DBA393415E9727982FC52C202549E6C1740BC0933C694B3DEPronto habrá artículos con análisis de las tareas restantes, ¡no te lo pierdas!
PD: ¡Te recordamos que todos los que hayan completado al menos una tarea en NeoQUEST-2019 por completo tienen derecho a un premio! Revise su correo para obtener una carta, y si no le llegó, ¡escriba a
support@neoquest.ru !