Introduccion
Esta publicación tiene como objetivo estudiar algunas técnicas de ingeniería inversa. Todos los materiales se presentan solo con fines informativos y no están destinados a ser utilizados para ningún beneficio personal.Lectura recomendada después de la
primera parteSi a un cirujano se le enseña cómo funciona una persona y le da un bisturí, eso no significa que usará este conocimiento en detrimento de alguien, y un ensamblador experto no sueña con escribir un súper virus.
Por lo tanto, en estas lecciones no debe buscar indicios de grietas y hacks.
Sujeto de investigación
Continuamos estudiando el código del complemento para la documentación de Visual Studio Atomineer Pro (en adelante, APD). Conozcamos la herramienta y sus capacidades.
Entonces, supongamos que tenemos una clase en C ++.
class ClassForReadFile { public: ClassForReadFile(); };
configure el APD para que los comentarios estén en estilo Doxygen. Obtenemos el cursor en la
clase y presionamos
CTRL + MAYÚS + D. Obtenemos lo siguiente:
class ClassForReadFile { public: ClassForReadFile(); };
El complemento agregó una buena descripción de la clase. ¡Todo es genial! Seguimos adelante. Supongamos que una clase pertenece a una biblioteca y debemos exportarla. Agregue una macro y cambie la definición de clase
#ifdef DLL_EXPORTS #define DATA_READER_DLL_EXPORTS __declspec(dllexport) #else #define DATA_READER_DLL_EXPORTS __declspec(dllimport) #endif class DATA_READER_DLL_EXPORTS ClassForReadFile { public: ClassForReadFile(); };
Para C ++ (sistema operativo Windows), la situación es estándar. Echa un vistazo a nuestro complemento. Presione
CTRL + MAYÚS + D y no obtenga nada de lo que esperábamos
class DATA_READER_DLL_EXPORTS ClassForReadFile { };
el nombre de la
definición DATA_READER_DLL_EXPORTS
se definió como el nombre de la clase, en lugar de
ClassForReadFile , y la descripción de la clase se generó a partir de este nombre. Es decir, en el código del complemento, esta situación, es decir, la exportación de la clase, no se procesa o se procesa con un error. Esto es lo que intentaremos corregir.
Paso 1
Buscaremos pistas. En primer lugar, dado que la exportación de funciones y clases desde C / C ++ es una situación estándar, todavía tratamos de "forzar" el complemento correctamente. En lugar de la
definición de DATA_READER_DLL_EXPORTS
, inserte la instrucción
__declspec y genere la documentación
class __declspec(dllexport) ClassForReadFile { };
Y, he aquí, ¡obtuvieron la descripción correcta de la clase! Por lo tanto, concluimos que en APD hay algún código que verifica la presencia de la cadena "__declspec" en la descripción de la clase e ignora su algoritmo adicional para construir documentación.
Descompilamos la biblioteca con el ildasm.exe estándar de los SDK de Microsoft. Encuentra la línea "__declspec". Se encuentra en 2 métodos CmdDocComment :: a y CmdDocComment :: b. Clase uno. Lo someteremos a más estudios.
Paso 2
Debo decir de inmediato que lo que estamos buscando está en CmdDocComment :: un método
Aquí es donde se encuentra
__declspec . Solo se muestran las líneas más interesantes.
cadena a (CmdDocComment.GeneratorInfo A_0) Concluimos esto sobre la base de que después de la verificación
list [num3] == "__declspec"
el método de eliminación se llama
list.RemoveAt (num3);
Razonamiento (pensar en voz alta):
- CmdDocComment :: un método tiene una variable local que contiene una matriz de cadenas
List<string> list = A_0.e;
- El primer elemento de esta matriz almacena el comienzo de la descripción de la función, estructura, clase, etc., es decir, la palabra clave "clase", "estructura", "unión"
list[0] == "struct"
- Cada elemento de la matriz contiene una palabra separada. En nuestro caso, será {"clase", "DATA_READER_DLL_EXPORTS", "ClassForReadFile"}
- Hay un bucle que rodea todos los elementos de la matriz "e", busca el elemento "__declspec" y lo elimina y todo lo que está entre paréntesis
- Hay una condición adicional para salir del ciclo. Esta es la ubicación de las palabras "where" o ":". La palabra oficial "donde" es, para ser honesto, no está familiarizado, pero ":" se usa al heredar clases
Defina un nuevo algoritmo y el propósito de los cambios:
1. los cambios no deberían afectar el resto de la funcionalidad
2. eliminaremos los elementos de la matriz "lista" de acuerdo con el algoritmo
- omita el primer elemento;
- si el siguiente elemento no es ":" y no "dónde" y no el final de la matriz, elimine.
Escribe el ciclo deseado
Queda por programarlo.
Paso 3
Programar en voz alta. La programación en el caso general es la escritura de códigos fuente, compilación, vinculación. Pero el ofuscador nos privó de tal oportunidad. Utilizaremos la
herramienta recomendada
dnSpy . Cambiaremos los códigos HEX de los comandos CIL directamente en la biblioteca, lo que resultó ser muy emocionante e informativo. Empecemos Abra dnSpy, cargue la biblioteca.
seleccione while y cambie la vista a IL
También daré una lista, aunque es bastante voluminosa
Ahora tenemos una ventana en comandos CIL, su representación HEX, desplazamiento de archivo y descripción. Todo en un solo lugar. Muy conveniente (gracias
CrazyAlex25 ).
Prestemos atención al bloque que menciona "__declspec". Desplazamiento de bloque 0x0001675A. Este será el comienzo de nuestras ediciones. Luego, encuentre el método RemoveAt. Será útil para nosotros sin cambios. El último byte del bloque es 0x000167BF. Vaya al editor HEX
Ctrl + X y escriba 0x00 en este rango. Guardaremos y comprobaremos a qué han llevado los cambios.
bucle vacío while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } list.RemoveAt(num3); num3--; num3++; }
Ahora implementaremos la nueva lógica. Primero, agregue la condición
if (num3 != 0 && num3 < list.Count - 1)
La tabla muestra los nuevos comandos y su descripción.
1119 | ldloc.s | Carga una variable local con el índice especificado en la pila de cálculo (forma abreviada). |
---|
2C61 | brfalse.s | Pasa el control a la declaración final si el valor es falso, una referencia nula o cero. Nota : Si num3 == 0, vaya al paso de aumentar el iterador de bucle. El valor 0x64 es el desplazamiento de dirección a la instrucción 0x000167BF (ver listado) |
---|
1119 | ldloc.s | Carga una variable local con el índice especificado en la pila de cálculo (forma abreviada) |
---|
07 | ldloc.1 | Carga una variable local con índice 1 en la pila de cálculo |
---|
6FF700000A | callvirt | get_Count () - Llama a un método de objeto enlazado tarde y empuja el valor de retorno a la pila de cálculo |
---|
17 | ldc.i4.1 | Empuja el valor entero 1 en la pila de cálculo como int32 |
---|
59 | sub | Resta un valor de otro y empuja el resultado a la pila de cálculo. |
---|
2F55 | bge.s | Transfiere el control a la instrucción final (forma abreviada) si el primer valor es mayor o igual que el segundo. Nota : Si num3> list.Count - 1, vaya al paso de aumentar el iterador de bucle. El valor 0x55 es el desplazamiento de dirección antes de la instrucción 0x000167BF |
---|
Escribimos estos bytes comenzando en el desplazamiento 0x0001675A. Guardar y descompilar nuevamente
Primera condición while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; }
Ahora agregamos la cadena de verificación "where" y ":". Cito el siguiente código HEX sin comentarios adicionales:
07 11 19 17 58 6F F9 00 00 0A 72 A3 1D 00 70 28 70 00 00 0A 2D 3F 07 11 19 17 58 6F F9 00 00 0A 72 92 5E 00 70 28 70 00 00 0A 2D 29
descompilar y obtener lo que planeaste
Nuevo ciclo while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } if (num3 != 0 && num3 < list.Count - 1 && !(list[num3 + 1] == ":") && !(list[num3 + 1] == "where")) { list.RemoveAt(num3); num3--; } num3++; }
Con estos cambios, el complemento generará la siguiente documentación del código:
class DATA_READER_DLL_EXPORTS ClassForReadFile { };
Conclusión
En esta lección, aprendimos cómo aplicar nuestros conocimientos para corregir errores. Por supuesto, este ejemplo no refleja toda la variedad de errores y su tratamiento, pero esto no es una "grieta banal". Solucionamos el error obvio sin tener el código fuente y sin reconstruir la aplicación.