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