BROP - Blind Return Oriented Programming

HackTricksのサポート

基本情報

この攻撃の目的は、脆弱なバイナリに関する情報がなくても、バッファオーバーフローを介してROPを悪用できるようにすることです。 この攻撃は次のシナリオに基づいています:

  • スタックの脆弱性とそれをトリガーする方法の知識。

  • クラッシュ後に再起動するサーバーアプリケーション。

攻撃

1. 脆弱なオフセットを見つける サーバーの故障が検出されるまで1文字追加して送信

2. キャナリーをブルートフォース してリークする

3. スタック内の保存されたRBPとRIP アドレスをブルートフォースしてリークする

これらのプロセスに関する詳細情報は、こちら(BF Forked & Threaded Stack Canaries)およびこちら(BF Addresses in the Stack)で見つけることができます。

4. ストップガジェットを見つける

このガジェットは、ROPガジェットによって実行された興味深い動作を確認するためのもので、実行がクラッシュしなかったことを示します。通常、このガジェットは実行を停止するものであり、特定のROPガジェットが実行されたことを確認するためにROPガジェットを探す際にROPチェーンの最後に配置されます。

5. BROPガジェットを見つける

このテクニックは、ret2csuガジェットを使用します。これは、このガジェットにアクセスすると、**rsirdi**を制御するガジェットを取得できるためです:

これらがガジェットです:

  • pop rsi; pop r15; ret

  • pop rdi; ret

これらのガジェットを使用すると、関数の引数を2つ制御することが可能です。

また、ret2csuガジェットは、スタックから6つのレジスタをポップするため、非常にユニークなシグネチャを持っています。そのため、次のようなチェーンを送信すると:

'A' * オフセット + キャナリー + rbp + ADDR + 0xdead * 6 + STOP

STOPが実行される場合、これは基本的にスタックから6つのレジスタをポップするアドレスが使用されたことを意味します。または使用されたアドレスがSTOPアドレスであることを意味します。

この最後のオプションを除外するために、前のものが6つのレジスタをポップしたことを確認するために、次のような新しいチェーンが実行され、STOPガジェットが実行されない必要があります:

'A' * オフセット + キャナリー + rbp + ADDR

ret2csuガジェットのアドレスを知っていると、rsirdiを制御するガジェットのアドレスを推測することができます。

6. PLTを見つける

PLTテーブルは0x400000からまたはスタックからのリークしたRIPアドレス(PIEが使用されている場合)から検索できます。テーブルのエントリは16B(0x10B)で区切られており、1つの関数が呼び出されると、引数が正しくなくてもサーバーはクラッシュしません。また、PLTのエントリ + 6Bのアドレスをチェックしてもクラッシュしません。これは最初に実行されるコードです。

したがって、次の動作をチェックしてPLTテーブルを見つけることができます:

  • 'A' * オフセット + キャナリー + rbp + ADDR + STOP -> クラッシュしない

  • 'A' * オフセット + キャナリー + rbp + (ADDR + 0x6) + STOP -> クラッシュしない

  • 'A' * オフセット + キャナリー + rbp + (ADDR + 0x10) + STOP -> クラッシュしない

7. strcmpを見つける

strcmp関数は、比較される文字列の長さをレジスタrdxに設定します。rdxは3番目の引数であり、後でwriteを使用してプログラムをリークさせるためにそれが0より大きい必要があります。

strcmpの場所をPLTで見つけることができ、2つの最初の引数を制御できることを利用して、次のような動作に基づいてPLT内のstrcmpの場所を見つけることができます:

  • strcmp(<読み取り不可アドレス>, <読み取り不可アドレス>) -> クラッシュ

  • strcmp(<読み取り不可アドレス>, <読み取り可能アドレス>) -> クラッシュ

  • strcmp(<読み取り可能アドレス>, <読み取り不可アドレス>) -> クラッシュ

  • strcmp(<読み取り可能アドレス>, <読み取り可能アドレス>) -> クラッシュしない

これを確認するために、PLTテーブルの各エントリを呼び出すか、PLTスローパスを使用することができます。これは、PLTテーブルのエントリ + 0xbdlresolveを呼び出す)を呼び出し、スタックに続いて調査したいエントリ番号(ゼロから開始)を配置することで、最初のエントリからすべてのPLTエントリをスキャンする方法です:

  • strcmp(<読み取り不可アドレス>, <読み取り可能アドレス>) -> クラッシュ

  • b'A' * オフセット + キャナリー + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP -> クラッシュする

  • strcmp(<読み取り可能アドレス>, <読み取り不可アドレス>) -> クラッシュ

  • b'A' * オフセット + キャナリー + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP

  • strcmp(<読み取り可能アドレス>, <読み取り可能アドレス>) -> クラッシュしない

  • b'A' * オフセット + キャナリー + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP

覚えておくべきこと:

  • BROP + 0x7は**pop RSI; pop R15; ret;**を指す

  • BROP + 0x9は**pop RDI; ret;**を指す

  • PLT + 0xbは**dl\_resolve**を呼び出す

strcmpを見つけたら、**rdx**を0より大きい値に設定できます。

通常、rdxにはすでに0より大きい値が格納されている場合が多いため、このステップは必要ないかもしれません。

### 8. Writeまたは同等の機能の検索

最後に、バイナリを外部に持ち出すためにデータを外部に持ち出すためのガジェットが必要です。そしてこの時点で2つの引数を制御し、rdxを0より大きく設定できる必要があります。

これを達成するために悪用できる3つの一般的な関数があります:

  • puts(data)

  • dprintf(fd, data)

  • write(fd, data, len(data)

しかし、元の論文では**write**のみが言及されているため、それについて話しましょう:

現在の問題は、PLT内のwrite関数の場所がわからず、かつソケットにデータを送信するためのfd番号もわからないということです。

ただし、PLTテーブルの場所はわかっており、その動作に基づいてwriteを見つけることが可能です。そして、サーバーと複数の接続を作成し、高いFDを使用して、いくつかの接続に一致することを期待することができます。

これらの関数を見つけるための動作のシグネチャ:

  • 'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP -> データが印刷された場合、putsが見つかりました

  • 'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP -> データが印刷された場合、dprintfが見つかりました

  • 'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP -> データが印刷された場合、writeが見つかりました

自動的な悪用

参考文献

Last updated