デバッガーとは何ですか、その使用方法と実装方法
は、記事の
最初と
2番目の部分を読んだ後です。 記事の最後の部分では、デバッガーの動作原理に関する知識に基づいて、デバッガーを処理するいくつかの方法を検討します。 アンチデバッグトリックのテンプレートセットは提供しません。必要に応じて、インターネット上でこれらすべてを見つけることができます。抽象的なアプリケーションに基づいて、少し異なる方法でそれを試してみます。
すぐに、私は予約をします。アプリケーション/デバッガーに反対して、最後のものが常に勝ちます:)
しかし、有能な専門家がそれを使用し、そのような専門家に対処することは実質的に役に立たない場合にのみです(もちろん、少なくとも同じ資格を持たない限り)。
確かに、実践が示すように、有能な専門家は彼らのために面白くない仕事に従事せず、彼らはまだ科学の花崗岩をかじっていないいくつかの明らかなトリックにつまずくかもしれないリバースリバースの容赦に任せています。
ここでは、非常に単純化された形でのみ検討するものです。
最も単純なShareWare:
売ることに決めたソフトウェアがあるとします。 簡単にするために、空のフォームからの通常のVCLアプリケーションとします(空ではありませんが、全体が画像になっています)。販売したいと思います。 世話をする必要がある最初の質問は、私たちの写真をそれを支払った人だけに見えるようにする方法ですか? より正確に-トライアルユーザーと法的ユーザーを区別する方法は?
最も明らかな解決策が鍵です。 トライアルユーザーは彼を知らず、実際のお金で支払いを行った合法的なユーザーは、アプリケーションの合法的なコピーをアクティブにして写真を楽しむことができます。
キーはとても重要です。
新しいVCLアプリケーションを作成し、TImageフォームに画像をスローし、VisibleをFalseに設定します。 次に、2つのTEditをフォームに配置します。1つ目はユーザー名用、2つ目はアクティベーションコード用です。 さて、2つのボタン-アプリケーションを閉じてアクティブにします。
さて、このようなもの:
その後、「トップシークレット」アクティベーションコードを記述します。
function TForm1.GenerateSerial(const AppUserName: string): string; const MagicSerialMask: int64 = $C5315E6121543992; var I: Integer; SN: int64; RawSN: string; begin SN := 0; Result := ''; for I := 1 to Length(AppUserName) do begin Inc(SN, Word(AppUserName[I])); SN := SN * 123456; end; Sn := SN xor MagicSerialMask; RawSN := IntToHex(SN, 16); for I := 1 to 16 do if ((I - 1) mod 4 = 0) and (I > 1) then Result := Result + '-' + RawSN[I] else Result := Result + RawSN[I]; end; procedure TForm1.btnCheckSerialClick(Sender: TObject); begin if edSerial.Text <> GenerateSerial(edAppUserName.Text) then Application.MessageBox(' ', PChar(Application.Title), MB_OK or MB_ICONERROR) else begin Image1.Visible := True; Label1.Visible := False; Label2.Visible := False; Label3.Visible := False; edAppUserName.Visible := False; edSerial.Visible := False; btnCancel.Visible := False; btnCheckSerial.Visible := False; end; end;
コードの本質は次のとおりです。
ユーザー名に基づいて、アプリケーションは特定のシリアル番号を生成し、入力したユーザーと比較します。 すべてが正常である場合、アクティベーションに関与するすべてのコントロールが削除され、ユーザーが見たいと思っていた画像が表示されます。
これを言ってみましょう:
(まあ...私が最初に見つけたもの:)
操作後、「it」はさまざまなシェアウェアサイトで公開され、プログラマフォーラムへのリンクが「test plz protection」という形式のトピックで提供されることもあります。
そして、クラッカーはどのように見えますか?
彼はデバッガーを使い(簡単にするために、同じOlly Debugを使います)、次の図を見ます:
彼はアプリケーションコードを持っていませんが、ソフトウェアの「ディフェンダー」を開始するという典型的な間違いがあります-間違ったキーに関するダイアログを表示します。
これはクラッカーに何を与えますか?
彼はBPをMessageBoxA呼び出しに配置し、アプリケーションを起動してこのメッセージの呼び出しをキャッチします。その後、「OK」ボタンをクリックすることで、エラーが呼び出されたコードに戻ることができます。この呼び出しが発生します:
写真では、プログラムの決定点が感嘆符で強調表示されています。
彼がやるべきことは、JMPのJE命令を修正し、シリアルコード検証を無効にし、アプリケーションがアクティブ化されたときにのみ実行されるコード領域への有効な遷移を確保することだけです。
どういうわけか明確ではないでしょう?
それでは、Delphiデバッガーの写真を次に示します。
ここでは、デバッグのためにコードがより理解しやすく、アドレスを解読して読み取り可能な形式にするため、コードの読み取りがより便利になります。 たとえば、決定が行われるアドレス0x475729に到達する前に、TEditsからテキストが受信され、GenerateSerialプロシージャが呼び出されることがはっきりとわかります。
クラッカーにはそのような情報がなく、前の画像に見られるように、分析のために画像を多少理解できるようにするために、すべての呼び出しを分析する必要があります。 実は、ここで少し誇張しますが、実際、アプリケーションマップはツールを使用して非常に簡単に構築されますが、...一部の人々は、イルカのシステムモジュールをデバッグしようと努力することもあります。
さて、スクリーンショットの0x475729にあるニュアンスには、2つの異なる指示があります-JZとJE、これらは逆アセンブラーを解釈するニュアンスであり、同一です。
私に何度か表明されている興味深いアプローチがあります。
ここで少し上に、私はBPをMessageBoxAに置くことを発表しました。彼らは、MessageBoxWを呼び出すと呼び出しをキャッチできないことを教えてくれます。 このステートメントは、プラスの付いた堅実な4に対するものです。はい、確かに、アプリケーションがUnicode APIを呼び出すと、ブレークダウンで小さなミスがありますが、ニュアンスがあります。 MessageBox呼び出しスタック全体を展開してみましょう。
それがどのような面白いスキームであるかを見てください:
MessageBoxA-> MessageBoxExA-> MessageBoxTimeOutA-> MessageBoxTimeOutW-> SoftModalMessageBox()
そのため、必要な呼び出しをキャッチするために、MessageBoxW関数も呼び出すことで、リストされた関数のいずれかにBPを呼び出すことができます(通常はMessageBoxTimeOutWで十分です)。
微妙なニュアンスがありますが、Delphiにはウィンドウを表示する他の方法があります。
さて、たとえばShowMessage()。 このメソッドは、MessageBox APIを呼び出しません。
このメソッドは、必要に応じてボタンが配置される別のフォームを作成する形で完全に実装され、一般にこれらはVCL自体の内部であり、デバッガーでは何も明確ではないという理由を聞くのは十分に面白いです
この呼び出しがShowWindow APIに基づいていない場合は、そのようになります。この呼び出しから、必要なコードのスタックに進みます。
対話の課題はまだありますが、まったく同じ料理があります。 このすべては、多くの時間なしで検出されます。
したがって、ノートブックに最初の結論を導きます。
このチェックの直後に失敗したコードチェックに関するメッセージを呼び出すことは、悪意の兆候です。
アプリケーションの整合性制御の導入:
それでは-彼らは私たちをハッキングし、アプリケーションの1バイトだけを変更しました。 今、私たちの楽しい写真は、完全に無料で利用できます。
悲しいことに、重要ではない-私たちは戦う...
ハッキングは、アプリケーション本体の直接編集を通じて発生しました。
そのため、タスクは成長しました。ソースコードの整合性の検証を提供することです。
恐ろしいように聞こえますが、実際には実際には不可能です:)
このテストには何を適用できますか?
流行語はたくさんあります。デジタル署名、ディスク上のファイルのイメージの確認、チェックサムでコードセクションをチェックします。 すべてが空です-結局、とにかく、メモリ内のアプリケーションコードの現在の値を取得する必要が生じます...
さて、私たちはデジタル署名を見ています。 彼女は、最初に支払った。 次に、傍受に対して脆弱なWinVerifyTrust関数のAPIを呼び出してチェックします。 第三に、ImageRemoveCertificateを使用して通常の方法で簡単に削除できます。
選択肢ではありません。ディスク上のファイルの画像を確認するにはどうすればよいですか?
ここでも、すべてが悲しいです。 見て、実行可能ファイルにパッチが適用されているので、ディスク上のイメージと比較してこれを判断し、同じParamStr(0)(たとえば)を介して現在のファイルへのパスを取得し、このパスでファイルを開いてチェックを開始しますが、...
ただし、OpenFile / CreateFileを呼び出す段階で、クラッカーは対応するパラメーターのパスを元の変更されていないイメージへのパスに置き換え、すべてのチェックがフォレストを通過します。
別の興味深い点があります。 ただし、アプリケーションをディスクに保存して変更することはできません。 ローダーのようなものがあります。 彼らの本質は、プロセスを開始し、メモリ内のアプリケーション本体を直接変更するという事実にあります。
たとえば、前の記事からデバッガーを取り出し、それを使用して魔法の絵でアプリケーションを起動し、エントリポイントに到達したら次のコードを実行します。
procedure TTestDebugger.OnBreakPoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord; BreakPointIndex: Integer; var ReleaseBreakpoint: Boolean); var JmpOpcode: Byte; begin if ExceptionRecord.ExceptionAddress = Pointer(FCore.DebugProcessData.EntryPoint) then begin JmpOpcode := $EB; FCore.WriteData(Pointer($475729), @JmpOpcode, 1);
ディスク上のアプリケーションは変更されませんが、JE命令の代わりに、記録されたJMP命令により直接ジャンプが実行されます。 すでにかなり悲しいです、なぜなら この場合、整合性をチェックするための最初の2つのオプションは機能しないことが保証されています。
3番目のオプションは残り、アプリケーション本体のコードセクションを直接チェックします。
これは実装のためのかなりリソースを消費するオプションであり、次の理由で常に成功につながるとは限りません。
まず、チェックサム定数。 アプリケーションの本体に保存されている場合、クラッカーはそれらを正しいものに変更します。 (ノートブックの2番目の結論は、アプリケーションのコードブロックのCRC定数です。トーンが悪いです)。
第二に、記事の後半では、MIA-Memory Breakpointについて説明しました。 これは、コードの整合性チェックを検出するための理想的なメカニズムです(さらに有能なHBP-Hardware BreakPointを考慮しない場合)。
単純に機能します。コードの現在のセクションが保護メカニズムによって制御されている疑いがある場合、MVRまたはHBPがハングして、コード整合性チェック自体の場所を判断します。
そのようなチェックが検出されると、パッチによっても無効になります。
さて、私たちは実際に行き詰まりに行きました:加入者は加入者ではありません:)
しかし...
もちろん外に出ることができますが、...
しかし、最初に、一般的なアプリケーションコード整合性チェックの実装方法を見てみましょう。
より具体的には、最初にあったコードを変更から保護する必要があります。 これを行うには、アプリケーションの実行中に何らかの形でメモリ内の場所を見つける必要があります。
ラベルは「すべて」です。
ほとんどのヒンジ付きプロテクターはラベルに基づいて機能するので、なぜ別の自転車を考え出す必要があります。 ラベルとは何ですか-原則として、goto()で使用される誰もがあまりにも愛されていないラベルであり、最も怠theな人だけが高度に修飾された "FI"を表現していません。
しかし...私たちはそれらについてどう思いますか? 私が言ったように-私たちのタグはすべてです:)
確かに、微妙な違いがあります。ラベルは、プロシージャ内のコードの小さな部分を制御するときに使用すると便利です(クロスチェックのために-後で)、合計でいくつかのプロシージャに興味があります。
このため、ラベルは機能しませんが、完全性チェックコードからアドレスを取得できるラベルとしての空のプロシージャは機能します。
さて、ヒープには整合性を計算するための手順全体が必要です。また、(実際には上記のニュアンスの1つでした)データブロックのCRCを比較する特定の定数が必要です。
さて、暴言をやめるのはもうやめましょう。
const CheckedCodeValidCheckSum: DWORD = 248268;
ここにあるもの:
空のプロシージャCheckedCodeBeginおよびCheckedCodeEndの形式の2つのラベル、これら2つのラベル間のデータの「チェックサム」の計算は、CheckCodeProtectプロシージャによって実行されます。
原則として、複雑なものはまったくありませんが、分析してみましょう。
実際には、多くの理由:
- このコードは、ディスク上のアプリケーション本体のパッチを検出します(起動時に既にバイトが変更されているため)。
- このコードは、ローダーによってアプリケーション本体のパッチを検出します(上記を参照)。
- そして、このコードは...最後の記事の写真を覚えていますか?
はい、はい、これはデバッガーによってインストールされたブレークポイントです。 また、BPのインストールメカニズムはアプリケーション本体の変更にあるため、彼のコードも完全に検出します。
ノートブックの3番目の注意点-BPの検出は、コードの本文をチェックすることで実行されます。
確かに、残念なことに、ここではすべてがそれほど単純ではありません。場合によっては、このコードは機能しませんが、急ぎません。これに到達します。
さて、悲しいことに、私が言ったように、このチェックは簡単に検出されます。 たとえば、以下はデバッガーの下からのスクリーンショットで、スキャンの開始時にすぐに中断されます。
チェックサム計算手順のコードは青で強調表示され、デバッガーは保護された領域を読み取ろうとする最初の試みでアドレス0x467069で中断されました。
正確に言うと、ここで少しごまかしました。確認コードがチェック対象の範囲外にある場合、この命令で停止しただけなので、もちろん最初の「PUSH EBX」で停止しました。
しかし、これは歌詞です、質問は異なります、そして今、何をしますか?
まあ、最初に、すべてがそれほど怖いわけではありません。 ここでは、アプリケーションコードの整合性の単一のチェックのみが実装されています。 はい、簡単に検出されます。 はい、パッチでも簡単に削除できますが、それらのいくつかが相互に制御し合うことを妨げるのはなぜですか? それらも同様に削除されますか? さて、質問ではありませんが、さらに追加しますが、費用はいくらですか?
保護自体の開発者から直接アプリケーションセキュリティの分析に製品が送られてきました(申し訳ありませんが、名前はありません)。 VM初期化コードをすばやく見て、すぐに分析のパスを概説しました。特定のAPI関数を呼び出すときに、小さなデータブロックの暗号化アルゴリズムを引き出すだけでした。 問題は、アプリケーションの1バイトにパッチを適用するとすぐに、チェックサム検証メカニズムが機能することでした。 当然、私はすぐにそれをあふれさせましたが、判明したように、あふれたコードは4つの異なるアルゴリズムによって制御されていました。 私はそれらにパッチを適用し始めましたが、どう思いますか? 各パッチについて、雪崩のようにコードの整合性を制御するコードがますます理解されています。 その結果、大量の手動パッチにjustれ、すべてのニュアンスを考慮して、ほぼ1週間の作業を要する自動ユーティリティ/デバッガーを作成する必要がありました。 そして最後に、私は保護コアの次のレベルに出くわしました。
ただし、これはもはや重要ではなく、意味も重要です。必要であれば、チェックサムの些細な検証であっても、クラッカーにまともな頭痛をもたらす可能性があります。
さて、今現実に。
整合性チェックコードを検出するために、クラッカーはMBPを使用しました。
そして、ページにPAGE_GUARD属性を割り当てることで、それらがどのように機能するかを覚えています。 したがって、デバッガーの原理を知っているので、これを防ぐことができます。この属性を削除するだけで、デバッガーが制御するはずのメモリーへのアクセスに応答しなくなります。
確かに、微妙な違いがあります。これは、デバッガーがインターセプトして呼び出しを禁止できるため、脆弱なVirtualProtectを呼び出すことで実現できます。 しかし、これには逆スレッドのボルトもあります。たとえば、この記事で説明されているように行うことができ
ます 。
真実は、デモアプリケーションでPAGE_GUARDを削除するオプションを考慮しないことです。 しかし、心配しないでください。別の興味深い方法を紹介します。これについては、もう少しニュアンスを考慮する必要があるので、少し後で説明します。
これからは、アプリケーションの整合性制御コードは、ハッキングされないように(単純化するために)書かれていると思います...
デバッガー検出
さて、今、私たちは絵とデバッガの助けを借りてフォームが欲しいという結論に達しました。 もちろん、それを検出する方法を学ぶ必要があります。 とりあえず、IsDebuggerPresent関数について説明しましょう。これで十分です。
コードを書きます:
function IsDebuggerPresent: BOOL; stdcall; external kernel32; procedure TForm1.FormCreate(Sender: TObject); begin if IsDebuggerPresent then begin MessageBox(Handle, ' .', PChar(Application.Title), MB_ICONERROR); TerminateProcess(GetCurrentProcess, 0); end; end;
すべてが非常に単純です。デバッガの下にいる場合、この関数はTrueを返します。
アプリケーションの整合性をチェックするためのコードは非常に複雑であるため、パッチを適用することは不可能であり、この関数の呼び出しは保護された領域に配置されると想定しています。
この場合、クラッカーは何に適用されますか?
アプリケーション本体にパッチを適用できないという事実を考えると、実際には3つのオプションしかありません。
- BPをこの関数の呼び出しに配置し、呼び出しの結果を置き換えます。
- 常にFalseを返すように、この関数のコードを修正します
- デバッグされたプロセスのアドレス空間でPeb.BeingDebugged変数を変更します。
3番目のオプションに対処することは困難です(可能ですが、必須ではありません)が、最初の2つをより詳細に検討し、より正確には2番目のオプションを検討します。 最初に、0xCCオペコードを使用してBPをインストールするときに、アプリケーションコードパッチも生成されます。
最初に、デバッグされたアプリケーションのこのコードをFormCreateプロシージャに追加します。
procedure TForm1.FormCreate(Sender: TObject); var P: PCardinal; begin P := GetProcAddress(GetModuleHandle(kernel32), 'IsDebuggerPresent'); ShowMessage(IntToHex(P^, 8));
IsDebuggerPresent関数の最初の4バイトが表示されます。
そのようなコードを書くことはできません:
function IsDebuggerPresent: BOOL; stdcall; external kernel32; procedure TForm1.FormCreate(Sender: TObject); var P: PCardinal; begin P := @IsDebuggerPresent; ShowMessage(IntToHex(P^, 8));
第2の実施形態では、静的関数を使用し、アドレスは関数本体の先頭を指すのではなく、JMP形式のアダプターがあるインポートテーブルを指します。
コードを実行して、値を思い出しましょう。
各システムの下では異なります。たとえば、XPでは元の関数の本体になり、7つではカーネルベースのアナログ用のアダプターがあります。 次の図に対応する値9090F3EBを取得しました。
次に、記事の2番目の部分からデバッガーを取り上げて、OnBreakPointメソッドでこのコードを使用してこの関数の本体にパッチを適用します。
procedure TTestDebugger.HideDebugger; const PachBuff: array [0..2] of Byte = ( $31, $C0,
ニュアンスがあります。kernel32.dllライブラリのアドレスはすべてのアプリケーションで同じであるため、IsDebuggerPresent関数のアドレスはデバッガーとデバッグされたアプリケーションで同じになります。
パッチの意味は、EAXレジスタを無効にすることです。これにより、関数の結果が返され、この関数を呼び出すコードに戻ります。
デバッガーを起動すると、アプリケーションが起動し、プロセスメモリでの干渉の結果、デバッガーのFormCreate関数のコードが検出されなくなります。 確かに、この関数の最初の4バイトを読み取るコードは、番号9090F3EBではなく、パッチのオペコードに対応する番号90C3C031を返します。
特定の関数の本体にパッチが適用されていることをどのように判断できますか? 原則として、この関数の最初の4バイトはディスク上にあるkernel32.dllファイルから読み取ることができますが、この場合、ライブラリ本体を開くと、同じパッチが適用されたファイルへのパスに置き換えられ、すべてが正常であることが確認されます。
しかし、実際にはめったに使用されない別の方法があります(間違えていなければ、一度だけ会ったことがあります)。
ディスクから正しい値を読み取ることができないため、他のプロセスのメモリから必要な4バイトを読み取ることで取得できます。
もちろん、このプロセスがデバッガーの下にあり、同じ方法で必要な機能をインターセプトする可能性はわずかですが、非常に小さいです。その結果、次のコードを記述します。 function IsDebuggerPresent: BOOL; stdcall; external kernel32; procedure TForm1.CheckIsDebugerPresent; var Snapshot: THandle; ProcessEntry: TProcessEntry32; ProcessHandle: THandle; pIsDebuggerPresent: PDWORD; OriginalBytes: DWORD; lpNumberOfBytesRead: DWORD; begin pIsDebuggerPresent := GetProcAddress(GetModuleHandle(kernel32), 'IsDebuggerPresent'); Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if Snapshot <> INVALID_HANDLE_VALUE then try ProcessEntry.dwSize := SizeOf(TProcessEntry32); if Process32First(Snapshot, ProcessEntry) then begin repeat if ProcessEntry.th32ProcessID = GetCurrentProcessId then Continue; ProcessHandle := OpenProcess(PROCESS_ALL_ACCESS, False, ProcessEntry.th32ProcessID); if ProcessHandle <> 0 then try if ReadProcessMemory(ProcessHandle, pIsDebuggerPresent, @OriginalBytes, 4, lpNumberOfBytesRead) then begin if OriginalBytes <> pIsDebuggerPresent^ then begin MessageBox(Handle, ' IsDebuggerPresent .', PChar(Application.Title), MB_ICONERROR); TerminateProcess(GetCurrentProcess, 0); end; if IsDebuggerPresent then begin MessageBox(Handle, ' .', PChar(Application.Title), MB_ICONERROR); TerminateProcess(GetCurrentProcess, 0); end; end; finally CloseHandle(ProcessHandle); end; until not Process32Next(Snapshot, ProcessEntry) end; finally CloseHandle(Snapshot); end; end; procedure TForm1.FormCreate(Sender: TObject); begin CheckIsDebugerPresent; CheckCodeProtect; end;
ここでは頭が良くなく、TlHelp32の標準機能を利用して、たとえばプロセスのリストを十分に取得しました。したがって、ノートブックの別のメモ-可能な限り、重要なAPI関数の整合性を常に確認します。これは、既知の方法で行う必要はありません。はい、まあ、ここにも別のニュアンスがあります。7の下で、kernel32.dllからIsDebuggerPresentを呼び出すと、kernelbase.dllから同じ関数を呼び出すことになります。その結果、すべての変更の後、この機能は完全性監視システムにしっかりと愛用され、額にそれをパッチすることはすでに非常に問題があります。この保護を回避する方法については後で説明しますが、今度は他のことを見てみましょう。プロセスへのデバッガーの接続を検出します。
デバッガの下でアプリケーションを起動する前に、デバッガなしで何を起動する必要があるかを見てください。デバッグのすべてのチェックに合格するまで待ち、その後デバッガをアプリケーションに接続しますか?はい、この場合、すべてのコードは機能しません。むしろ、部分的には機能します。または、そのような怒りを検出するために、たとえば、タイマーを設定し、CheckIsDebugerPresentプロシージャを定期的に呼び出すことができますが、事実上、接続を検出するためにこれを必要としません。実際には、DebugActiveProcess関数がデバッガーで呼び出されると、DbgUiRemoteBreakin関数がデバッグされたアプリケーションで常に呼び出されます。これを知って、次のトリックを上げていきます。自身またはDbgUiRemoteBreakin関数の本体にパッチを適用し、開始時にTerminateProcess関数のアドレスに遷移を追加するため、デバッガーがプロセスに接続されるとすぐにプロセスが終了します。次のコードブロックを作成します。 type TDbgUiRemoteBreakinPath = packed record push0: Word; push: Byte; CurrProc: DWORD; moveax: byte; TerminateProcAddr: DWORD; calleax: Word; end; procedure TForm1.BlockDebugActiveProcess; var pDbgUiRemoteBreakin: Pointer; Path: TDbgUiRemoteBreakinPath; OldProtect: DWORD; begin pDbgUiRemoteBreakin := GetProcAddress(GetModuleHandle('ntdll.dll'), 'DbgUiRemoteBreakin'); if pDbgUiRemoteBreakin = nil then Exit; Path.push0 := $006A; Path.push := $68; Path.CurrProc := $FFFFFFFF; Path.moveax := $B8; Path.TerminateProcAddr := DWORD(GetProcAddress(GetModuleHandle(kernel32), 'TerminateProcess')); Path.calleax := $D0FF; if VirtualProtect(pDbgUiRemoteBreakin, SizeOf(TDbgUiRemoteBreakinPath), PAGE_READWRITE, OldProtect) then try Move(Path, pDbgUiRemoteBreakin^, SizeOf(TDbgUiRemoteBreakinPath)); finally VirtualProtect(pDbgUiRemoteBreakin, SizeOf(TDbgUiRemoteBreakinPath), OldProtect, OldProtect); end; end; procedure TForm1.FormCreate(Sender: TObject); begin BlockDebugActiveProcess; CheckIsDebugerPresent; CheckCodeProtect; end;
そのようなパッチの結果として、次のコードがDbgUiRemoteBreakin関数の先頭に配置されます:つまり、TerminateProcess関数に必要なおよそ2つのパラメーターが大まかにスタックに配置されます(逆の順序で移動します)。現在のプロセス。その後、EAXレジスタはTerminateProcess関数のアドレスで初期化され、呼び出されます。記事の2番目の部分からデバッガーを使用してプロセスに参加しようとすると、CREATE_PROCESS_DEBUG_EVENTイベントの到着のみが表示されますが、このイベントの到着時でも、デバッグされたプロセスでは何もできません。たとえば、BPのインストールは失敗します。などほとんどのデバッガーでは、これで十分です。残念ながら、これは装甲貫通オプションではありません。DebugActiveProcessを呼び出す前に元のコードを返すことで、アプリケーションの本体にパッチを適用することを妨げるものは何もないからです。(確かに、私はこれを見たことがないが、それでも...)メモリブレークポイントバイパス
既に述べたように、PAGE_GUARDページのセキュリティ属性を確認することにより、MBPの存在を判断できます。これは、VirtualQuery関数を呼び出すことで実行できます。または、VirtualProtectを呼び出して属性を簡単に再割り当てできます。しかし、別のトリッキーな方法があり、それはReadProcessMemoryと呼ばれます。これは、デバッガーでデバッグされたプロセスからデータを読み取るのと同じ関数です。そのニュアンスは次のとおりです。PAGE_GUARDフラグで保護されたページからデータを読み取ろうとすると、対応するページのデータブロックはゼロで埋められ、デバッガーではEXCEPTION_GUARD_PAGEイベントが発生しません。これが「地域のサイレントチェック」です。アプリケーションコードの整合性をチェックするときに使用し、MVRがインストールされている場合、データは正しいと見なされず、その結果、チェックサムは期待されるものと収束しません。さらに、記録を制御するハードウェアブレークポイントがこの関数の読み取り元のアドレスに設定されている場合、読み取り/書き込みデバッガーはその操作に関する通知も受信しません。したがって、CalcCheckSum関数を次のように書き換えます。 function TForm1.CalcCheckSum(Addr: Pointer; Size: Integer): DWORD; var pRealData, pCursor: PByte; I: Integer; Dumee: DWORD; begin pRealData := GetMemory(Size); try ReadProcessMemory(GetCurrentProcess, Addr, pRealData, Size, Dumee); Result := 0; pCursor := pRealData; for I := 0 to Size - 1 do begin if pCursor^ <> 0 then Inc(Result, pCursor^) else Dec(Result); Inc(pCursor); end; finally FreeMemory(pRealData); end; end;
このように、1つの機能で、BP、内務省、さらにはNVRからも防御します。これを回避する方法は?
さて、アプリケーション保護コードはもう複雑になりません。この情報で十分です。記事の最後でいくつかのニュアンスについて説明しますが、完全性検証コードを不可解と見なすことに同意した瞬間を考慮して、デバッガーに基づいてローダーを作成しようとします。したがって、検証本体にパッチを適用しません。ワイヤーフレームを作成しています。開始と停止は次のようになります。 constructor TTestDebugger.Create(const Path: string); begin FCore := TFWDebugerCore.Create; if not FCore.DebugNewProcess(Path, True) then RaiseLastOSError; FCore.OnCreateProcess := OnCreateProcess; FCore.OnLoadDll := OnLoadDll; FCore.OnDebugString := OnDebugString; FCore.OnBreakPoint := OnBreakPoint; FCore.OnHardwareBreakpoint := OnHardwareBreakpoint; FCore.OnUnknownBreakPoint := OnUnknownBreakPoint; FCore.OnUnknownException := OnUnknownException; end; destructor TTestDebugger.Destroy; begin FCore.Free; inherited; end;
私は二次ハンドラーをスキップします。それらは例のソースコードで見ることができます。原則として、新しいものは何もありません。すべては記事の最後の部分ですでに説明されています。最初のタスクは、アプリケーションによるデバッガーの検出を無効にすることです。アプリケーションはIsDebuggerPresentの整合性をチェックし、(タスクに従って)チェックにパッチを適用できないため、Peb.BeingDebuggedパラメーターの値を変更するオプションが1つしかありません。次のコードでそれをやってみましょう: procedure TTestDebugger.HideDebugger(hProcess: THandle); var pProcBasicInfo: PROCESS_BASIC_INFORMATION; pPeb: PEB; ReturnLength: DWORD; begin if NtQueryInformationProcess(hProcess, 0, @pProcBasicInfo, SizeOf(PROCESS_BASIC_INFORMATION), @ReturnLength) <> STATUS_SUCCESS then RaiseLastOSError; if not ReadProcessMemory(hProcess, pProcBasicInfo.PebBaseAddress, @pPeb, SizeOf(PEB), ReturnLength) then RaiseLastOSError; pPeb.BeingDebugged := False; if not WriteProcessMemory(hProcess, pProcBasicInfo.PebBaseAddress, @pPeb, SizeOf(PEB), ReturnLength) then RaiseLastOSError; end;
ここではすべてが簡単です。プロセス環境ブロックのアドレスを取得し、BeingDebuggedパラメーターを変更して、すべてを書き戻します。したがって、IsDebuggerPresent関数はデバッガーへの応答を停止します。使用される構造体の宣言は、デモソースにあります。最初の段階、今度は2番目の段階を完了しました。誤って入力されたコードにアプリケーションが応答しないようにし、どのような場合でも画像を表示する必要があります。これを行います:おそらくデバッガで変数の値を複数回変更している可能性があります(これについては、記事の最初の部分で説明しました)。ここで、同様のことを行います。あなたが覚えているように、JE命令は画像を表示する責任があります。粗い場合、ブール変数と値if..elseの条件があると想像してください。そうでなければ、そのような条件で中断すると、コードの実行条件を制御できます。値変数の変更によって、正確に何を実行すべきかを示します:thenまたはelseブロック。JEオペレーターは、このようなブール変数に基づいて遷移を決定しますが、ZFフラグの形式で表示されます。フラグが有効な場合、新しいアドレスへのジャンプが発生します。したがって、このフラグの値を必要な値に変更できるように、JE命令でアプリケーションを中断することがタスクです。これを行うには、HBPをJE命令のアドレスに設定します。これは、安全なアプリケーションを制御できない唯一のものです。このアドレスを見つける方法私は欠場します。この例では、アーカイブにはcrackme.exe実行可能ファイルが含まれています。特に、再コンパイルのたびに、イルカのバージョンなどによってこのアドレスが異なるため、アーカイブに特別に配置します。コンパイルされた実行可能ファイルでは、このアドレスは既に計算されており、値0x467840と等しくなっています。コードを書くことは残っています: procedure TTestDebugger.OnBreakPoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord; BreakPointIndex: Integer; var ReleaseBreakpoint: Boolean); begin if ExceptionRecord.ExceptionAddress = Pointer(FCore.DebugProcessData.EntryPoint) then begin Writeln; Writeln(Format('!!! --> Process Entry Point found. Address: %p', [Pointer(FCore.DebugProcessData.EntryPoint)])); Writeln; HideDebugger(FCore.DebugProcessData.AttachedProcessHandle); FCore.SetHardwareBreakpoint(ThreadIndex, Pointer($467840), hsByte, hmExecute, 0, 'wait JE'); end else begin Writeln; Writeln(Format('!!! --> BreakPoint at addr 0X%p - "%s"', [ExceptionRecord.ExceptionAddress, FCore.BreakpointItem(BreakPointIndex).Description])); Writeln; end; end;
その後、HBPで割り込みを処理し、正しいフラグ値を設定する必要があります。 procedure TTestDebugger.OnHardwareBreakPoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord; BreakPointIndex: THWBPIndex; var ReleaseBreakpoint: Boolean); var ThreadData: TThreadData; begin Writeln; ThreadData := FCore.GetThreadData(ThreadIndex); Writeln(Format('!!! --> Hardware BreakPoint at addr 0X%p - "%s"', [ExceptionRecord.ExceptionAddress, ThreadData.Breakpoint.Description[BreakPointIndex]])); FCore.SetFlag(ThreadIndex, EFLAGS_ZF, True); Writeln; end;
さて、それですべてです。実行して、左の値を入力して、写真を楽しんでください。結果は次のようになります:それで通常は起こりますが、あなたはあなたが鎧を貫通する保護を書いたと思います、そしてそれが膝にかかると、もちろん、必ずしもそうではありませんが、それは起こります:)ハードウェアブレークポイントの検出:
保護されたアプリケーションのフレームワーク内でNVRを検出することを意図的に停止しませんでした。そのようなチェックがある場合、かなり複雑なバイパスコードを記述する必要があるためです。そして一般的には、もちろん、それらの存在を確認することをお勧めします。したがって、デバッガーを閉じて通常の動作が可能になります。HBPの存在の検出は非常に簡単です。同じGetThreadContextを介して実装し、DR7レジスタをチェックする(空でない場合、HBPが立っている)か、API関数呼び出しによってインターセプトされないように、例外をスローしてスレッドコンテキストを取得できます。これが最初のオプションです procedure TForm1.CheckHardwareBreakPoint; var Context: TContext; begin Context.ContextFlags := CONTEXT_DEBUG_REGISTERS; GetThreadContext(GetCurrentThread, Context); if Context.Dr7 <> 0 then begin MessageBox(Handle, ' HardwareBreaakPoint.', PChar(Application.Title), MB_ICONERROR); TerminateProcess(GetCurrentProcess, 0); end; end;
2番目のオプションでは、デバッグ例外が発生し、_except_handlerハンドラーのスレッドコンテキストに関する情報が削除されます。 type
ところで、興味深い点。例外ハンドラに送られる情報の量に注意してください。このすべての情報は例外ハンドラでは利用できないため、SEHの短いラッパーを除いてtry..finally..exceptを呼び出すことがよくあります。要約する
デバッガーに対処するいくつかの方法を知っていますが、それらに対抗する方法はわかっていますが、結論を出すための記事があります。例付きのソースコードは、次のリンクから取得できます。http ://rouse.drkb.ru/blog/dbg_part3.zipこれで、私のタスクを完了できます。デバッガについて伝えたかったことはすべて、私は言いました。当初は、たった1つの記事が計画されていましたが、最終的にはどれだけの資料が作成されるかがわかります。©Alexander(Rouse_)BagelMoscow、2012年11月