Zeronights Crackme 2013ソリューションとそのマトリックス

みなさんこんにちは。 最近、ZeroNights 2013カンファレンスのKaspersky Labからのクラッククッキングについて説明した記事がHabréに掲載されました。 ダーウィンとは異なり、私はOllyDbgでのデバッグのみを使用しました。



ランダムな値を入力します。 失敗したメッセージボックスを取得します。



画像



味方のモジュールウィンドウ(大きなM)を開きます。 Ctrl + B(検索)を押します。 ASCIIフィールドにfailと入力します。 検索ヒット-見つかった:



画像



メッセージの最初のバイト、Breakpoit-> Memory-> On Accessを右クリックします。



デバッグを開始します。 crackmeフィールドに同じ値を入力します。 MessageBoxパラメーターを渡すことができます。



画像



ラインでプッシュする前に、条件付きJNZ遷移が表示されます。 それがTEST EAXになる前に、EAX。 つまり、EAXがゼロに等しくない場合、文字列を含むMessageBoxがクラッシュします
良い仕事です、シリアルは有効です!
。 EAXを取得する場所を確認します。 比較する前に、CALLがあります。 どうやら、このキーもキーをチェックします。 彼女に伝わるものを見てみましょう。 パスワードの長さは、スタックとメールのある行へのポインターを介して渡されます。 ECXレジスタでは、パスワード付きの文字列へのポインターが渡されます。 関数を入力します:



画像



最初のJNZを見てください。 その前に、パスワードの長さが0x12(10進数で18)と比較されます。 そうでない場合、失敗します。 次にループ(左側の黒い角括弧で強調表示されています)で、パスワード文字がチェックされます。 これらは、セット0-9、AZおよびazに属している必要があります。 右側のコメントから、すべての機能がリバースにとって重要ではないことが理解できます。 一部は単純なコピーまたは配列選択関数であり、特に興味深いものではありません。 サイクル後の最初の関数は非常に重要なので、詳細に検討します。



画像



2つのサイクルがすぐに目立ちます。 それらで何が起こっていますか? 最初に、メモリ内の配列に0〜255の値を入力します。

2番目のサイクルでは、すべてがそれほど単純ではありません。 最初のループで埋められた配列と、メールのある行への2つのポインターが使用されました。 配列要素はループ番号によって取得され、メールの文字はメールの長さを法とするループ番号によって取得されます。 これらのバイトは256を法として加算されます(AND EDI操作、0xFFはC構文の__edi%0x100と同等です)。 サイクルのステップで2つの要素番号が取得され、メールで受信されます。 これら2つの要素は逆になります。 メールは、0〜256の要素を置換するキーです。配列は単純に混在しています。 この関数は一般的なものに戻します。 次に、9バイトの配列が選択され、パスワードの最初の9文字がそこにコピーされます。 これらの機能は考慮しません。 「hHeap zabivajetsa」で始まる音訳に関するコメントを付けて、関数に入ります。



画像



怖いように見えますが、よく見ると、アクションが繰り返されていることがわかります。これは、詳細なサイクルがあることを意味します。 1回の反復のみを考慮するだけで十分です。他のすべては同じように発生します。



9バイトの配列の最初の文字はEDXに配置されます。 つまり、パスワードの最初の文字。 次の魔法:



MOV EAX、EDX

SHR EAX、4

SHL EAX、3

SUB EDX、EAX

およびEDX、0F



Cでは、次のようになります。
EDX=(EDX- ((EDX >>4 )<<3))%16
      
      







hHeapのパスワードシンボルを、EDX番号による混合配列の値に置き換えます。 他のキャラクターについても同じです。 関数を終了します。



9バイトの別の配列が割り当てられ、最初の配列からの値がそこに転送されますが、特定の規則に従います。



画像



移動は、MOVとMOVZXのブロックです。 最初の配列をA、2番目の配列を-Bとしましょう。Cの場合:

 b[0]=a[0]; b[1]=a[1]; b[2]=a[2]; b[3]=a[5]; b[4]=a[3]; b[5]=a[4]; b[6]=a[8]; b[7]=a[6]; b[8]=a[7];
      
      







