ROP - Return Oriented Programing
基本情報
**Return-Oriented Programming (ROP)**は、**No-Execute (NX)やData Execution Prevention (DEP)などのセキュリティ対策を回避するために使用される高度なエクスプロイト技術です。シェルコードを注入して実行する代わりに、攻撃者はバイナリやロードされたライブラリに既に存在するコード片("ガジェット"**として知られる)を利用します。各ガジェットは通常、ret
命令で終わり、データをレジスタ間で移動したり算術演算を行ったりするなど、小さな操作を実行します。これらのガジェットを連鎖させることで、攻撃者は任意の操作を実行するペイロードを構築し、NX/DEP保護をバイパスできます。
ROPの動作方法
制御フローのハイジャック:まず、攻撃者は通常、バッファオーバーフローを悪用してスタック上の保存されたリターンアドレスを上書きすることで、プログラムの制御フローをハイジャックする必要があります。
ガジェットの連鎖:次に、攻撃者は注意深くガジェットを選択して連鎖させ、必要なアクションを実行します。これには、関数呼び出しの引数を設定したり、関数を呼び出したり(例:
system("/bin/sh")
)、必要なクリーンアップや追加の操作を処理したりすることが含まれる可能性があります。ペイロードの実行:脆弱な関数が返されると、合法的な場所に戻る代わりに、ガジェットの連鎖を実行し始めます。
ツール
通常、ガジェットはROPgadget、ropper、または直接pwntools(ROP)を使用して見つけることができます。
x86のROPチェーンの例
x86(32ビット)呼び出し規約
cdecl:呼び出し元がスタックをクリーンアップします。関数引数はスタックに逆順でプッシュされます(右から左に)。引数は右から左にスタックにプッシュされます。
stdcall:cdeclに似ていますが、スタックのクリーンアップは呼び出し先が担当します。
ガジェットの検索
まず、バイナリまたはそのロードされたライブラリ内で必要なガジェットを特定したと仮定しましょう。興味を持つガジェットは次のとおりです:
pop eax; ret
:このガジェットはスタックのトップの値をEAX
レジスタにポップしてから戻り、EAX
を制御できるようにします。pop ebx; ret
:上記と同様ですが、EBX
レジスタ用であり、EBX
を制御できるようにします。mov [ebx], eax; ret
:EAX
の値をEBX
が指すメモリ位置に移動してから戻ります。これは通常、write-what-whereガジェットと呼ばれます。さらに、
system()
関数のアドレスが利用可能です。
ROPチェーン
pwntoolsを使用して、次のようにROPチェーンの実行のためにスタックを準備します。system('/bin/sh')
を実行することを目指しています。チェーンが次のように始まることに注目してください:
アライメント目的の
ret
命令(オプション)system
関数のアドレス(ASLRが無効で既知のlibcの場合、詳細はRet2libにあります)system()
からの戻りアドレスのプレースホルダ"/bin/sh"
文字列アドレス(system関数のパラメータ)
x64でのROPチェーンの例
x64(64ビット)呼び出し規約
Unix系システムでは、最初の6つの整数またはポインタ引数はレジスタ
RDI
、RSI
、RDX
、RCX
、R8
、およびR9
に渡されます。追加の引数はスタックに渡されます。戻り値はRAX
に配置されます。Windows x64 呼び出し規約では、最初の4つの整数またはポインタ引数には
RCX
、RDX
、R8
、およびR9
が使用され、追加の引数はスタックに渡されます。戻り値はRAX
に配置されます。レジスタ: 64ビットレジスタには
RAX
、RBX
、RCX
、RDX
、RSI
、RDI
、RBP
、RSP
、およびR8
からR15
が含まれます。
ガジェットの検索
今回は、RDI レジスタを設定し(system() に "/bin/sh" 文字列を引数として渡すため)、その後 system() 関数を呼び出すためのガジェットに焦点を当てます。以下のガジェットを特定したと仮定します:
pop rdi; ret: スタックのトップの値を RDI にポップしてから戻ります。system() の引数を設定するために必要です。
ret: 単純なリターンで、一部のシナリオでスタックの整列に役立ちます。
そして、system() 関数のアドレスを知っているとします。
ROPチェーン
以下は、pwntools を使用して x64 で system('/bin/sh') を実行するためのROPチェーンを設定して実行する例です:
スタックアライメント
x86-64 ABI は、call命令が実行される際にスタックが16バイトに整列されることを保証します。LIBC は、パフォーマンスを最適化するためにSSE命令(例: movaps)を使用し、この整列が必要です。スタックが適切に整列されていない場合(つまりRSPが16の倍数でない場合)、systemなどの関数の呼び出しはROPチェーンで失敗します。これを修正するには、ROPチェーンでsystemを呼び出す前にretガジェットを追加するだけです。
x86とx64の主な違い
x64は最初の数値引数にレジスタを使用するため、単純な関数呼び出しにはx86よりも少ないガジェットが必要ですが、適切なガジェットを見つけてチェーンすることは、レジスタの数の増加とアドレス空間の拡大により、より複雑になる可能性があります。x64アーキテクチャの増加したレジスタ数と大きなアドレス空間は、特にReturn-Oriented Programming(ROP)の文脈でのエクスプロイト開発において、機会と課題の両方を提供します。
ARM64のROPチェーンの例
ARM64の基礎と呼び出し規約
この情報については、次のページをチェックしてください:
pageIntroduction to ARM64v8ROPに対する保護
スタックキャナリー: BOFの場合、ROPチェーンを悪用するためには、スタックキャナリーをバイパスしてリターンポインタを上書きする必要があります。
ガジェットの不足: 十分なガジェットがない場合、ROPチェーンを生成することはできません。
ROPベースのテクニック
ROPは単なる任意のコードを実行するためのテクニックであることに注意してください。ROPを基に、多くのRet2XXXテクニックが開発されました:
Ret2lib: ROPを使用して、ロードされたライブラリから任意のパラメータ(通常は
system('/bin/sh')
のようなもの)を持つ任意の関数を呼び出します。
Ret2Syscall: ROPを使用して、
execve
などのシスコールを呼び出す準備をし、任意のコマンドを実行します。
EBP2Ret & EBP Chaining: 最初のものはEIPの代わりにEBPを悪用してフローを制御し、2番目はRet2libに似ていますが、この場合、フローは主にEBPアドレスで制御されます(ただし、EIPの制御も必要です)。
その他の例と参照
64ビット、Pieおよびnx有効、キャナリなし、
vsyscall
アドレスでRIPを上書きし、スタック内の次のアドレスに戻ることで、フラグを漏洩させる関数の一部を取得するための部分的な上書きを行います。arm64、ASLRなし、スタックを実行可能にするROPガジェットとスタック内のシェルコードにジャンプします
Last updated