そのため、この記事では、 ArtMoneyに keygenをどのように作成したかを学習します(バージョン7.45.1については、 こちらで説明します )。
ステージ1:実行可能ファイルの分析
IDAや他のユーティリティが通常テキストを探すように、私は自分用に英語版のプログラムをインストールしました。
まず第一に、あなたはAMが何に書かれているか/何が詰め込まれているかを知る必要があります。 私のお気に入りのExeInfo PEでそれを開きます(ファイルam745.exe ):
これはAspack v2.24-2.34であると言われています。 まあ、それは複雑なパッカーではないようです。 最初の自動アンパッカーでそれを削除します(記事はアンパックに関するものではないため)。
ステージ2:再度分析
ExeInfo PEをもう一度見ると、プログラムがBorland Delphiで書かれていることがわかります。 いいね! Delphiプログラムの分析にはスーパープログラムIDR ( Interactive 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*)
これがなぜそうなのかが明確になることを願っています。 まあ、 プロシージャのために無効 。
そして、多くの多くの関数で繰り返します...プロトタイプを指定するとき、そのような単純なルールを使用します:
- 引数はeax 、 edx 、 ecx 、 stackを介して渡されます (左から右へ)。 これは、逆コンパイラが「 正のsp値 」に遭遇した場合に役立ちます。
- StrCatN関数は、 ArgCnt:Integerが指定されている場合、 可変引数関数であり、プロトタイプには...が含まれます。 逆コンパイラでは、そのような関数の呼び出しの各場所に立って、 Numpadキーボードで+ / -を押して、引数を追加/削除する必要があります。 金額は、dysmusリストに記載されています。 プロトタイプの例: " void LStrCatN(char *、char *、...) ";
- 関数が、 Delphiプロトタイプから判断して、文字列へのポインターを返す場合、ほとんどの場合、プロトタイプの暗黙的な出力引数であり、最後の引数として追加する必要があります。
さて、これで登録手順の検索を開始できます...
ステージ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 より興味深い逆の記事を提供します!