ArtMoney Protection Research。 パート1

ご挨拶! ArtMoney自体は、かなり前に私にとって重要な要素でした 。 このプログラムがどのようにキャッシュされたかについての記事を書き始めるのはこれが初めてではありませんが、常にどこかで停止しました。 今回は、すべてを最後まで仕上げることにしました! さらに、この記事は、初心者向けのクラッキングに関する一連の記事の続きと考えることができます。



そのため、この記事では、 ArtMoneyに keygenをどのように作成したかを学習します(バージョン7.45.1については、 こちらで説明します )。



ステージ1:実行可能ファイルの分析



IDAや他のユーティリティが通常テキストを探すように、私は自分用に英語版のプログラムをインストールしました。



まず第一に、あなたはAMが何に書かれているか/何が詰め込まれているかを知る必要があります。 私のお気に入りのExeInfo PEでそれを開きます(ファイルam745.exe ):











これはAspack v2.24-2.34であると言われています。 まあ、それは複雑なパッカーではないようです。 最初の自動アンパッカーでそれを削除します(記事はアンパックに関するものではないため)。



ステージ2:再度分析



ExeInfo PEをもう一度見ると、プログラムがBorland Delphiで書かれていることがわかります。 いいね! Delphiプログラムの分析にはスーパープログラムIDRInteractive Delphi Reconstructor )を使用します。 ところで、彼女はオープンソースも持っています 。 そこでIDEのバージョンを定義します。



使用可能なすべてのデータベースをダウンロードし、 IDRのあるディレクトリに配置します。 解凍した実験対象の再構築器にそれをドラッグし、分析の終了を待ちます。



IDA Pro (およびHex Rays )ですべての調査プロセスを実行するため、フォーム、メソッド、クラスなどのすべての名前を通知するIDCスクリプトを生成しましょう。 IDRメニューで[ ツール] [ IDC Generator ]をクリックします。 スクリプトが作成されるのを待っています。



次に、 IDAを開き、プログラムをプッシュします。 分析が完了するまで待ちます。 次に、生成されたIDCスクリプトを適用します。IDAProで、 [ ファイル ] [ スクリプトファイル... ]をクリックして、スクリプトを選択します。 申し込みを待っています。



IDAが通常Delphiコードを逆コンパイルするには、 Delphiコンパイラと__fastcall呼び出しを処理していることを通知する必要があります。 残念ながら、生成されたIDCは、 IDA自体と同様に、これについて何も言わない/何も知らない。



IDAコンパイラー設定に移動します: オプションコンパイラー...









次に、クリックしてプログラムを再分析します。 オプション一般分析 →プログラムの再分析とOK



それだけではありません... :)何らかの理由で、 IDCスクリプトはライブラリ関数の境界をマークしませんでした。 このため、逆アセンブルと逆コンパイルのプロセスは容易になりません。 タイプにラベルを付けて指定する必要があります( Yキー)。 よく知られている関数を呼び出して、その境界を見てください。 関数が呼び出しの先ではなく開始する場合、修正します。 関数の先頭より高い命令に移動し(ほとんどの場合、これはretnです )、そこでEを押します(関数の末尾のアドレスを指定します)。 それは良いです。



次に、関数のタイプを指定する必要があります。 @LStrClrを例にとります。 思いやりのあるIDRは、この関数が引数の1つ(文字列のアドレス)を取ることをコメントで指摘しているため、関数のプロトタイプを次のように示します( Delphiには fastcall呼び出し規約 があることを思い出してください)。



void __fastcall LStrClr(char*)
      
      





これがなぜそうなのかが明確になることを願っています。 まあ、 プロシージャのために無効



そして、多くの多くの関数で繰り返します...プロトタイプを指定するとき、そのような単純なルールを使用します:





さて、これで登録手順の検索を開始できます...



ステージ3:あなたはどこにいますか、私の愛する人、どこですか?



フォームのIDRに移動し、(すぐにフォームForm28を開きます)、視覚表示に切り替えます。 登録ウィンドウが表示されます。 ウィンドウをスクロールして、3つのボタンの輪郭を見つけます。 左は、登録アプリケーションの[ OK ]ボタンです。









ステップ4:OnClick()を分析します。 表面



RMBをクリックして、 OKClickハンドラーに移動します。 IDAでは、この手順の最初のアドレスに移動します(ボタンG )。



まず、これがbpベースの関数であることを示します(つまり、ローカル変数のアドレス指定はEBPレジスタからオフセットを引いたものを通過します)。それ以外の場合、ローカル変数は認識されません。 Alt + P (または機能ごとに RMB、 編集機能... )を押して、DAW BPベースのフレームを配置します。



逆コンパイルしてみましょう...すべてがうまくいった場合、逆コンパイラのひどい擬似コードが表示されます。そうでない場合は、プロトタイプを修正します。









