Applications d'ingénierie inverse après obscurcissement (partie 2)

Présentation


Cette publication vise à étudier certaines techniques d'ingénierie inverse. Tous les documents sont présentés à titre informatif uniquement et ne sont pas destinés à être utilisés à des fins personnelles.

Lecture recommandée après la première partie
Si un chirurgien apprend comment fonctionne une personne et lui donne un scalpel, cela ne signifie pas qu'il utilisera ces connaissances au détriment de quelqu'un, et un assembleur compétent ne rêve pas d'écrire un super virus.
Donc, dans ces leçons, vous ne devez pas rechercher des indices de fissures et de hacks.

Sujet de recherche


Nous continuons d'étudier le code du plug-in pour la documentation de Visual Studio Atomineer Pro (ci-après APD). Familiarisons-nous avec l'outil et ses capacités. Supposons donc que nous ayons une classe en C ++.

class ClassForReadFile { public: ClassForReadFile(); }; 

configurer l'APD pour que les commentaires soient dans le style Doxygen. Nous obtenons le curseur sur la classe et appuyez sur CTRL + MAJ + D. Nous obtenons ce qui suit:

 /** The class for read file. */ class ClassForReadFile { public: ClassForReadFile(); }; 

Le plugin a ajouté une belle description de la classe. Tout est super! Nous continuons. Supposons qu'une classe appartient à une bibliothèque et que nous devons l'exporter. Ajouter une macro et modifier la définition de classe

 #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(); }; 

Pour C ++ (Windows OS), la situation est standard. Découvrez notre plugin. Appuyez sur CTRL + MAJ + D et n'obtenez pas du tout ce que nous attendions

 /** A data reader DLL exports. */ class DATA_READER_DLL_EXPORTS ClassForReadFile { }; 

le nom de la définition DATA_READER_DLL_EXPORTS a été défini comme le nom de la classe, au lieu de ClassForReadFile , et la description de la classe a été générée à partir de ce nom. Autrement dit, dans le code du plugin, cette situation, à savoir l'exportation de la classe, n'est pas traitée ou est traitée avec une erreur. C'est ce que nous allons essayer de corriger.

Étape 1


Nous chercherons des indices. Premièrement, étant donné que l'exportation de fonctions et de classes depuis C / C ++ est une situation standard, nous essayons toujours de «forcer» le plugin correctement. Au lieu de la définition DATA_READER_DLL_EXPORTS , insérez l'instruction __declspec elle - même et générez la documentation

 /** The class for read file. */ class __declspec(dllexport) ClassForReadFile { }; 

Et voilà, ils ont obtenu la bonne description de classe! Ainsi, nous concluons que dans APD il y a du code qui vérifie la présence de la chaîne "__declspec" dans la description de la classe et ignore son autre algorithme pour construire la documentation.

Nous décompilons la bibliothèque avec le ildasm.exe standard des SDK Microsoft. Recherchez la ligne "__declspec". Il se trouve dans 2 méthodes CmdDocComment :: a et CmdDocComment :: b. Classe un. Nous la soumettrons à un complément d'étude.

Étape 2


Je dois dire tout de suite que ce que nous recherchons est dans la méthode CmdDocComment :: a

C'est là que __declspec se réunit. Seules les lignes les plus intéressantes sont affichées.

