単純なクラックミーの調査(パート3)

こんにちは、Habralumi。

私は、奇抜な研究に関する私の記事シリーズの第3部を紹介します。 このトピックでは、いくつかのパッカーを手動で解凍し、複雑ではないアンチデバッグ方法を克服する方法について説明します。





1.手動開梱



必要なツールから:



理論


パックされたプログラムは次のように機能します。

最初に、アンパッカーコードが起動され、パックされたプログラムコードの解読が開始されます。 復号化が完了すると、OEPプログラムにジャンプし、既に解凍されたプログラムコードの実行が開始されます。



解凍アルゴリズムは次のとおりです。

1. RVA OEPを見つけます。

2.プログラムをダンプします。

3.インポートテーブルを復元します。

4.エントリポイントをオリジナルに変更します。



したがって、必要なOEPアドレスは次の式で計算されます。

RVA OEP = VA OEP - ImageBase



、ここで:

Image Baseは、プログラムがメモリにロードされるメモリ内のアドレスです。

OEP(Original Entry Point)は、プログラムがパックされていない場合にプログラムが実行を開始するアドレスです。

仮想アドレス(VA) -メモリ内のアイテムの仮想アドレス

相対仮想アドレス(RVA) -相対仮想アドレス。 アドレスはImageBaseに相対的です。

たとえば、OEPが00301000、ImageBaseが00300000の場合、RVA OEPは1000になります。ImageBaseの値は、PEヘッダーのエディターで確認できます。

RVA OEPを見つけたら、ダンププログラムを削除する必要があります。 ダンプの意味-メモリの領域(部分)またはメモリからディスクに保存されたファイル。 ダンプを削除するとは、目的のメモリ領域(通常はプログラムが占有している)をハードドライブに保存することです。 その結果、解凍されたプログラムを取得します。

次に、インポートテーブルを復元する必要があります。 インポートテーブルには、プログラムの操作中にプログラムが使用する機能に関する情報が格納されます。 最初に、インポートテーブルには、インポートされた関数の名前がファイル内にあるアドレス、つまり プログラム操作中に使用される関数。 プログラムが起動すると、これらのアドレス(これはすべてメモリ内にあります)は、インポートされた関数の直接アドレスによって上書きされます。 オペレーティングシステムのすべてのバージョンで関数の直接アドレスを取得するために使用される関数名のアドレスを元々含むセルには、プログラムがダンプされたシステムのこれらの関数のアドレスが既に入力されているため、復元する必要があります。 この場合、関数の名前のアドレスに関する情報は復元できなくなり、そのようなプログラムを開始すると、すでに記録されている関数の直接アドレスが使用されます。 そして、これはOSの他のバージョンでのプログラムの動作不能につながります。

そして最後に、OEPを復元します。 これは、任意のPEヘッダーエディターを使用して実行できます。

それが理論全体です。



練習する


この記事では、2つのパッカーについて検討します。 これらはUPXとASPackです。 他のパッカーを開梱しても、これら2つを開梱してもそれほど違いはありません。



UPX


最新バージョンをダウンロードしてください。 何かを詰めます。 これをデバッガーで実行します。

パックされたコードの復号化中に、パッカーはスタックを最大限に活用します。 当然、パックされたプログラムが正しく機能するために、パッカーはスタックの初期値を保存し、アンパック後に復元する必要があります。 ほとんどすべてのパッカーでは、OEPに切り替える前にスタックを復元すると、スタック内の値がesp-4で読み取られます。

したがって、Ollyでは、コマンド「hr esp-4」を使用して分類を行います。 次に、プログラムを実行し、ここでブレークが機能したことを確認します。



00472176 . 8D4424 80 LEA EAX,DWORD PTR SS:[ESP-80] //

0047217A > 6A 00 PUSH 0 //

0047217C . 39C4 CMP ESP,EAX //

0047217E .^75 FA JNZ SHORT 111.0047217A //.

00472180 . 83EC 80 SUB ESP,-80

00472183 .^E9 386EFEFF JMP 111.00458FC0 // OEP








次に、プログラムをOEPにトレースします(大まかに言うと、OEPになります)。 Olly Dumpプラグインを使用して、プログラムをダンプします。

パッケージ化されたプログラムとImpRECを実行します。 ImpREC'aプロセスのリストには、プログラムがあります。 [RVA]フィールドに、RVA OEPと入力します(上記の検索方法を参照)。 [自動検索]をクリックします。 何かが見つかった可能性が高いというメッセージが表示されたら、[インポートを取得]をクリックし、関数がリストに表示されたら、[ダンプを修正]をクリックしてダンプを選択します。 これでプログラムが解凍されました。