ライブラリ/スクリプト関数の呼び出しごとにYを押し、プロトタイプが__fastcallであり、引数が正しく設定されていることを確認します。



IDRによって判断される最初のサイクルは、アルファベットに属するシンボルのキーをチェックし、それらをグローバル変数に接着します。 g_LicKey呼びます。



次に、これまで未知の関数の呼び出しがあり、明らかに、これは実際にはキー検証関数そのものです...



ステップ5:鍵の確認(側面図)



この関数は2つの引数を取ります。1つ目はoutパラメーターで、何らかのエラーコードが含まます。2つ目は文字(英語版では「 A 」、ロシア語版では「 B 」)です。



逆コンパイル...



コードは非常に大きいですが、同意しますが、コードに十分にゆっくりと正しくアプローチし、豊富なコードを避けなければ、ここで何が起こっているのかをうまく理解できます。



非常に多くの場合、逆コンパイラが発行した類似のコードを見つけることができます(もちろん、変数名は異なる場合があります)。



  v2 = g_LicKey; if ( g_LicKey ) v2 = (char *)*((_DWORD *)g_LicKey - 1);
      
      





ここで、 Delphiには独自の特殊な文字列型があり、構造体の形式では次のように記述できると言う価値があります。



 d_str struc ; (sizeof=0x8, mappedto_243, variable size) _top dd ? length dd ? string db 0 dup(?) ; string(C) delphi_string ends
      
      





最初のdvordは常に0xFFFFFFFFであり、2番目は行の長さであり、次に行自体が進みます。 したがって、同様のコード構成は文字列の長さのみを取得します。



別の重要な注意事項:このため、 Delphi文字列のインデックスは1から取得されるため、デコンパイラ/逆アセンブラでインデックスから1が減算されることがよくあります(メモリ内の実際の文字の位置に一致するため)。



次は長さのチェックです: > = 70 and <= 500



次に、キーの最初の文字が関数の2番目の引数と等しいかどうかを確認します。 ' A '、または ' B '、および文字に応じてフラグを設定します。 このフラグをrus_verと呼びました



同意します、かさばっています:



 LStrFromChar((char *)&v193, (char *)(unsigned __int8)g_LicKey[2]); gvar_006F58C0 = Pos(v193, *(char **)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_); gvar_006F58C8 = *(_BYTE *)(*(_DWORD *)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_ + (unsigned __int8)gvar_006F58C0 - 3); gvar_006F58C9 = *(_BYTE *)(*(_DWORD *)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_ + (unsigned __int8)gvar_006F58C0 - 4); LStrFromChar((char *)&v192, (char *)(unsigned __int8)g_LicKey[1]); v242 = Pos(v192, *(char **)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_);
      
      





そして、はるかに良い:



 LStrFromChar((char *)&lic_key, g_LicKey[2]); g_PosChar2 = Pos(lic_key, str_EngAlpha); g_AlphaChar1 = str_EngAlpha[g_PosChar2 - 3]; g_AlphaChar2 = str_EngAlpha[g_PosChar2 - 4]; LStrFromChar((char *)&key_char_1, g_LicKey[1]); key_char1_pos = Pos(key_char_1, str_EngAlpha);
      
      





次に、 key_char1_posが等しいかどうかを確認します 12



これで、最後の2つを除いてキー文字が合計され、合計が0xFFより大きい場合、2で除算し、1で増分します。



 idx = key_len_minus_2 - 2; if ( key_len_minus_2 - 2 > 0 ) { key_idx = 1; do { key_sum += (unsigned __int8)g_LicKey[key_idx++ - 1]; --idx; } while ( idx ); } for ( ; key_sum > 0xFF; key_sum = (key_sum >> 1) + 1 ) ;
      
      





結果の量を16進文字列に変換してから小文字に変換します。



次に、キーの最後の2文字を取得し、それぞれを何らかの種類の関数に渡し、出力で変換された文字を取得します。 この機能を分析しましょう...



ステップ6:文字変換



一般的に、シンボル変換関数は次のようになります。



 LStrFromChar((char *)&inChar_2, inChar); v3 = Pos(inChar_2, str_EngAlpha); if ( v3 > 0 ) { if ( g_PosChar2 > 0xAu ) { for ( i = 1; i < g_PosChar2 - 5; i += 2 ) { if ( i == v3 ) { v3 = i + 1; } else if ( v3 == i + 1 ) { v3 = i; } } } for ( j = g_PosChar2 + 1; j < 60; j += 2 ) { if ( j == v3 ) { v3 = j + 1; } else if ( v3 == j + 1 ) { v3 = j; } } v6 = v3 - g_PosChar2 + ((char)(v3 - g_PosChar2) < 0 ? 0x3E : 0); if ( flag_1 ) v2 = str_RusAlpha1[v6 - 1]; else v2 = str_EngAlpha1[v6 - 1]; } return v2;
      
      