chaîne 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) { //...... 


Nous avons conclu cela sur la base qu'après vérification
list [num3] == "__declspec"
la méthode de suppression est appelée
list.RemoveAt (num3);
Raisonnement (penser à haute voix):

  1. La méthode CmdDocComment :: a a une variable locale contenant un tableau de chaînes

     List<string> list = A_0.e; 
  2. Le premier élément de ce tableau stocke le début de la description de la fonction, la structure, la classe, etc., c'est-à-dire le mot-clé "class", "struct", "union"
     list[0] == "struct" 
  3. Chaque élément du tableau contient un mot distinct. Dans notre cas, ce sera {"class", "DATA_READER_DLL_EXPORTS", "ClassForReadFile"}
  4. Il y a une boucle qui fait le tour de tous les éléments du tableau "e", recherche l'élément "__declspec", et le supprime ainsi que tout ce qui est entre crochets
  5. Il existe une condition supplémentaire pour quitter le cycle. Il s'agit de l'emplacement des mots "où" ou ":". Le mot officiel "où" est, pour être honnête, pas familier, mais ":" est utilisé lors de l'héritage des classes

Définissez un nouvel algorithme et le but des modifications:

1. les modifications ne devraient pas affecter le reste de la fonctionnalité

2. nous allons supprimer les éléments du tableau "list" selon l'algorithme
- sauter le premier élément;
- si l'élément suivant n'est pas ":" et non "où" et non la fin du tableau, supprimez.

Écrivez le cycle souhaité

 //     ,          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++; } 

Reste à le programmer.

Étape 3


Programmez fort. La programmation dans le cas général est l'écriture de codes sources, la compilation, la liaison. Mais l'obfuscateur nous a privés d'une telle opportunité. Nous utiliserons l' outil dnSpy recommandé. Nous allons changer les codes HEX des commandes CIL directement dans la bibliothèque, ce qui s'est avéré très excitant et instructif! Commençons. Ouvrez dnSpy, chargez la bibliothèque.

Trouvez notre méthode
image

sélectionnez tout et changez la vue en IL
Notre cycle
image

Je donnerai également une liste, bien qu'elle soit plutôt volumineuse

Notre cycle
 /* 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 //    ,     . 


Nous avons maintenant une fenêtre dans les commandes CIL, leur représentation HEX, décalage de fichier et description. Tout en un seul endroit. Très pratique (merci CrazyAlex25 ).
Faisons attention au bloc mentionnant "__declspec". Décalage de bloc 0x0001675A. Ce sera le début de nos modifications. Ensuite, recherchez la méthode RemoveAt. Il nous sera utile sans changement. Le dernier octet du bloc est 0x000167BF. Accédez à l'éditeur HEX Ctrl + X et écrivez 0x00 dans cette plage. Nous enregistrerons et vérifierons les conséquences de ces changements.
boucle vide
 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } list.RemoveAt(num3); num3--; num3++; } 


Nous allons maintenant mettre en œuvre la nouvelle logique. Tout d'abord, ajoutez la condition

  if (num3 != 0 && num3 < list.Count - 1) 

Le tableau présente les nouvelles commandes et leur description.
1119ldloc.sCharge une variable locale avec l'index spécifié sur la pile de calcul (forme courte).
2C61brfalse.sPasse le contrôle à l'instruction finale si la valeur est fausse, une référence nulle ou zéro. Remarque : Si num3 == 0, passez à l'étape d'augmentation de l'itérateur de boucle. La valeur 0x64 est le décalage d'adresse vers l'instruction 0x000167BF (voir liste)
1119ldloc.sCharge une variable locale avec l'index spécifié dans la pile de calcul (forme courte)
07ldloc.1Charge une variable locale d'index 1 sur la pile de calcul
6FF700000Acallvirtget_Count () - Appelle une méthode d'objet à liaison tardive et pousse la valeur de retour sur la pile de calcul
17ldc.i4.1Pousse la valeur entière 1 sur la pile de calcul comme int32
59sousSoustrait une valeur d'une autre et pousse le résultat dans la pile de calcul.
2F55bge.sIl transfère le contrôle à l'instruction finale (forme abrégée) si la première valeur est supérieure ou égale à la seconde. Remarque : Si num3> list.Count - 1, passez à l'étape d'augmentation de l'itérateur de boucle. La valeur 0x55 est le décalage d'adresse avant l'instruction 0x000167BF

Nous écrivons ces octets à partir de l'offset 0x0001675A. Enregistrez et décompilez à nouveau

Première condition
 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++; } 


Maintenant, nous ajoutons la vérification de chaîne "où" et ":". Je cite le code HEX suivant sans commentaires supplémentaires:

 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 

décompiler et obtenir ce que vous avez prévu

Nouveau cycle
 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++; } 


Avec ces modifications, le plugin générera la documentation suivante du code:

 /** The class for read file. */ class DATA_READER_DLL_EXPORTS ClassForReadFile { }; 

Conclusion


Dans cette leçon, nous avons appris à appliquer nos connaissances pour corriger les bogues. Bien sûr, cet exemple ne reflète pas toute la variété des erreurs et de leur traitement, mais ce n'est pas une «fissure banale». Nous avons corrigé le bogue évident sans avoir le code source et sans reconstruire l'application.

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


All Articles