アスパック


ここでは、いくつかの点を除いてすべてが似ています。 クラックをインストールすると、次のようになります。



0046F416 75 08 JNZ SHORT Test_Com.0046F420

0046F418 B8 01000000 MOV EAX,1

0046F41D C2 0C00 RETN 0C

0046F420 68 C08F4500 PUSH Test_Com.00458FC0 // OEP

0046F425 C3 RETN // OEP








残りの手順は同じです。



Kryakmisの開梱


こちらがこの亀裂です。

上記のように開梱します。 すべてが完全に開梱されています(参考のため、OEP = 00401000)。 その後、GetDlgItemTextA関数の呼び出しでブレークを絞り、開始し、偽のパスを入力し、ボタンをクリックして、ここに到達します。



00401206 . E8 1B060000 CALL <JMP.&user32.GetDlgItemTextA> //

0040120B . 8B35 00604000 MOV ESI,DWORD PTR DS:[406000]

00401211 . 81C6 7F010300 ADD ESI,3017F

00401217 . 81EE 66060000 SUB ESI,666

0040121D . 81F6 ADDE0000 XOR ESI,0DEAD

00401223 . BB 33604000 MOV EBX,dddddddd.00406033

00401228 . C0E0 03 SHL AL,3 // !

0040122B . 83F8 78 CMP EAX,78 // !

0040122E . 0F85 9A050000 JNZ dddddddd.004017CE // ""








EAXには、入力されたパスワードの長さがあります。 SHL al、3コマンドを使用して、AL値を3だけ左に論理シフトし、理想的には78を取得する必要があります。逆shl手順を実行します。 このshr 78.3 = 0F = 15は、有効なパスワードの長さです。 その後、ある時点まで非常に長い間トレースし、途中でいくつかのアンチデバッグのトリックに出会いました。



004012B0 0F31 RDTSC //

004012B2 8BC8 MOV ECX,EAX

004012B4 0F31 RDTSC //

004012B6 2BC8 SUB ECX,EAX

004012B8 F7D1 NOT ECX

004012BA 81F9 00500000 CMP ECX,5000

004012C0 -7F FE JG SHORT crackme2.004012C0 //








RDTSC命令は、最後のプロセッサリセット以降のクロックサイクル数をEAXレジスタに返します。 上記のコードでは、この命令を2回呼び出してから、出力の違いを特定の参照値と比較しています。 実際、プログラムがデバッガーなしで実行されると、クロックの差は小さくなり、デバッガーの下にあると、差は大きくなります。 このような多くの瞬間を見つけて、それらをパッチするか、フラグを変更するだけです。 次の瞬間にトレースするとき:



0040126A 0F31 RDTSC

0040126C 8BC8 MOV ECX,EAX

0040126E 0F31 RDTSC

00401270 2BC8 SUB ECX,EAX

00401272 F7D1 NOT ECX

00401274 81F9 00500000 CMP ECX,5000

0040127A 7C 05 JL SHORT crackme2.00401281

0040127C -E9 139C04EC JMP EC44AE94

00401281 EB 0D JMP SHORT crackme2.00401290








0040127Cに注意してください。 ここでは、存在しないアドレスにジャンプするため、00401281への移行に大胆にパッチを当てます。そのような瞬間がいくつかあります。 次のコードにトレースします。



004014F1 0FB613 MOVZX EDX,BYTE PTR DS:[EBX] ; EBX, EDX

004014F4 B9 08000000 MOV ECX,8

004014F9 AC LODS BYTE PTR DS:[ESI] ; EAX -

004014FA 24 01 AND AL,1 ; and 1

004014FC 74 04 JE SHORT crackme2.00401502

004014FE D0E2 SHL DL,1

00401500 72 08 JB SHORT crackme2.0040150A

00401502 D0E2 SHL DL,1

00401504 0F82 BF020000 JB crackme2.004017C9 ;

0040150A ^E2 ED LOOPD SHORT crackme2.004014F9

0040150C 43 INC EBX

0040150D 58 POP EAX

0040150E 48 DEC EAX

0040150F 0F84 9A020000 JE crackme2.004017AF

00401515 50 PUSH EAX

00401516 ^EB D9 JMP SHORT crackme2.004014F1








私は非常に長い間この瞬間について考えました。 これはパスワード生成手順であることが判明しました。 つまり、パスワードは平文でプログラムに保存されません。 生成中のALは値1または0を取ります。したがって、パスワードを生成してすべての値を書き込むための手順全体をたどると、バイナリ値の巨大な文字列が得られました(便宜上、10進数に変換しました)。



119 101 108 108 100 111 110 101 85 102 105 110 100 109 101







