BROP - Blind Return Oriented Programming
基本情報
この攻撃の目標は、脆弱なバイナリに関する情報がなくても、バッファオーバーフローを介して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 ガジェットを使用します。これは、このガジェットにアクセスすると、rsi
と rdi
を制御するガジェットを取得できるためです:
これらがガジェットです:
pop rsi; pop r15; ret
pop rdi; ret
これらのガジェットを使用すると、関数の引数を2つ制御することが可能であることに注意してください。
また、ret2csuガジェットには非常にユニークなシグネチャがあるため、スタックから6つのレジスタをポップすることになります。そのため、次のようなチェーンを送信すると:
'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
STOPが実行された場合、これは基本的にスタックから6つのレジスタをポップするアドレスが使用されたことを意味します。または使用されたアドレスがSTOPアドレスであることを意味します。
この最後のオプションを取り除くために、前のものが6つのレジスタをポップしたことを確認するために、次のような新しいチェーンが実行され、STOPガジェットが実行されない必要があります:
'A' * offset + canary + rbp + ADDR
ret2csuガジェットのアドレスを知っている場合、rsi
と rdi
を制御するガジェットのアドレスを推測することができます。
6. PLTを見つける
PLTテーブルは、0x400000から検索するか、スタックからリークしたRIPアドレスから検索することができます(PIEが使用されている場合)。テーブルのエントリは16B(0x10B)で区切られており、1つの関数が呼び出されると、引数が正しくなくてもサーバーはクラッシュしません。また、PLTのエントリ + 6Bのアドレスを確認してもクラッシュしません。これは最初に実行されるコードです。
したがって、次の動作をチェックしてPLTテーブルを見つけることができます:
'A' * offset + canary + rbp + ADDR + STOP
-> クラッシュしない'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
-> クラッシュしない'A' * offset + canary + rbp + (ADDR + 0x10) + STOP
-> クラッシュしない
7. strcmpを見つける
strcmp
関数は、比較される文字列の長さを示すレジスタ rdx
を設定します。rdx
は 3番目の引数であり、後でプログラムをリークさせるために write
を使用するためにそれが0より大きい必要があります。
strcmp
の場所を見つけることができます。PLTでの振る舞いに基づいて、関数の最初の2つの引数を制御できるようになったことを利用して:
strcmp(<読み取り不可アドレス>, <読み取り不可アドレス>) -> クラッシュ
strcmp(<読み取り不可アドレス>, <読み取り可能アドレス>) -> クラッシュ
strcmp(<読み取り可能アドレス>, <読み取り不可アドレス>) -> クラッシュ
strcmp(<読み取り可能アドレス>, <読み取り可能アドレス>) -> クラッシュしない
これを確認するために、PLTテーブルの各エントリを呼び出すか、PLTスローパスを使用することができます。これは、PLTテーブルのエントリ + 0xb(dlresolve
を呼び出す)を呼び出し、スタック内にスキャンしたいエントリ番号(ゼロから開始)を続けるものです:
strcmp(<読み取り不可アドレス>, <読み取り可能アドレス>) -> クラッシュ
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> クラッシュするstrcmp(<読み取り可能アドレス>, <読み取り不可アドレス>) -> クラッシュ
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<読み取り可能アドレス>, <読み取り可能アドレス>) -> クラッシュしない
b'A' * offset + canary + 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