その後、2番目から最初にコピーします。 結局のところ、混合されただけです。



パスワードの2番目の部分については、9バイトの配列を持つすべてのアクションが繰り返されます。 2つの配列を取得します。



画像



関数「moshnaja obrabotka kuchi」に入ります。



画像



2番目の配列のスタックへの転送を確認します。 配列はBで示され、転送先の領域の先頭はDです。C:



 D[0]=B[0]; D[1]=B[3]; D[2]=B[6]; D[3]=B[1]; D[4]=B[4]; D[5]=B[7]; D[6]=B[2]; D[7]=B[5]; D[8]=B[8];
      
      







その後、3回実行される長いサイクル:



画像



画像

誰もが忍耐力を持っているわけではないので、Cのアルゴリズムに置き換えてください。Aを最初の配列、Dをスタック内の配列、Eを他のすべてを出力する新しい配列とし、中間変数を使用します。 次に:

 for(int i=0;i<3;i++){ X[0]=A[0+i*3]; X[1]=A[1+i*3]; X[2]=A[2+i*3]; E[0+i*3]=((X[0]*D[0])%7+(X[1]*D[1])%7+(X[2]*D[2])%7)%7; E[1+i*3]=((X[0]*D[3])%7+(X[1]*D[4])%7+(X[2]*D[5])%7)%7; E[2+i*3]=((X[0]*D[6])%7+(X[1]*D[7])%7+(X[2]*D[8])%7)%7; }
      
      







関数を終了すると、Eの値の配列が配列[1,0,0,0,1,0,0,0,0,1]と比較されます。



画像



一致する場合、シリアルは正しいです。



それで、ここにマトリックスは何ですか。 まず、9つの要素からなる配列は、3 x 3のマトリックスとして表すことができます。 パスワードの2つの部分は2つのマトリックスであることがわかります。 その奇妙なシャッフルを覚えておいてください:



 b[0]=a[0]; b[1]=a[1]; b[2]=a[2]; b[3]=a[5]; b[4]=a[3]; b[5]=a[4]; b[6]=a[8]; b[7]=a[6]; b[8]=a[7];
      
      







行列の各行はその番号にシフトされます。



次に、2番目の配列をスタックに移動する方法を詳しく見てみましょう。



 D[0]=B[0]; D[1]=B[3]; D[2]=B[6]; D[3]=B[1]; D[4]=B[4]; D[5]=B[7]; D[6]=B[2]; D[7]=B[5]; D[8]=B[8];
      
      







そして、これは行列補間です。 なぜ補間が必要なのですか? はい、入力で9バイトの2つの配列を受け取る関数は、それらを行列として乗算するためです。 そして、%7はそれと何の関係があるのでしょうか? すべてがシンプルです。 行列セルは、7を法とするフィールドでモジュラー演算を使用します。

配列[1,0,0,0,1,0,0,0,1]が理解できます。 マトリックス表現では、これは単位マトリックスです。



それでは、どのようにしてメールのキーを見つけるのでしょうか? メールにはキーがないため、keygenを作成しませんでしたが、その理由が理解できます。



メールで配列を混合した後、使用可能な16個の要素を取得します。 つまり、これらの要素を2つの行列の任意のセルに配置できます。 以下ではモジュラー算術のみが使用されるため、これらの要素と7を法とするフィールドメンバーとの等価性を確立できます。これらの要素から、乗算時に単位行列を取得するために2つの反対行列を選択する必要があります。 私たちは試します:



画像



主なことは、行番号によるオフセットを忘れないことです。



どのように逆行列を選択しますか:

シンボルが0になると、2つのシンボルが反対の要素(4と2など)になることがわかります。

1があれば幸運です。



Keygenの問題は、逆行列を作成できる要素が常にあるとは限らないことです。



アドバイスやコメントを歓迎します。 最後まで読んでくれてありがとう。



All Articles