難読化後のリバースエンジニアリングアプリケーション(パート2)

はじめに



この出版物は、いくつかのリバースエンジニアリング技術の研究を目的としています。 すべての資料は情報提供のみを目的としており、個人的な利益のために使用することを意図したものではありません。



最初の部分の後の推奨読書

外科医が人の働き方を教えられ、メスを与えられたとしても、この知識を誰かの不利益に使用するという意味ではなく、知識のあるアセンブラーはスーパーウイルスを書くことを夢見ていません。

したがって、これらのレッスンでは、クラックやハッキングのヒントを探すべきではありません。



研究テーマ



Visual Studio Atomineer Proドキュメント(以降、APD)のプラグインコードの調査を続けます。 ツールとその機能を詳しく見てみましょう。 したがって、C ++でクラスがあるとします。



class ClassForReadFile { public: ClassForReadFile(); };
      
      





コメントがDoxygenスタイルになるようにAPDを構成します。 クラスにカーソルを置き、CTRL + SHIFT + Dを押します 次のものが得られます。



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





プラグインは、クラスの説明を追加しました。 すべてが素晴らしい! 先に進みます。 クラスがライブラリに属し、エクスポートする必要があるとします。 マクロを追加してクラス定義を変更する



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





C ++(Windows OS)の場合、状況は標準です。 プラグインをご覧ください。 CTRL + SHIFT + Dを押して、期待したものがまったく得られない



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





DATA_READER_DLL_EXPORTS 定義の名前はClassForReadFileの代わりにクラスの名前として定義され、クラスの説明はこの名前から生成されました。 つまり、プラグインコードでは、この状況、つまりクラスのエクスポートは処理されないか、エラーで処理されます。 これが修正を試みるものです。



ステップ1



手がかりを探します。 まず、C / C ++からの関数とクラスのエクスポートは標準的な状況なので、プラグインを正しく「強制」しようとします。 DATA_READER_DLL_EXPORTS 定義の代わりに、 __ declspec命令自体を挿入し、ドキュメントを生成します



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





そして、見よ、彼らは正しいクラスの説明を得た! したがって、APDには、クラスの説明に文字列「__declspec」が存在するかどうかをチェックし、ドキュメントを構築するためのさらなるアルゴリズムを無視するコードがあると結論付けています。



Microsoft SDKの標準のildasm.exeを使用してライブラリを逆コンパイルします。 行「__declspec」を見つけます。 2つのメソッドCmdDocComment :: aおよびCmdDocComment :: bにあります。 クラス1。 私たちはそれをさらに研究することにします。



ステップ2



私たちが探しているのはCmdDocComment ::メソッドであるとすぐに言わなければなりません



__declspecが出会うのはここです。 最も興味深い行のみが表示されます。



文字列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) { //......
      
      







これは、検証後、
リスト[num3] == "__declspec"
削除メソッドが呼び出されます
list.RemoveAt(num3);
推論(大声で考える):



  1. CmdDocComment ::メソッドには、文字列の配列を含むローカル変数があります



     List<string> list = A_0.e;
          
          



  2. この配列の最初の要素は、関数、構造、クラスなどの説明の始まりを格納します。つまり、キーワード「クラス」、「構造」、「ユニオン」
     list[0] == "struct"
          
          



  3. 配列の各要素には個別の単語が含まれます。 この場合、それは{"class"、 "DATA_READER_DLL_EXPORTS"、 "ClassForReadFile"}になります
  4. 配列「e」のすべての要素を巡回し、要素「__declspec」を検索し、それと角括弧内のすべてを削除するループがあります

  5. サイクルを終了するための追加条件があります。 これは、「where」または「:」という単語の場所です。 公式の「where」という言葉は、正直なところ、なじみがありませんが、クラスを継承する場合は「:」を使用します


新しいアルゴリズムと変更の目的を定義します。



1.変更は、残りの機能に影響を与えません



2.アルゴリズムに従って「リスト」配列の要素を削除します

-最初の要素をスキップします。

-次の要素が「:」ではなく「where」ではなく、配列の最後ではない場合は、削除します。



希望のサイクルを書く



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





それをプログラムするために残っています。



ステップ3



大声でプログラムします。 一般的な場合のプログラミングは、ソースコードの作成、コンパイル、リンクです。 しかし、難読化ツールはそのような機会を私たちから奪いました。 推奨されるdnSpyツールを使用します 。 ライブラリ内でCILコマンドの16進コードを直接変更します。これは、非常に刺激的で有益なものです。 始めましょう。 dnSpyを開き、ライブラリをロードします。



メソッドを見つける
画像



whileを選択し、ビューをILに変更します

私たちのサイクル
画像



それはかなりかさ高いですが、私もリストを与えます



私たちのサイクル
 /* 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 //    ,     .
      
      







これで、CILコマンド、HEX表現、ファイルオフセット、および説明のウィンドウができました。 一箇所にすべて。 非常に便利です( CrazyAlex25に感謝)。

「__declspec」に言及しているブロックに注目しましょう。 ブロックオフセット0x0001675A。 これが編集の始まりです。 次に、RemoveAtメソッドを見つけます。 それは私たちにとって変わらずに役立つでしょう。 ブロックの最後のバイトは0x000167BFです。 HEXエディターCtrl + Xに移動し、この範囲に0x00を書き込みます。 変更が何につながったかを保存して確認します。

空のループ
 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } list.RemoveAt(num3); num3--; num3++; }
      
      







次に、新しいロジックを実装します。 まず、条件を追加します



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





表は、新しいコマンドとその説明を示しています。

1119 ldloc.s 指定されたインデックスを持つローカル変数を計算スタックにロードします(短縮形)。
2C61 brfalse.s 値がfalse、null参照、またはゼロの場合、制御を最終ステートメントに渡します。 :num3 == 0の場合、ループ反復子を増やすステップに進みます。 値0x64は、命令0x000167BFへのアドレスオフセットです(リストを参照)
1119 ldloc.s 指定されたインデックスを持つローカル変数を計算スタックにロードします(短い形式)
07 ldloc.1 インデックス1のローカル変数を計算スタックにロードします
6FF700000A コールバート get_Count()-遅延バインドされたオブジェクトメソッドを呼び出し、戻り値を計算スタックにプッシュします
17 ldc.i4.1 整数値1を計算スタックにint32としてプッシュします
59 サブ ある値を別の値から減算し、結果を計算スタックにプッシュします。
2F55 bge.s 最初の値が2番目の値以上である場合、制御を最終命令(短い形式)に転送します。 :num3> list.Count-1の場合、ループ反復子を増やすステップに進みます。 値0x55は、命令0x000167BFの前のアドレスオフセットです。


オフセット0x0001675Aから始まるこれらのバイトを書き込みます。 もう一度保存して逆コンパイルする



最初の条件
 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++; }
      
      







次に、文字列チェック「where」と「:」を追加します。 追加のコメントなしで次のHEXコードを引用します。



 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
      
      





逆コンパイルして、計画したものを取得する



新しいサイクル
 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++; }
      
      







このような変更により、プラグインは次のコードドキュメントを生成します。



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





おわりに



このレッスンでは、バグを修正するために知識を適用する方法を学びました。 もちろん、この例はさまざまなエラーとその処理を反映したものではありませんが、これは「一般的なクラック」ではありません。 ソースコードを持たず、アプリケーションを再構築することなく、明らかなバグを修正しました。



All Articles