Aplicaciones de ingeniería inversa después de la ofuscación (Parte 2)

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 parte
Si 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:

 /** The class for read file. */ 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

 /** A data reader DLL exports. */ 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

 /** The class for read file. */ 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)
 List<string> e = A_0.e; //....... List<string> list = A_0.e; int num3 = 0; while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } if (list[num3] == "__declspec") { if (num3 + 1 < list.Count) { num = list[num3 + 1].IndexOf(')'); if (num >= 0) { list[num3 + 1] = list[num3 + 1].Substring(num + 1).Trim(); } } list.RemoveAt(num3); num3--; } num3++; } if (list.Count > 0 && (list[0] == "struct" || list[0] == "union")) { if (list.Count == 1) { //...... 


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):

  1. CmdDocComment :: un método tiene una variable local que contiene una matriz de cadenas

     List<string> list = A_0.e; 
  2. 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" 
  3. Cada elemento de la matriz contiene una palabra separada. En nuestro caso, será {"clase", "DATA_READER_DLL_EXPORTS", "ClassForReadFile"}
  4. 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
  5. 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

 //     ,          num2 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { // ,      .     if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } //  if (list[num3] == "__declspec"),  if (num3 != 0 && num3 < (list.Count - 1) && list[num3 + 1] != ":" && list[num3 + 1] != "where") { e.RemoveAt(index); --index; } num3++; } 

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.

Encuentra nuestro método
imagen

seleccione while y cambie la vista a IL
Nuestro ciclo
imagen

También daré una lista, aunque es bastante voluminosa

