📜 ⬆️ ⬇️

Reverse engineering applications after obfuscation (Part 2)

Introduction


This publication aims to explore some of the techniques of reverse engineering. All materials are presented for informational purposes only and are not intended to be used for personal gain.

Recommended for reading after the first part.
If a surgeon is taught how a person works and gives him a scalpel, it does not mean that he will apply this knowledge to someone to harm, and a knowledgeable assembler does not dream of writing a super virus.
So in these lessons you should not look for hints of cracks and hacks.

Subject of study


We continue to study the plug-in code for Visual Studio Atomineer Pro Documentation (hereinafter referred to as APD). Let's take a closer look at the tool and its capabilities. So, suppose that we have a class in C ++.
')
class ClassForReadFile { public: ClassForReadFile(); }; 

configure APD so that comments are Doxygen style. We get up the cursor on the class and press CTRL + SHIFT + D. We get the following:

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

The plugin has added a beautiful description of the class. All perfectly! Moving on. Suppose the class belongs to the library and we have to export it. Add a macro and change the class definition

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

For C ++ language (Windows OS) the situation is standard. Check out our plugin. Press CTRL + SHIFT + D and get what we expected.

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

The name of the DATA_READER_DLL_EXPORTS define was defined as the name of the class, instead of ClassForReadFile , and the class description was generated for this name. That is, in the code of the plug-in, this situation, namely the export of the class, is either not processed or processed with an error. This is what we will try to correct.

Step 1


We will look for clues. Firstly, since the export of functions and classes with C / C ++ is a standard situation, we still try to “force” the plugin correctly. In place of the DATA_READER_DLL_EXPORTS define, insert the instruction __ declspec itself and generate the documentation

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

And, lo and behold, got the correct description of the class! Thus, we conclude that in APD there is some code that checks for the presence of the string "__ declspec" in the class description and ignores its further algorithm for constructing documentation.

We decompile the library with regular ildasm.exe from the Microsoft SDKs. Find the string "__declspec". It is found in 2 methods CmdDocComment :: a and CmdDocComment :: b. Class one. We will examine it further.

Step 2


I’ll say right away that what we are looking for is in the CmdDocComment :: a method

This is where __declspec is found. Only the most interesting lines are given.

string 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) { //...... 


We made this conclusion based on the fact that after checking
list [num3] == "__declspec"
call delete method
list.RemoveAt (num3);
Reasoning (thinking out loud):

  1. In the CmdDocComment :: a method there is a local variable containing an array of strings.

     List<string> list = A_0.e; 
  2. The first element of this array stores the beginning of the description of the function, structure, class, etc., Ie the keyword "class", "struct", "union"
     list[0] == "struct" 
  3. Each element of the array contains a separate word. In our case, it will be {"class", "DATA_READER_DLL_EXPORTS", "ClassForReadFile"}
  4. There is a loop that bypasses all elements of the array "e", searches for the element "__ declspec", and deletes it and everything in brackets
  5. There is an additional condition for exiting the loop. This is the finding of the words "where" or ":". The official word "where", frankly speaking, is not familiar to me, but ":" is used when inheriting classes

We define the new algorithm and the purpose of the changes:

1. changes should not affect the rest of the functionality

2. delete the elements of the array "list" will be according to the algorithm
- skip the first item;
- if the next element is not ":" and not "where" and not the end of the array, then delete.

Write the desired cycle

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

It remains to be programmed.

Step 3


Program loudly said. Programming in general is writing source codes, compiling, linking. But obfuskator deprived us of this opportunity. Use the recommended dnSpy tool . We will change the HEX CIL command codes right in the library, which, as it turned out, is very exciting and informative! Let's get started Open dnSpy, load the library.

Find our method
image

select while and change the view to IL
Our cycle
image

Also give a listing, although it is rather cumbersome

Our 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 //    ,     . 


Now we have a window in the CIL teams, their HEX representation, offset in the file and description. All in one place. Very convenient (thanks to CrazyAlex25 ).
Pay attention to the block mentioning "__ declspec". Offset block 0x0001675A. This will be the beginning of our edits. Next, find the RemoveAt method. It is useful to us unchanged. The last byte of the block is 0x000167BF. Go to the HEX-editor Ctrl + X and write to this range 0x00. Let's keep and check what the changes have led to.
empty cycle
 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } list.RemoveAt(num3); num3--; num3++; } 


Now we will implement the new logic. To begin, add a condition

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

The table shows the new commands and their description.
1119ldloc.sLoads the local variable with the specified index (short form) into the calculation stack.
2C61brfalse.sSends control to the final instruction if value is false, a null reference, or zero. Note : If num3 == 0, then go on to increment the loop iterator. The value 0x64 is the address offset before instruction 0x000167BF (see listing)
1119ldloc.sLoads a local variable with the specified index into the stack (short form)
07ldloc.1Loads a local variable with index 1 into the stack
6FF700000Acallvirtget_Count () - Calls the method of the object with late binding and puts the return value on the calculation stack.
17ldc.i4.1Puts integer value 1 on stack as int32
59subSubtracts one value from another and pushes the result onto the calculation stack.
2F55bge.sSends control to the final instruction (short form) if the first value is greater than or equal to the second. Note : If num3> list.Count is 1, then go to the increment step of the loop iterator. The value 0x55 is the address offset before instruction 0x000167BF

We write these bytes starting at offset 0x0001675A. Save and decompile again

First 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++; } 


Now we add a check for the strings "where" and ":". I give the following HEX code without additional comments:

 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 

we decompile and get what we planned

New 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++; } 


With such changes, the plugin generates the following code documentation:

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

Conclusion


In this lesson we learned how to apply our knowledge to fix bugs. Of course, this example does not reflect all the diversity of errors and their treatment, but this is not a “banal crack”. We fixed an obvious bug without having the source code, and not rebuilding the application.

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


All Articles