簡単そうです。 私たちはすぐにそれを逆にしなければならないと言います。 それをDecodeCharと呼びましょう



その結果、キーの最後の2文字がこの関数によって変換され、以前に取得したキーの他のすべての文字の16進数の合計と比較されます。



ToRevertこの単語を使用して、キー検証機能の逆で重要なポイントをマークします ):最後に、キーの準備ができたら、その文字の合計を考慮し、それを16進数0xXX )に変換し、次に各ニブル-反転DecodeChar 、およびキーに接着します。


さらに、キーの3番目の文字が変換され、hexからintに変換され、ビットがチェックされます。 それで終わりです。



 v202 = meffi_DecodeChar(g_LicKey[3], 0); PStrCpy(&v132, str_Hex); v134 = v202; v133 = 1; PStrNCat(&v132, &v133, 2); LStrFromString((char *)&v129, &v132); key_char_3 = StrToInt(v129); k3_flag1 = (key_char_3 & 1) != 0; k3_flag2 = (key_char_3 & 2) != 0; k3_flag3 = (key_char_3 & 4) != 0; k3_flag4 = (key_char_3 & 8) != 0;
      
      





ビットの目的がわからないため、少なくとも意味のある名前を付けます。



繰り返しますが、わからない関数で、フラグの1つが渡されます。



 key_idx = 5; sub_66408C(&key_idx, k3_flag1, &v128);
      
      





最後のパラメータは、明らかに、出力文字列です。 Trim()に渡され、後で使用されます。



この関数に対応するプロトタイプをすぐに与えます:



 void __fastcall sub_66408C(int idx, bool flag, char *output)
      
      





ステップ7:キーから文字列を読み取る



はい、これがこの関数の機能です。 デバッグが示すもの、およびコードの簡単な概要。 しかし、まず最初に。



 while ( g_LicKey[*(_DWORD *)idx_1 - 1] != g_AlphaChar1 && LStrLen(g_LicKey) >= *(_DWORD *)idx_1 )
      
      





最初のパラメータはdword(int)へのポインタとして使用されることに注意してください。そのため、その型をint *に変更しますDelphiでは、これはvar- argumentsと呼ばれます)。



すべての型変換と引数の調整の後、関数は次の形式を取ります。



 while ( g_LicKey[*idx_1 - 1] != g_AlphaChar1 && LStrLen(g_LicKey) >= *idx_1 ) { c1 = g_LicKey[*idx_1 - 1]; if ( c1 == g_AlphaChar2 ) { LStrFromChar((char *)&c1_str, c1); c1_str_ = c1_str; LStrFromChar((char *)&c2_str, g_LicKey[*idx_1]); c2_str_ = c2_str; LStrFromChar((char *)&c3_str, g_LicKey[*idx_1 + 1]); LStrCatN(c3_str, c2_str_, c1_str_, gvar_0070DF4C); PStrCpy(&str_hex, str_Hex_0); cc[1] = meffi_DecodeChar(g_LicKey[*idx_1], 0); cc[0] = 1; PStrNCat(&str_hex, cc, 2); PStrCpy(&hexVal, &str_hex); cc[1] = meffi_DecodeChar(g_LicKey[*idx_1 + 1], 0); cc[0] = 1; PStrNCat(&hexVal, cc, 3); LStrFromString((char *)&hexVal_1, &hexVal); value = ValLong(hexVal_1, &outCode); LStrFromChar((char *)&value_1, value); LStrCat((char *)&output_2, value_1); *idx_1 += 2; } else { LStrFromChar((char *)&keyChar, g_LicKey[*idx_1 - 1]); LStrCat((char *)&gvar_0070DF4C, keyChar); c = meffi_DecodeChar(g_LicKey[*idx_1 - 1], flag_1); LStrFromChar((char *)&c_str, c); LStrCat((char *)&output_2, c_str); } ++*idx_1; } ++*idx_1;
      
      





g_AlphaChar1が検出されるまで、キー文字が読み取られていることがわかります。 文字がg_AlphaChar2と等しくない場合、グローバル変数に接着し、DecodeChar()で変換され文字を出力バッファーに接着します。



g_AlphaChar2文字をヒットした場合、それに続く2文字を読み取り、変換し、数値に変換して、出力バッファーに接着します。 未修正フォームの同じ2文字は、 g_AlphaChar2でグローバル変数に接着されます。 それをg_stringFromKey1と呼びましょう



どうやら、この関数はDecodeStringと呼ぶことができます。



ToRevert :キーにエンコードする文字列は、 DecodeString()の逆関数を使用して変換する必要があります。


PSこれで、 ArtMoneyのキーイングに関する記事の最初の部分で、おそらく終了します。 第二部では、新たな困難に直面しているキー検証コードと、馬鹿げたコードを逆コンパイルし続けます。 しかし、これは私たちを止めますか?

PPS より興味深い逆の記事を提供します!



All Articles