簡単に言えば、私はいつか退屈し、友人のマキシマス・ハッカーマンに何かをするように頼みました。すぐに、彼は「Reverseme.exe」(Virustotal Link)という単純な実行可能ファイルを介して、「あなた自身のアプリケーションに隠されたメッセージを印刷する」という簡単なコメントを送りました。当然、CPPヘッダーファイルは提供されていません。実行可能ファイルを実行すると、コンソールベースのアプリケーションとして開き、「すべてのマジックパケットを送信し、すぐに出て、Beep Boop!」を印刷します。そして、ほとんどすぐに終了します。 「すぐに」は主観的だと思います。
幸いなことに、反政府勢力があるようには見えないので、ハッカーマンはその日に気分が良かったと思います。
WindBGと廃止装置を取り付けた後、アプリケーションがスローし、最初に見えるものが、 divide by zeroであることがわかります。
ただし、綿密な検査では、この例外がアプリケーションの唯一の目に見えるメッセージを印刷する直前にスローされることがわかります。 2つの車両(ベクトル付き例外ハンドラー)がこのポイントのわずかに前に登録されていることに注意することが重要です。そのため、この例外は意図的であり、制御フローをマングルするために使用される可能性があります。本当に私たちを捨てようとする安いトリック本当に!
現時点では、車両を忘れて、アプリケーションがパケットを「送信」している場合、Winsock送信機能を使用してそうすることを想定するのは安全です(ただし、アプリケーションは「魔法」パケットであると明確に言っているので、表示されます)。
予想どおり、魔法でパケットを送信できないことが判明しているため、Winsock send関数は実際にインポートされています。
送信関数にブレークポイントを配置することにより、送信時に抽象化できる関連データがあるかどうかを確認して確認できます。悲しいことに、バッファが関数にロードされるまでに、データはすでに暗号化されています。ただし、バッファーの長さは常に3バイトで、ソケットのハードコード値は常に0x69 (ニース)に結合し、送信機能ブレークポイントが合計14倍ヒットしていることを判断できます。
上記の暗号化を回避する方法はいくつかあります。 1つはそれを完全に逆転させることです。これは多くの努力であることが判明する可能性があります。もう1つは、暗号化の前に目的のデータを見つけて、暗号化される前に抽象化することです。後者は前者よりもかなり簡単なので、私たちはそれを使用しています。暗号化を自由に逆転させてください、私は短い見方をしていましたが、それは恐ろしくありません。あるいは、派手に感じている場合は、ソケットハンドルの値を常に上書きし、受信アプリケーションの取引をすることができます。
悲しいことに、最初のメッセージを除いて、読み取り専用データセグメントに有用な文字列はないため、その側面からの役立つポインターはありません!
車両に戻ると、両方の登録されたハンドラーが、いくつかの未知のオブジェクトを割り当てられたメモリバッファーにコピーするために使用されることがわかります。これは、MemcPyを使用して、2番目のパラメーターとして関数を使用して行われるように見えます。これらのハードコードされた値がどの時点でも14を超えないことは注目に値します。ハードコードされた値を除いて、基本的にコードごとに同一のコードであるため、スクリーンショットに車の1つのみを含めました。
MEMCPY関数の1つでブレークポイント化し、2番目のパラメーターのサブ機能を検査することにより、ハードコードされた整数(以下の例では13 / 0DH)がA1パラメーターの最初のバイトに設定され、A1+1にはポインターが解体された直後に文字が含まれていることがわかります。
この関数の他の14の呼び出しのいくつかを確認すると、同じ動作が繰り返されることがわかります。対応する数値を順序の指標として使用して、1〜14のキャラクターを配置すると、読みやすい言葉を綴り始めることがわかります。今、私たちは非常に怠け者であり、それが何であるかがわかったら、メッセージを印刷するためのコンソールアプリを作ることができますが、それは本当に不正行為のように感じます。さらに、メッセージはある時点で変更される場合があります。それでは、暗号化される前に値を傍受するためにコード洞窟を書きましょう。
申し訳ありませんが、これにはC ++を使用しています! C#を使用することを好む場合は、970万の機能のプロセスを呼び出して、完了したらここに戻ってきてください。とにかく、最初に洞窟をコーディングする場所を決定する必要があります。幸いなことに、私たちはすでに知っています!ただし、上記の関数を分析することにより、ただし一時的に、強調表示されたポイントRDXにはインデックスが含まれ、 RDX+1には対応する文字が含まれていることがわかります。以下は、議論された関数のアセンブリコードです。
現在、論理的に言えば、3番目のパラメーターは本当に気にしないが、 RDXレジスタとRDX+1インターセプトしたいので、ジャンプを取得するのに最適な場所はmov [rsp+arg_8], rdxにあります。この子供を行うには、 MOV命令に10バイトのバイトが必要になります。コード洞窟のアドレスをレジスタに移動します(10時のニュースの後半でRAXを使用します)、 JMP命令の2バイトをレジスタにジャンプします。さらに数学からPTSDを持っている人のために、それは12バイトで合計です。ここで、すべてのおばさんに行って、そのコードをWriteProcessMemoryでスパゲット化する前に、上記のアセンブリに12バイトを置き換える理想的な場所がないことを考慮する必要があります。オフセット0x2905 ( mov [rsp+arg_8], rdx )からジャンプしたい場合、それを行うには12バイトが必要な場合は、2つのMOV指示の間にSMACK BANGである0x2917オフセットする必要があります。残念ながら、単にバイトをそこに書くだけで、それはアセンブリを完全にマングルし、おそらく「興味深い」副作用を引き起こす可能性があります。その結果、パディングのための1バイトの指示を追加して、指示の終わりまで丸める方が簡単になります(おそらく、もう少しハッキーで申し訳ありません)。ウェルカム、0x90。
とにかく、私たちの計画が何であるかがわかったので、コードを書いてみましょう。
以下は、バイトがアプリケーションのアセンブリに書き込まれると、コード洞窟への最初のジャンプがどのように見えますか。
ただし、実際にアセンブリを書き込み、交換する前に、中断された状態で申請を開始する必要があります。これにより、アプリケーションのランタイムが初期段階で停止し、アプリケーションが関心のある命令に到達する前にメモリの変更を行うことができます。以下のコード抽出物はこのプロセスを示しています。
プロセスが生まれたので、プロセスの基本アドレスを取得する必要があります。通常、これにはEnumprocessmodulesを使用できますが、メインプロセススレッドをすぐに停止したため、PEBには完全に人口の多いPEB_LDR_DATA構造、特にInMemoryOrderModuleListが含まれていないため、現在ベースアドレスを取得できません。ちなみに、これはMSDNのどこにも文書化されていないようです。幸いなことに、これは比較的簡単に回避できます。プロセスを非常に迅速に再開し、モジュールを照会し、プロセスが進行しすぎずに必要な情報を取得できるプロセスを再懸濁することにより。 Windowsのほとんどのものと同様に、Microsoftは、必要な機能を文書化しないことで、オペレーティングシステムが優れたシステムであることを繰り返し繰り返し好みます: NtSuspendProcessとNtResumeProcess 。便利なことに、私の父はビル・ゲイツと友達であり、彼はこれらの機能がntdll.dllで添えられていると言っているので、以前に作成した以下のクラスを使用してそれらを取得することができます。
必要な2つの機能が揃ったので、プロセスを再開し、モジュールを照会し、プロセスを再懸濁できます。
あなたは疑問に思うかもしれませんが、なぜ1つではなく2つのモジュールの発見を待っているのですか?まあ、Microsoftは、 EnumProcessModulesによって見つかった最初のモジュールがntdll.dllであり、2番目が実行可能なものになることも言及していないため、文書化されていないSpagettiネットの後ろにミートボールでハットトリックを獲得しようとしています。これは合理的に聞こえますが、実行可能ファイルが見つかると、ntdll.dllとインデックスを交換します。これが例です:
最初のモジュールのみをクエリした後の結果:
2つのモジュールを照会した後の結果:
さらに進む前に、コード洞窟への最初のジャンプを行うアセンブリコードを作成する必要があり、コードはそれ自体を洞窟にします。基本的に、オフセット0x2905から始まるメモリを上書きし、ジャンプし、コードケービングスパイを行い、 0x2911に戻って通常のプログラムフローを続けます。最初は、私たちがジャンプするアドレスが動的であり、まだ何があるのかわからないため、アドレスのRAXレジスタへの移動をMOV RAX, 0x0として宣言します。 RAX 、揮発性レジスタであり、とにかくすぐに上書きされるため、使用する安全なレジスタです。楽しい事実、これが任天堂が元のマリオ・ブラザーズプラットフォーマーをプログラムした方法であり、たくさんのジャンプで(私は面白いと思います)!以下は、コンパイラ内のコードの外観です。他の方法で作成できますが、必要なアセンブリ命令をBytecodeに分類することを選択しました。自宅でこれをやりたい場合は、このウェブサイトを使用してください。
実際のコード洞窟のコードはもう少し複雑であり、そのロジックもこのファイルにコメントされていますが、ここに大まかなプロセスがあります。
R10に移動しますRDXの下部をR11Bに移動しますRDXの2番目のバイトをR11Bに移動します+12 )からR10 ( 0x2911 )にジャンプしたアドレスを移動しますR10にジャンプすることも、スタックなどを保存するために(NOPスライドを使用して)コード洞窟に上書きするアセンブリコードを書き換える必要があります。このコードは、NOPSを除く「 Predetermined Assembly 」地域で参照されます。以下は、その醜い栄光のコード洞窟です:主にハードな部分を超えています。
この時点で、私たちは本質的に、記憶から隠されたメッセージを吸い上げるために必要なすべてを持っているので、それを実装する必要があります。要約すると、アセンブリロジックを表す2バイト配列、中断プロセスのベースアドレス、 modules[0] 、およびジャンプロジックを記述する必要がある場所のオフセットを表す2バイト配列が生成されました。以下のコードスニペットは、ジャンプするアドレス、コード洞窟のアドレス(ジャンプする)、3バイトメモリストレージのアドレスを作成し、アドレスをアセンブリコードに書き込み、アセンブリコードに中断されたプロセスを書き込みます。
codeCaveStorageAddrの魔法のハリーポッタースタイルのキャストは、アドレスをバイトに変換することであり、ループのハードコードされた値は、アセンブリアレイの動的アドレス配置用です。もちろん、これははるかにクリーンな方法で実行できます(魔法の数字を書いてはいけません)、私はそれらすべてのバイトを手動で書き留めなければならない後、怠け者です。書く最後のいくつかのビットは、読み取りのループです。ReadProcessMemory、メモリ、文字とインデックスを順序付きバイト配列に保存し、 WriteProcessMemoryを使用して、現在のメモリの読み取りが完成したときに、隠されたメッセージを印刷します。送信機能は14回しか発生しないことがわかっているため、バイト配列が充填されたら、whileループを終了します。これは、より多くのメモリ編集で変更され、プロセスが14のハードコード値を使用するのではなく、プロセスが「終了」し、ループを停止できることをアプリケーションに信号を送信できますが、この例では機能します。
結果?隠されたメッセージは、独自のコンソールアプリケーションに印刷されています!誰かが好奇心が強い場合、NPTは別のソフトウェアの最大値Hacker-I Called-Himが書いたものへの参照です。