入力したパスワードの特定の文字をEDXに入れた後、8文字ごとに(2行で10進数に変換したため、ここでは各文字がスペースで囲まれています)生成されたことを考えると、上の行は有効なパスワード。 再コード化すると、「welldoneUfindme」が判明しました。



2.いくつかのアンチデバッグ方法



デバッグ対策には多くの方法がありますが、この記事から始めて、単純なものから複雑なものまで順番に分析していきます。

そのため、crackmes.deにはAntiOllyと呼ばれる特別なクラックがあります。 ダウンロードして実行すると、次のウィンドウが表示されます。



画像



ここで、彼らは何も見つからなかったと私たちに伝えました。 これで、「良い」メッセージがどのように見えるかがわかりました。 kryakmisをOllyにロードすると、次のように表示されます。



画像



エラーは、クラックのヘッダーを分析したOllyがエラー(ヘッダー)を見つけたという事実が原因です。 しかし、それは修正可能です。 PE ediorにクラックをロードし、オプションのヘッダータブに移動します。 NumberOfRVAandSize、Base of Code、およびBase of Dataパラメーターの「大きすぎる」値に注意が向けられました。 通常、NumberOfRVAandSize = 0x00000010、Base of Code = 00001000、Base of Data =00002000。これらのパラメーターを「通常」に変更し、デバッガーの下でクラックを実行します。 不正なメッセージは表示されなくなります(つまり、メッセージは残りますが、分析には影響しません)。クラックを安全に分析できます。 したがって、これは最初のアンチデバッグのトリックでした。

デバッグ中にkryakmisを実行すると、「悪い」メッセージが表示されます。



画像



プログラムを分析した後、アンチデバッグコードのセクションを見つけます。



00401010 |. FFD7 CALL EDI // GetTickCount

00401012 |. 6A 00 PUSH 0

00401014 |. 68 34214000 PUSH AntiOlly.00402134

00401019 |. 8BF0 MOV ESI,EAX

0040101B |. FF15 DC204000 CALL DWORD PTR DS:[4020DC] //FindWindowA

00401021 |. 85C0 TEST EAX,EAX

00401023 |. 75 04 JNZ SHORT AntiOlly.00401029

00401025 |. 884424 0F MOV BYTE PTR SS:[ESP+F],AL

00401029 |> FF15 04204000 CALL DWORD PTR DS:[402004] //IsDebuggerPresent








そのため、最初はGetTickCount関数です。 この関数は、システムの開始から経過した時間をミリ秒で返します。 実際、この関数には別の呼び出しがあります。 さらに、次のコードでは、これらの機能を実行した結果として得られた値の差が測定されます。 これは2番目の反デバッグトリックでした。

次にFindWindowAが呼び出され、OllyDbgというタイトルのウィンドウが検索されます。 最後に、IsDebuggerPresentを呼び出します。これは、プログラムがデバッグされているかどうかを単純に確認します。 もしそうなら、Eax 1で、そうでなければ0。

krykmisが行うチェックは次のとおりです。



0040102F |. 85C0 TEST EAX,EAX // IsDebuggerPresent

00401031 |. 75 02 JNZ SHORT AntiOlly.00401035 // 00401035

00401033 |. 32DB XOR BL,BL // BL

00401035 |> FFD7 CALL EDI // GetTickCount

00401037 |. 2BF0 SUB ESI,EAX

00401039 |. 83FE 64 CMP ESI,64 //

0040103C |. 76 0D JBE SHORT AntiOlly.0040104B // ok 0040104B

0040103E |. A1 44204000 MOV EAX,DWORD PTR DS:[402044]

00401043 |. 50 PUSH EAX

00401044 |. 68 3C214000 PUSH AntiOlly.0040213C

00401049 |. EB 3F JMP SHORT AntiOlly.0040108A // ok

0040104B |> 84DB TEST BL,BL

0040104D |. 74 14 JE SHORT AntiOlly.00401063 // IsDebuggerPresent

0040104F |. 8B15 44204000 MOV EDX,DWORD PTR DS:[402044]

00401055 |. A1 60204000 MOV EAX,DWORD PTR DS:[402060]

0040105A |. 52 PUSH EDX

0040105B |. 68 3C214000 PUSH AntiOlly.0040213C

00401060 |. 50 PUSH EAX

00401061 |. EB 2E JMP SHORT AntiOlly.00401091

00401063 |> 807C24 0F 00 CMP BYTE PTR SS:[ESP+F],0 // FindWindow

00401068 |. 74 15 JE SHORT AntiOlly.0040107F








レジスタのパッチや編集の場所は明確になったと思います。

ご清聴ありがとうございました。



All Articles