Nuestro ciclo
 /* 0x00016710 07 */ IL_018C: ldloc.1 //         1. /* 0x00016711 1119 */ IL_018D: ldloc.s V_25 //          ( ). /* 0x00016713 6FF900000A */ IL_018F: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016718 72925E0070 */ IL_0194: ldstr "where" //       ,   ,   . /* 0x0001671D 287000000A */ IL_0199: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x00016722 3AAB000000 */ IL_019E: brtrue IL_024E //    ,   value  true,    null   . /* 0x00016727 07 */ IL_01A3: ldloc.1 //         1. /* 0x00016728 1119 */ IL_01A4: ldloc.s V_25 //          ( ). /* 0x0001672A 6FF900000A */ IL_01A6: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x0001672F 72A31D0070 */ IL_01AB: ldstr ":" //       ,   ,   . /* 0x00016734 287000000A */ IL_01B0: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x00016739 3A94000000 */ IL_01B5: brtrue IL_024E //    ,   value  true,    null   . /* 0x0001673E 07 */ IL_01BA: ldloc.1 //         1. /* 0x0001673F 1119 */ IL_01BB: ldloc.s V_25 //          ( ). /* 0x00016741 6FF900000A */ IL_01BD: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016746 03 */ IL_01C2: ldarg.1 //     1   . /* 0x00016747 7B12010004 */ IL_01C3: ldfld string Atomineer.Utils.CmdDocComment/GeneratorInfo::b //      ,       . /* 0x0001674C 287000000A */ IL_01C8: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x00016751 2C07 */ IL_01CD: brfalse.s IL_01D6 //    ,   value  false,    . /* 0x00016753 09 */ IL_01CF: ldloc.3 //         3. /* 0x00016754 16 */ IL_01D0: ldc.i4.0 //    0     int32. /* 0x00016755 2F03 */ IL_01D1: bge.s IL_01D6 //     ( ),        . /* 0x00016757 1119 */ IL_01D3: ldloc.s V_25 //          ( ). /* 0x00016759 0D */ IL_01D5: stloc.3 //                3. /* 0x0001675A 07 */ IL_01D6: ldloc.1 //         1. /* 0x0001675B 1119 */ IL_01D7: ldloc.s V_25 //          ( ). /* 0x0001675D 6FF900000A */ IL_01D9: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016762 729E5E0070 */ IL_01DE: ldstr "__declspec" //       ,   ,   . /* 0x00016767 287000000A */ IL_01E3: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x0001676C 2C51 */ IL_01E8: brfalse.s IL_023B //    ,   value  false,    . /* 0x0001676E 1119 */ IL_01EA: ldloc.s V_25 //          ( ). /* 0x00016770 17 */ IL_01EC: ldc.i4.1 //    1     int32. /* 0x00016771 58 */ IL_01ED: add //         . /* 0x00016772 07 */ IL_01EE: ldloc.1 //         1. /* 0x00016773 6FF700000A */ IL_01EF: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count() //             . /* 0x00016778 2F37 */ IL_01F4: bge.s IL_022D //     ( ),        . /* 0x0001677A 07 */ IL_01F6: ldloc.1 //         1. /* 0x0001677B 1119 */ IL_01F7: ldloc.s V_25 //          ( ). /* 0x0001677D 17 */ IL_01F9: ldc.i4.1 //    1     int32. /* 0x0001677E 58 */ IL_01FA: add //         . /* 0x0001677F 6FF900000A */ IL_01FB: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016784 1F29 */ IL_0200: ldc.i4.s 41 //      int8     int32 ( ). /* 0x00016786 6FC800000A */ IL_0202: callvirt instance int32 [mscorlib]System.String::IndexOf(char) //             . /* 0x0001678B 0C */ IL_0207: stloc.2 //                2. /* 0x0001678C 08 */ IL_0208: ldloc.2 //         2. /* 0x0001678D 16 */ IL_0209: ldc.i4.0 //    0     int32. /* 0x0001678E 3221 */ IL_020A: blt.s IL_022D //     ( ),      . /* 0x00016790 07 */ IL_020C: ldloc.1 //         1. /* 0x00016791 1119 */ IL_020D: ldloc.s V_25 //          ( ). /* 0x00016793 17 */ IL_020F: ldc.i4.1 //    1     int32. /* 0x00016794 58 */ IL_0210: add //         . /* 0x00016795 07 */ IL_0211: ldloc.1 //         1. /* 0x00016796 1119 */ IL_0212: ldloc.s V_25 //          ( ). /* 0x00016798 17 */ IL_0214: ldc.i4.1 //    1     int32. /* 0x00016799 58 */ IL_0215: add //         . /* 0x0001679A 6FF900000A */ IL_0216: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x0001679F 08 */ IL_021B: ldloc.2 //         2. /* 0x000167A0 17 */ IL_021C: ldc.i4.1 //    1     int32. /* 0x000167A1 58 */ IL_021D: add //         . /* 0x000167A2 6FCB00000A */ IL_021E: callvirt instance string [mscorlib]System.String::Substring(int32) //             . /* 0x000167A7 6F8600000A */ IL_0223: callvirt instance string [mscorlib]System.String::Trim() //             . /* 0x000167AC 6FFF00000A */ IL_0228: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::set_Item(int32, !0) //             . /* 0x000167B1 07 */ IL_022D: ldloc.1 //         1. /* 0x000167B2 1119 */ IL_022E: ldloc.s V_25 //          ( ). /* 0x000167B4 6F6701000A */ IL_0230: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::RemoveAt(int32) //             . /* 0x000167B9 1119 */ IL_0235: ldloc.s V_25 //          ( ). /* 0x000167BB 17 */ IL_0237: ldc.i4.1 //    1     int32. /* 0x000167BC 59 */ IL_0238: sub //           . /* 0x000167BD 1319 */ IL_0239: stloc.s V_25 //                index ( ). /* 0x000167BF 1119 */ IL_023B: ldloc.s V_25 //          ( ). /* 0x000167C1 17 */ IL_023D: ldc.i4.1 //    1     int32. /* 0x000167C2 58 */ IL_023E: add //         . /* 0x000167C3 1319 */ IL_023F: stloc.s V_25 //                index ( ). /* 0x000167C5 1119 */ IL_0241: ldloc.s V_25 //          ( ). /* 0x000167C7 07 */ IL_0243: ldloc.1 //         1. /* 0x000167C8 6FF700000A */ IL_0244: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count() //             . /* 0x000167CD 3F3EFFFFFF */ IL_0249: blt IL_018C //    ,     . 


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.
1119ldloc.sCarga una variable local con el índice especificado en la pila de cálculo (forma abreviada).
2C61brfalse.sPasa 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)
1119ldloc.sCarga una variable local con el índice especificado en la pila de cálculo (forma abreviada)
07ldloc.1Carga una variable local con índice 1 en la pila de cálculo
6FF700000Acallvirtget_Count () - Llama a un método de objeto enlazado tarde y empuja el valor de retorno a la pila de cálculo
17ldc.i4.1Empuja el valor entero 1 en la pila de cálculo como int32
59subResta un valor de otro y empuja el resultado a la pila de cálculo.
2F55bge.sTransfiere 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; } //     if (num3 != 0 && num3 < list.Count - 1) { list.RemoveAt(num3); num3--; } 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:

 /** The class for read file. */ 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.

Source: https://habr.com/ru/post/445126/


All Articles